diff --git a/services/clsi/.dockerignore b/services/clsi/.dockerignore new file mode 100644 index 0000000000..bcbd758418 --- /dev/null +++ b/services/clsi/.dockerignore @@ -0,0 +1,11 @@ +node_modules/* +gitrev +.git +.gitignore +.npm +.nvmrc +nodemon.json +cache/ +compiles/ +db/ +output/ diff --git a/services/clsi/.eslintrc b/services/clsi/.eslintrc new file mode 100644 index 0000000000..a97661b15f --- /dev/null +++ b/services/clsi/.eslintrc @@ -0,0 +1,86 @@ +// this file was auto-generated, do not edit it directly. +// instead run bin/update_build_scripts from +// https://github.com/sharelatex/sharelatex-dev-environment +{ + "extends": [ + "eslint:recommended", + "standard", + "prettier" + ], + "parserOptions": { + "ecmaVersion": 2018 + }, + "plugins": [ + "mocha", + "chai-expect", + "chai-friendly" + ], + "env": { + "node": true, + "mocha": true + }, + "rules": { + // TODO(das7pad): remove overrides after fixing all the violations manually (https://github.com/overleaf/issues/issues/3882#issuecomment-878999671) + // START of temporary overrides + "array-callback-return": "off", + "no-dupe-else-if": "off", + "no-var": "off", + "no-empty": "off", + "node/handle-callback-err": "off", + "no-loss-of-precision": "off", + "node/no-callback-literal": "off", + "node/no-path-concat": "off", + "prefer-regex-literals": "off", + // END of temporary overrides + + // Swap the no-unused-expressions rule with a more chai-friendly one + "no-unused-expressions": 0, + "chai-friendly/no-unused-expressions": "error", + + // Do not allow importing of implicit dependencies. + "import/no-extraneous-dependencies": "error" + }, + "overrides": [ + { + // Test specific rules + "files": ["test/**/*.js"], + "globals": { + "expect": true + }, + "rules": { + // mocha-specific rules + "mocha/handle-done-callback": "error", + "mocha/no-exclusive-tests": "error", + "mocha/no-global-tests": "error", + "mocha/no-identical-title": "error", + "mocha/no-nested-tests": "error", + "mocha/no-pending-tests": "error", + "mocha/no-skipped-tests": "error", + "mocha/no-mocha-arrows": "error", + + // chai-specific rules + "chai-expect/missing-assertion": "error", + "chai-expect/terminating-properties": "error", + + // prefer-arrow-callback applies to all callbacks, not just ones in mocha tests. + // we don't enforce this at the top-level - just in tests to manage `this` scope + // based on mocha's context mechanism + "mocha/prefer-arrow-callback": "error" + } + }, + { + // Backend specific rules + "files": ["app/**/*.js", "app.js", "index.js"], + "rules": { + // don't allow console.log in backend code + "no-console": "error", + + // Do not allow importing of implicit dependencies. + "import/no-extraneous-dependencies": ["error", { + // Do not allow importing of devDependencies. + "devDependencies": false + }] + } + } + ] +} diff --git a/services/clsi/.github/ISSUE_TEMPLATE.md b/services/clsi/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..e0093aa90c --- /dev/null +++ b/services/clsi/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ + + +## Steps to Reproduce + + + +1. +2. +3. + +## Expected Behaviour + + +## Observed Behaviour + + + +## Context + + +## Technical Info + + +* URL: +* Browser Name and version: +* Operating System and version (desktop or mobile): +* Signed in as: +* Project and/or file: + +## Analysis + + +## Who Needs to Know? + + + +- +- diff --git a/services/clsi/.github/PULL_REQUEST_TEMPLATE.md b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..12bb2eeb3f --- /dev/null +++ b/services/clsi/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,48 @@ + + + + + +### Description + + + +#### Screenshots + + + +#### Related Issues / PRs + + + +### Review + + + +#### Potential Impact + + + +#### Manual Testing Performed + +- [ ] +- [ ] + +#### Accessibility + + + +### Deployment + + + +#### Deployment Checklist + +- [ ] Update documentation not included in the PR (if any) +- [ ] + +#### Metrics and Monitoring + + + +#### Who Needs to Know? diff --git a/services/clsi/.github/dependabot.yml b/services/clsi/.github/dependabot.yml new file mode 100644 index 0000000000..c856753655 --- /dev/null +++ b/services/clsi/.github/dependabot.yml @@ -0,0 +1,23 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + + pull-request-branch-name: + # Separate sections of the branch name with a hyphen + # Docker images use the branch name and do not support slashes in tags + # https://github.com/overleaf/google-ops/issues/822 + # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#pull-request-branch-nameseparator + separator: "-" + + # Block informal upgrades -- security upgrades use a separate queue. + # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#open-pull-requests-limit + open-pull-requests-limit: 0 + + # currently assign team-magma to all dependabot PRs - this may change in + # future if we reorganise teams + labels: + - "dependencies" + - "type:maintenance" diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore new file mode 100644 index 0000000000..dee3eca1f5 --- /dev/null +++ b/services/clsi/.gitignore @@ -0,0 +1,17 @@ +**.swp +node_modules +test/acceptance/fixtures/tmp +compiles +output +.DS_Store +*~ +cache +.vagrant +db.sqlite +db.sqlite-wal +db.sqlite-shm +config/* +npm-debug.log + +# managed by dev-environment$ bin/update_build_scripts +.npmrc diff --git a/services/clsi/.mocharc.json b/services/clsi/.mocharc.json new file mode 100644 index 0000000000..dc3280aa96 --- /dev/null +++ b/services/clsi/.mocharc.json @@ -0,0 +1,3 @@ +{ + "require": "test/setup.js" +} diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc new file mode 100644 index 0000000000..5a80a7e912 --- /dev/null +++ b/services/clsi/.nvmrc @@ -0,0 +1 @@ +12.22.3 diff --git a/services/clsi/.prettierrc b/services/clsi/.prettierrc new file mode 100644 index 0000000000..c92c3526e7 --- /dev/null +++ b/services/clsi/.prettierrc @@ -0,0 +1,11 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment +{ + "arrowParens": "avoid", + "semi": false, + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "useTabs": false +} diff --git a/services/clsi/.viminfo b/services/clsi/.viminfo new file mode 100644 index 0000000000..78c0129851 --- /dev/null +++ b/services/clsi/.viminfo @@ -0,0 +1,35 @@ +# This viminfo file was generated by Vim 7.4. +# You may edit it if you're careful! + +# Value of 'encoding' when this file was written +*encoding=latin1 + + +# hlsearch on (H) or off (h): +~h +# Command Line History (newest to oldest): +:x + +# Search String History (newest to oldest): + +# Expression History (newest to oldest): + +# Input Line History (newest to oldest): + +# Input Line History (newest to oldest): + +# Registers: + +# File marks: +'0 1 0 ~/hello + +# Jumplist (newest first): +-' 1 0 ~/hello + +# History of marks within files (newest to oldest): + +> ~/hello + " 1 0 + ^ 1 1 + . 1 0 + + 1 0 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile new file mode 100644 index 0000000000..8bd4b9c62f --- /dev/null +++ b/services/clsi/Dockerfile @@ -0,0 +1,28 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment + +FROM node:12.22.3 as base + +WORKDIR /app +COPY install_deps.sh /app +RUN chmod 0755 ./install_deps.sh && ./install_deps.sh +ENTRYPOINT ["/bin/sh", "entrypoint.sh"] +COPY entrypoint.sh /app + +FROM base as app + +#wildcard as some files may not be in all repos +COPY package*.json npm-shrink*.json /app/ + +RUN npm ci --quiet + +COPY . /app + +FROM base + +COPY --from=app /app /app +RUN mkdir -p cache compiles db output \ +&& chown node:node cache compiles db output + +CMD ["node", "--expose-gc", "app.js"] diff --git a/services/clsi/LICENSE b/services/clsi/LICENSE new file mode 100644 index 0000000000..dba13ed2dd --- /dev/null +++ b/services/clsi/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + + Copyright (C) + + 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 . + +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 +. diff --git a/services/clsi/Makefile b/services/clsi/Makefile new file mode 100644 index 0000000000..45a4bc0400 --- /dev/null +++ b/services/clsi/Makefile @@ -0,0 +1,90 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment + +BUILD_NUMBER ?= local +BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD) +PROJECT_NAME = clsi +BUILD_DIR_NAME = $(shell pwd | xargs basename | tr -cd '[a-zA-Z0-9_.\-]') + +DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml +DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ + BRANCH_NAME=$(BRANCH_NAME) \ + PROJECT_NAME=$(PROJECT_NAME) \ + MOCHA_GREP=${MOCHA_GREP} \ + docker-compose ${DOCKER_COMPOSE_FLAGS} + +DOCKER_COMPOSE_TEST_ACCEPTANCE = \ + COMPOSE_PROJECT_NAME=test_acceptance_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) + +DOCKER_COMPOSE_TEST_UNIT = \ + COMPOSE_PROJECT_NAME=test_unit_$(BUILD_DIR_NAME) $(DOCKER_COMPOSE) + +clean: + -docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + -docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + -$(DOCKER_COMPOSE_TEST_UNIT) down --rmi local + -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --rmi local + +format: + $(DOCKER_COMPOSE) run --rm test_unit npm run --silent format + +format_fix: + $(DOCKER_COMPOSE) run --rm test_unit npm run --silent format:fix + +lint: + $(DOCKER_COMPOSE) run --rm test_unit npm run --silent lint + +test: format lint test_unit test_acceptance + +test_unit: +ifneq (,$(wildcard test/unit)) + $(DOCKER_COMPOSE_TEST_UNIT) run --rm test_unit + $(MAKE) test_unit_clean +endif + +test_clean: test_unit_clean +test_unit_clean: +ifneq (,$(wildcard test/unit)) + $(DOCKER_COMPOSE_TEST_UNIT) down -v -t 0 +endif + +test_acceptance: test_acceptance_clean test_acceptance_pre_run test_acceptance_run + $(MAKE) test_acceptance_clean + +test_acceptance_debug: test_acceptance_clean test_acceptance_pre_run test_acceptance_run_debug + $(MAKE) test_acceptance_clean + +test_acceptance_run: +ifneq (,$(wildcard test/acceptance)) + $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run --rm test_acceptance +endif + +test_acceptance_run_debug: +ifneq (,$(wildcard test/acceptance)) + $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run -p 127.0.0.9:19999:19999 --rm test_acceptance npm run test:acceptance -- --inspect=0.0.0.0:19999 --inspect-brk +endif + +test_clean: test_acceptance_clean +test_acceptance_clean: + $(DOCKER_COMPOSE_TEST_ACCEPTANCE) down -v -t 0 + +test_acceptance_pre_run: +ifneq (,$(wildcard test/acceptance/js/scripts/pre-run)) + $(DOCKER_COMPOSE_TEST_ACCEPTANCE) run --rm test_acceptance test/acceptance/js/scripts/pre-run +endif + +build: + docker build --pull --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + --tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + . + +tar: + $(DOCKER_COMPOSE) up tar + +publish: + + docker push $(DOCKER_REPO)/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) + + +.PHONY: clean test test_unit test_acceptance test_clean build publish diff --git a/services/clsi/README.md b/services/clsi/README.md new file mode 100644 index 0000000000..302cb34932 --- /dev/null +++ b/services/clsi/README.md @@ -0,0 +1,184 @@ +overleaf/clsi +=============== + +A web api for compiling LaTeX documents in the cloud + +The Common LaTeX Service Interface (CLSI) provides a RESTful interface to traditional LaTeX tools (or, more generally, any command line tool for composing marked-up documents into a display format such as PDF or HTML). The CLSI listens on the following ports by default: + +* TCP/3013 - the RESTful interface +* TCP/3048 - reports load information +* TCP/3049 - HTTP interface to control the CLSI service + +These defaults can be modified in `config/settings.defaults.js`. + +The provided `Dockerfile` builds a Docker image which has the Docker command line tools installed. The configuration in `docker-compose-config.yml` mounts the Docker socket, in order that the CLSI container can talk to the Docker host it is running in. This allows it to spin up `sibling containers` running an image with a TeX distribution installed to perform the actual compiles. + +The CLSI can be configured through the following environment variables: + +* `ALLOWED_COMPILE_GROUPS` - Space separated list of allowed compile groups +* `ALLOWED_IMAGES` - Space separated list of allowed Docker TeX Live images +* `CATCH_ERRORS` - Set to `true` to log uncaught exceptions +* `COMPILE_GROUP_DOCKER_CONFIGS` - JSON string of Docker configs for compile groups +* `COMPILES_HOST_DIR` - Working directory for LaTeX compiles +* `COMPILE_SIZE_LIMIT` - Sets the body-parser [limit](https://github.com/expressjs/body-parser#limit) +* `DOCKER_RUNNER` - Set to true to use sibling containers +* `DOCKER_RUNTIME` - +* `FILESTORE_DOMAIN_OVERRIDE` - The url for the filestore service e.g.`http://$FILESTORE_HOST:3009` +* `FILESTORE_PARALLEL_FILE_DOWNLOADS` - Number of parallel file downloads +* `FILESTORE_PARALLEL_SQL_QUERY_LIMIT` - Number of parallel SQL queries +* `LISTEN_ADDRESS` - The address for the RESTful service to listen on. Set to `0.0.0.0` to listen on all network interfaces +* `PROCESS_LIFE_SPAN_LIMIT_MS` - Process life span limit in milliseconds +* `SENTRY_DSN` - Sentry [Data Source Name](https://docs.sentry.io/product/sentry-basics/dsn-explainer/) +* `SMOKE_TEST` - Whether to run smoke tests +* `SQLITE_PATH` - Path to SQLite database +* `SYNCTEX_BIN_HOST_PATH` - Path to SyncTeX binary +* `TEXLIVE_IMAGE` - The TeX Live Docker image to use for sibling containers, e.g. `gcr.io/overleaf-ops/texlive-full:2017.1` +* `TEX_LIVE_IMAGE_NAME_OVERRIDE` - The name of the registry for the Docker image e.g. `gcr.io/overleaf-ops` +* `TEXLIVE_IMAGE_USER` - When using sibling containers, the user to run as in the TeX Live image. Defaults to `tex` +* `TEXLIVE_OPENOUT_ANY` - Sets the `openout_any` environment variable for TeX Live (see the `\openout` primitive [documentation](http://tug.org/texinfohtml/web2c.html#tex-invocation)) + +Further environment variables configure the [metrics module](https://github.com/overleaf/metrics-module) + +Installation +------------ + +The CLSI can be installed and set up as part of the entire [Overleaf stack](https://github.com/overleaf/overleaf) (complete with front end editor and document storage), or it can be run as a standalone service. To run is as a standalone service, first checkout this repository: + + $ git clone git@github.com:overleaf/clsi.git + +Then build the Docker image: + + $ docker build . -t overleaf/clsi + +Then pull the TeX Live image: + + $ docker pull texlive/texlive + +Then start the Docker container: + + $ docker run --rm \ + -p 127.0.0.1:3013:3013 \ + -e LISTEN_ADDRESS=0.0.0.0 \ + -e DOCKER_RUNNER=true \ + -e TEXLIVE_IMAGE=texlive/texlive \ + -e TEXLIVE_IMAGE_USER=root \ + -e COMPILES_HOST_DIR="$PWD/compiles" \ + -v "$PWD/compiles:/app/compiles" \ + -v "$PWD/cache:/app/cache" \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --name clsi \ + overleaf/clsi + +Note: if you're running the CLSI in macOS you may need to use `-v /var/run/docker.sock.raw:/var/run/docker.sock` instead. + +The CLSI should then be running at + +Important note for Linux users +============================== + +The Node application runs as user `node` in the CLSI, which has uid `1000`. As a consequence of this, the `compiles` folder gets created on your host with `uid` and `gid` set to `1000`. +``` +ls -lnd compiles +drwxr-xr-x 2 1000 1000 4096 Mar 19 12:41 compiles +``` + +If there is a user/group on your host which also happens to have `uid` / `gid` `1000` then that user/group will have ownership of the compiles folder on your host. + +LaTeX runs in the sibling containers as the user specified in the `TEXLIVE_IMAGE_USER` environment variable. In the example above this is set to `root`, which has uid `0`. This creates a problem with the above permissions, as the root user does not have permission to write to subfolders of `compiles`. + +A quick fix is to give the `root` group ownership and read write permissions to `compiles`, with `setgid` set so that new subfolders also inherit this ownership: +``` +sudo chown -R 1000:root compiles +sudo chmod -R g+w compiles +sudo chmod g+s compiles +``` +Another solution is to create a `sharelatex` group and add both `root` and the user with `uid` `1000` to it. If the host does not have a user with that `uid`, you will need to create one first. +``` +sudo useradd --uid 1000 host-node-user # If required +sudo groupadd sharelatex +sudo usermod -a -G sharelatex root +sudo usermod -a -G sharelatex $(id -nu 1000) +sudo chown -R 1000:sharelatex compiles +sudo chmod -R g+w compiles +sudo chmod g+s compiles +``` + +This is a facet of the way docker works on Linux. See this [upstream issue](https://github.com/moby/moby/issues/7198) + + +Config +------ + +The CLSI will use a SQLite database by default, but you can optionally set up a MySQL database and then fill in the database name, username and password in the config file at `config/settings.development.js`. + +API +--- + +The CLSI is based on a JSON API. + +#### Example Request + +(Note that valid JSON should not contain any comments like the example below). + + POST /project//compile + +```json5 +{ + "compile": { + "options": { + // Which compiler to use. Can be latex, pdflatex, xelatex or lualatex + "compiler": "lualatex", + // How many seconds to wait before killing the process. Default is 60. + "timeout": 40 + }, + // The main file to run LaTeX on + "rootResourcePath": "main.tex", + // An array of files to include in the compilation. May have either the content + // passed directly, or a URL where it can be downloaded. + "resources": [ + { + "path": "main.tex", + "content": "\\documentclass{article}\n\\begin{document}\nHello World\n\\end{document}" + } + // ,{ + // "path": "image.png", + // "url": "www.example.com/image.png", + // "modified": 123456789 // Unix time since epoch + // } + ] + } +} +``` + +With `curl`, if you place the above JSON in a file called `data.json`, the request would look like this: + +``` shell +$ curl -X POST -H 'Content-Type: application/json' -d @data.json http://localhost:3013/project//compile +``` + +You can specify any project-id in the URL, and the files and LaTeX environment will be persisted between requests. +URLs will be downloaded and cached until provided with a more recent modified date. + +#### Example Response + +```json +{ + "compile": { + "status": "success", + "outputFiles": [{ + "type": "pdf", + "url": "http://localhost:3013/project//output/output.pdf" + }, { + "type": "log", + "url": "http://localhost:3013/project//output/output.log" + }] + } +} +``` + +License +------- + +The code in this repository is released under the GNU AFFERO GENERAL PUBLIC LICENSE, version 3. A copy can be found in the `LICENSE` file. + +Copyright (c) Overleaf, 2014-2021. diff --git a/services/clsi/app.js b/services/clsi/app.js new file mode 100644 index 0000000000..8916c0a04a --- /dev/null +++ b/services/clsi/app.js @@ -0,0 +1,425 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const tenMinutes = 10 * 60 * 1000 +const Metrics = require('@overleaf/metrics') +Metrics.initialize('clsi') + +const CompileController = require('./app/js/CompileController') +const ContentController = require('./app/js/ContentController') +const Settings = require('@overleaf/settings') +const logger = require('logger-sharelatex') +logger.initialize('clsi') +if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) { + logger.initializeErrorReporting(Settings.sentry.dsn) +} + +const smokeTest = require('./test/smoke/js/SmokeTests') +const ContentTypeMapper = require('./app/js/ContentTypeMapper') +const Errors = require('./app/js/Errors') + +const Path = require('path') + +Metrics.open_sockets.monitor(logger) +Metrics.memory.monitor(logger) + +const ProjectPersistenceManager = require('./app/js/ProjectPersistenceManager') +const OutputCacheManager = require('./app/js/OutputCacheManager') +const ContentCacheManager = require('./app/js/ContentCacheManager') + +require('./app/js/db').sync() + +const express = require('express') +const bodyParser = require('body-parser') +const app = express() + +Metrics.injectMetricsRoute(app) +app.use(Metrics.http.monitor(logger)) + +// Compile requests can take longer than the default two +// minutes (including file download time), so bump up the +// timeout a bit. +const TIMEOUT = 10 * 60 * 1000 +app.use(function (req, res, next) { + req.setTimeout(TIMEOUT) + res.setTimeout(TIMEOUT) + res.removeHeader('X-Powered-By') + return next() +}) + +app.param('project_id', function (req, res, next, projectId) { + if (projectId != null ? projectId.match(/^[a-zA-Z0-9_-]+$/) : undefined) { + return next() + } else { + return next(new Error('invalid project id')) + } +}) + +app.param('user_id', function (req, res, next, userId) { + if (userId != null ? userId.match(/^[0-9a-f]{24}$/) : undefined) { + return next() + } else { + return next(new Error('invalid user id')) + } +}) + +app.param('build_id', function (req, res, next, buildId) { + if ( + buildId != null ? buildId.match(OutputCacheManager.BUILD_REGEX) : undefined + ) { + return next() + } else { + return next(new Error(`invalid build id ${buildId}`)) + } +}) + +app.param('contentId', function (req, res, next, contentId) { + if ( + contentId != null + ? contentId.match(OutputCacheManager.CONTENT_REGEX) + : undefined + ) { + return next() + } else { + return next(new Error(`invalid content id ${contentId}`)) + } +}) + +app.param('hash', function (req, res, next, hash) { + if (hash != null ? hash.match(ContentCacheManager.HASH_REGEX) : undefined) { + return next() + } else { + return next(new Error(`invalid hash ${hash}`)) + } +}) + +app.post( + '/project/:project_id/compile', + bodyParser.json({ limit: Settings.compileSizeLimit }), + CompileController.compile +) +app.post('/project/:project_id/compile/stop', CompileController.stopCompile) +app.delete('/project/:project_id', CompileController.clearCache) + +app.get('/project/:project_id/sync/code', CompileController.syncFromCode) +app.get('/project/:project_id/sync/pdf', CompileController.syncFromPdf) +app.get('/project/:project_id/wordcount', CompileController.wordcount) +app.get('/project/:project_id/status', CompileController.status) +app.post('/project/:project_id/status', CompileController.status) + +// Per-user containers +app.post( + '/project/:project_id/user/:user_id/compile', + bodyParser.json({ limit: Settings.compileSizeLimit }), + CompileController.compile +) +app.post( + '/project/:project_id/user/:user_id/compile/stop', + CompileController.stopCompile +) +app.delete('/project/:project_id/user/:user_id', CompileController.clearCache) + +app.get( + '/project/:project_id/user/:user_id/sync/code', + CompileController.syncFromCode +) +app.get( + '/project/:project_id/user/:user_id/sync/pdf', + CompileController.syncFromPdf +) +app.get( + '/project/:project_id/user/:user_id/wordcount', + CompileController.wordcount +) + +const ForbidSymlinks = require('./app/js/StaticServerForbidSymlinks') + +// create a static server which does not allow access to any symlinks +// avoids possible mismatch of root directory between middleware check +// and serving the files +const staticCompileServer = ForbidSymlinks( + express.static, + Settings.path.compilesDir, + { + setHeaders(res, path, stat) { + if (Path.basename(path) === 'output.pdf') { + // Calculate an etag in the same way as nginx + // https://github.com/tj/send/issues/65 + const etag = (path, stat) => + `"${Math.ceil(+stat.mtime / 1000).toString(16)}` + + '-' + + Number(stat.size).toString(16) + + '"' + res.set('Etag', etag(path, stat)) + } + return res.set('Content-Type', ContentTypeMapper.map(path)) + }, + } +) + +const staticOutputServer = ForbidSymlinks( + express.static, + Settings.path.outputDir, + { + setHeaders(res, path, stat) { + if (Path.basename(path) === 'output.pdf') { + // Calculate an etag in the same way as nginx + // https://github.com/tj/send/issues/65 + const etag = (path, stat) => + `"${Math.ceil(+stat.mtime / 1000).toString(16)}` + + '-' + + Number(stat.size).toString(16) + + '"' + res.set('Etag', etag(path, stat)) + } + return res.set('Content-Type', ContentTypeMapper.map(path)) + }, + } +) + +app.get( + '/project/:project_id/user/:user_id/build/:build_id/output/*', + function (req, res, next) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = + `/${req.params.project_id}-${req.params.user_id}/` + + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) + return staticOutputServer(req, res, next) + } +) + +app.get( + '/project/:projectId/content/:contentId/:hash', + ContentController.getPdfRange +) +app.get( + '/project/:projectId/user/:userId/content/:contentId/:hash', + ContentController.getPdfRange +) + +app.get( + '/project/:project_id/build/:build_id/output/*', + function (req, res, next) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = + `/${req.params.project_id}/` + + OutputCacheManager.path(req.params.build_id, `/${req.params[0]}`) + return staticOutputServer(req, res, next) + } +) + +app.get( + '/project/:project_id/user/:user_id/output/*', + function (req, res, next) { + // for specific user get the path to the top level file + logger.warn( + { url: req.url }, + 'direct request for file in compile directory' + ) + req.url = `/${req.params.project_id}-${req.params.user_id}/${req.params[0]}` + return staticCompileServer(req, res, next) + } +) + +app.get('/project/:project_id/output/*', function (req, res, next) { + logger.warn({ url: req.url }, 'direct request for file in compile directory') + if ( + (req.query != null ? req.query.build : undefined) != null && + req.query.build.match(OutputCacheManager.BUILD_REGEX) + ) { + // for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId) + req.url = + `/${req.params.project_id}/` + + OutputCacheManager.path(req.query.build, `/${req.params[0]}`) + } else { + req.url = `/${req.params.project_id}/${req.params[0]}` + } + return staticCompileServer(req, res, next) +}) + +app.get('/oops', function (req, res, next) { + logger.error({ err: 'hello' }, 'test error') + return res.send('error\n') +}) + +app.get('/oops-internal', function (req, res, next) { + setTimeout(function () { + throw new Error('Test error') + }, 1) +}) + +app.get('/status', (req, res, next) => res.send('CLSI is alive\n')) + +Settings.processTooOld = false +if (Settings.processLifespanLimitMs) { + Settings.processLifespanLimitMs += + Settings.processLifespanLimitMs * (Math.random() / 10) + logger.info( + 'Lifespan limited to ', + Date.now() + Settings.processLifespanLimitMs + ) + + setTimeout(() => { + logger.log('shutting down, process is too old') + Settings.processTooOld = true + }, Settings.processLifespanLimitMs) +} + +function runSmokeTest() { + if (Settings.processTooOld) return + logger.log('running smoke tests') + smokeTest.triggerRun(err => { + if (err) logger.error({ err }, 'smoke tests failed') + setTimeout(runSmokeTest, 30 * 1000) + }) +} +if (Settings.smokeTest) { + runSmokeTest() +} + +app.get('/health_check', function (req, res) { + if (Settings.processTooOld) { + return res.status(500).json({ processTooOld: true }) + } + smokeTest.sendLastResult(res) +}) + +app.get('/smoke_test_force', (req, res) => smokeTest.sendNewResult(res)) + +app.use(function (error, req, res, next) { + if (error instanceof Errors.NotFoundError) { + logger.log({ err: error, url: req.url }, 'not found error') + return res.sendStatus(404) + } else if (error.code === 'EPIPE') { + // inspect container returns EPIPE when shutting down + return res.sendStatus(503) // send 503 Unavailable response + } else { + logger.error({ err: error, url: req.url }, 'server error') + return res.sendStatus((error != null ? error.statusCode : undefined) || 500) + } +}) + +const net = require('net') +const os = require('os') + +let STATE = 'up' + +const loadTcpServer = net.createServer(function (socket) { + socket.on('error', function (err) { + if (err.code === 'ECONNRESET') { + // this always comes up, we don't know why + return + } + logger.err({ err }, 'error with socket on load check') + return socket.destroy() + }) + + if (STATE === 'up' && Settings.internal.load_balancer_agent.report_load) { + let availableWorkingCpus + const currentLoad = os.loadavg()[0] + + // staging clis's have 1 cpu core only + if (os.cpus().length === 1) { + availableWorkingCpus = 1 + } else { + availableWorkingCpus = os.cpus().length - 1 + } + + const freeLoad = availableWorkingCpus - currentLoad + let freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) + if (freeLoadPercentage <= 0) { + freeLoadPercentage = 1 // when its 0 the server is set to drain and will move projects to different servers + } + socket.write(`up, ${freeLoadPercentage}%\n`, 'ASCII') + return socket.end() + } else { + socket.write(`${STATE}\n`, 'ASCII') + return socket.end() + } +}) + +const loadHttpServer = express() + +loadHttpServer.post('/state/up', function (req, res, next) { + STATE = 'up' + logger.info('getting message to set server to down') + return res.sendStatus(204) +}) + +loadHttpServer.post('/state/down', function (req, res, next) { + STATE = 'down' + logger.info('getting message to set server to down') + return res.sendStatus(204) +}) + +loadHttpServer.post('/state/maint', function (req, res, next) { + STATE = 'maint' + logger.info('getting message to set server to maint') + return res.sendStatus(204) +}) + +const port = + __guard__( + Settings.internal != null ? Settings.internal.clsi : undefined, + x => x.port + ) || 3013 +const host = + __guard__( + Settings.internal != null ? Settings.internal.clsi : undefined, + x1 => x1.host + ) || 'localhost' + +const loadTcpPort = Settings.internal.load_balancer_agent.load_port +const loadHttpPort = Settings.internal.load_balancer_agent.local_port + +if (!module.parent) { + // Called directly + + // handle uncaught exceptions when running in production + if (Settings.catchErrors) { + process.removeAllListeners('uncaughtException') + process.on('uncaughtException', error => + logger.error({ err: error }, 'uncaughtException') + ) + } + + app.listen(port, host, error => { + if (error) { + logger.fatal({ error }, `Error starting CLSI on ${host}:${port}`) + } else { + logger.info(`CLSI starting up, listening on ${host}:${port}`) + } + }) + + loadTcpServer.listen(loadTcpPort, host, function (error) { + if (error != null) { + throw error + } + return logger.info(`Load tcp agent listening on load port ${loadTcpPort}`) + }) + + loadHttpServer.listen(loadHttpPort, host, function (error) { + if (error != null) { + throw error + } + return logger.info(`Load http agent listening on load port ${loadHttpPort}`) + }) +} + +module.exports = app + +setInterval(() => { + ProjectPersistenceManager.refreshExpiryTimeout(() => { + ProjectPersistenceManager.clearExpiredProjects() + }) +}, tenMinutes) + +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/CommandRunner.js b/services/clsi/app/js/CommandRunner.js new file mode 100644 index 0000000000..782707b3a8 --- /dev/null +++ b/services/clsi/app/js/CommandRunner.js @@ -0,0 +1,20 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let commandRunnerPath +const Settings = require('@overleaf/settings') +const logger = require('logger-sharelatex') + +if ((Settings.clsi != null ? Settings.clsi.dockerRunner : undefined) === true) { + commandRunnerPath = './DockerRunner' +} else { + commandRunnerPath = './LocalCommandRunner' +} +logger.info({ commandRunnerPath }, 'selecting command runner for clsi') +const CommandRunner = require(commandRunnerPath) + +module.exports = CommandRunner diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js new file mode 100644 index 0000000000..488d818157 --- /dev/null +++ b/services/clsi/app/js/CompileController.js @@ -0,0 +1,269 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let CompileController +const RequestParser = require('./RequestParser') +const CompileManager = require('./CompileManager') +const Settings = require('@overleaf/settings') +const Metrics = require('./Metrics') +const ProjectPersistenceManager = require('./ProjectPersistenceManager') +const logger = require('logger-sharelatex') +const Errors = require('./Errors') + +function isImageNameAllowed(imageName) { + const ALLOWED_IMAGES = + Settings.clsi && Settings.clsi.docker && Settings.clsi.docker.allowedImages + return !ALLOWED_IMAGES || ALLOWED_IMAGES.includes(imageName) +} + +module.exports = CompileController = { + compile(req, res, next) { + if (next == null) { + next = function (error) {} + } + const timer = new Metrics.Timer('compile-request') + return RequestParser.parse(req.body, function (error, request) { + if (error != null) { + return next(error) + } + request.project_id = req.params.project_id + if (req.params.user_id != null) { + request.user_id = req.params.user_id + } + return ProjectPersistenceManager.markProjectAsJustAccessed( + request.project_id, + function (error) { + if (error != null) { + return next(error) + } + return CompileManager.doCompileWithLock( + request, + function (error, outputFiles, stats, timings) { + let code, status + if (outputFiles == null) { + outputFiles = [] + } + if (error instanceof Errors.AlreadyCompilingError) { + code = 423 // Http 423 Locked + status = 'compile-in-progress' + } else if (error instanceof Errors.FilesOutOfSyncError) { + code = 409 // Http 409 Conflict + status = 'retry' + } else if (error && error.code === 'EPIPE') { + // docker returns EPIPE when shutting down + code = 503 // send 503 Unavailable response + status = 'unavailable' + } else if (error != null ? error.terminated : undefined) { + status = 'terminated' + } else if (error != null ? error.validate : undefined) { + status = `validation-${error.validate}` + } else if (error != null ? error.timedout : undefined) { + status = 'timedout' + logger.log( + { err: error, project_id: request.project_id }, + 'timeout running compile' + ) + } else if (error != null) { + status = 'error' + code = 500 + logger.warn( + { err: error, project_id: request.project_id }, + 'error running compile' + ) + } else { + let file + status = 'failure' + for (file of Array.from(outputFiles)) { + if (file.path === 'output.pdf' && file.size > 0) { + status = 'success' + } + } + + if (status === 'failure') { + logger.warn( + { project_id: request.project_id, outputFiles }, + 'project failed to compile successfully, no output.pdf generated' + ) + } + + // log an error if any core files are found + for (file of Array.from(outputFiles)) { + if (file.path === 'core') { + logger.error( + { project_id: request.project_id, req, outputFiles }, + 'core file found in output' + ) + } + } + } + + if (error != null) { + outputFiles = error.outputFiles || [] + } + + timer.done() + return res.status(code || 200).send({ + compile: { + status, + error: (error != null ? error.message : undefined) || error, + stats, + timings, + outputFiles: outputFiles.map(file => { + return { + url: + `${Settings.apis.clsi.url}/project/${request.project_id}` + + (request.user_id != null + ? `/user/${request.user_id}` + : '') + + (file.build != null ? `/build/${file.build}` : '') + + `/output/${file.path}`, + ...file, + } + }), + }, + }) + } + ) + } + ) + }) + }, + + stopCompile(req, res, next) { + const { project_id, user_id } = req.params + return CompileManager.stopCompile(project_id, user_id, function (error) { + if (error != null) { + return next(error) + } + return res.sendStatus(204) + }) + }, + + clearCache(req, res, next) { + if (next == null) { + next = function (error) {} + } + return ProjectPersistenceManager.clearProject( + req.params.project_id, + req.params.user_id, + function (error) { + if (error != null) { + return next(error) + } + return res.sendStatus(204) + } + ) + }, // No content + + syncFromCode(req, res, next) { + if (next == null) { + next = function (error) {} + } + const { file } = req.query + const line = parseInt(req.query.line, 10) + const column = parseInt(req.query.column, 10) + const { imageName } = req.query + const { project_id } = req.params + const { user_id } = req.params + + if (imageName && !isImageNameAllowed(imageName)) { + return res.status(400).send('invalid image') + } + + return CompileManager.syncFromCode( + project_id, + user_id, + file, + line, + column, + imageName, + function (error, pdfPositions) { + if (error != null) { + return next(error) + } + return res.json({ + pdf: pdfPositions, + }) + } + ) + }, + + syncFromPdf(req, res, next) { + if (next == null) { + next = function (error) {} + } + const page = parseInt(req.query.page, 10) + const h = parseFloat(req.query.h) + const v = parseFloat(req.query.v) + const { imageName } = req.query + const { project_id } = req.params + const { user_id } = req.params + + if (imageName && !isImageNameAllowed(imageName)) { + return res.status(400).send('invalid image') + } + return CompileManager.syncFromPdf( + project_id, + user_id, + page, + h, + v, + imageName, + function (error, codePositions) { + if (error != null) { + return next(error) + } + return res.json({ + code: codePositions, + }) + } + ) + }, + + wordcount(req, res, next) { + if (next == null) { + next = function (error) {} + } + const file = req.query.file || 'main.tex' + const { project_id } = req.params + const { user_id } = req.params + const { image } = req.query + if (image && !isImageNameAllowed(image)) { + return res.status(400).send('invalid image') + } + logger.log({ image, file, project_id }, 'word count request') + + return CompileManager.wordcount( + project_id, + user_id, + file, + image, + function (error, result) { + if (error != null) { + return next(error) + } + return res.json({ + texcount: result, + }) + } + ) + }, + + status(req, res, next) { + if (next == null) { + next = function (error) {} + } + return res.send('OK') + }, +} diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js new file mode 100644 index 0000000000..07a9033b3d --- /dev/null +++ b/services/clsi/app/js/CompileManager.js @@ -0,0 +1,761 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, + no-undef, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let CompileManager +const ResourceWriter = require('./ResourceWriter') +const LatexRunner = require('./LatexRunner') +const OutputFileFinder = require('./OutputFileFinder') +const OutputCacheManager = require('./OutputCacheManager') +const Settings = require('@overleaf/settings') +const Path = require('path') +const logger = require('logger-sharelatex') +const Metrics = require('./Metrics') +const child_process = require('child_process') +const DraftModeManager = require('./DraftModeManager') +const TikzManager = require('./TikzManager') +const LockManager = require('./LockManager') +const fs = require('fs') +const fse = require('fs-extra') +const os = require('os') +const async = require('async') +const Errors = require('./Errors') +const CommandRunner = require('./CommandRunner') +const { emitPdfStats } = require('./ContentCacheMetrics') + +const getCompileName = function (project_id, user_id) { + if (user_id != null) { + return `${project_id}-${user_id}` + } else { + return project_id + } +} + +const getCompileDir = (project_id, user_id) => + Path.join(Settings.path.compilesDir, getCompileName(project_id, user_id)) + +const getOutputDir = (project_id, user_id) => + Path.join(Settings.path.outputDir, getCompileName(project_id, user_id)) + +module.exports = CompileManager = { + doCompileWithLock(request, callback) { + if (callback == null) { + callback = function (error, outputFiles) {} + } + const compileDir = getCompileDir(request.project_id, request.user_id) + const lockFile = Path.join(compileDir, '.project-lock') + // use a .project-lock file in the compile directory to prevent + // simultaneous compiles + return fse.ensureDir(compileDir, function (error) { + if (error != null) { + return callback(error) + } + return LockManager.runWithLock( + lockFile, + releaseLock => CompileManager.doCompile(request, releaseLock), + callback + ) + }) + }, + + doCompile(request, callback) { + if (callback == null) { + callback = function (error, outputFiles) {} + } + const compileDir = getCompileDir(request.project_id, request.user_id) + const outputDir = getOutputDir(request.project_id, request.user_id) + + const timerE2E = new Metrics.Timer('compile-e2e') + let timer = new Metrics.Timer('write-to-disk') + logger.log( + { project_id: request.project_id, user_id: request.user_id }, + 'syncing resources to disk' + ) + return ResourceWriter.syncResourcesToDisk( + request, + compileDir, + function (error, resourceList) { + // NOTE: resourceList is insecure, it should only be used to exclude files from the output list + if (error != null && error instanceof Errors.FilesOutOfSyncError) { + logger.warn( + { project_id: request.project_id, user_id: request.user_id }, + 'files out of sync, please retry' + ) + return callback(error) + } else if (error != null) { + logger.err( + { + err: error, + project_id: request.project_id, + user_id: request.user_id, + }, + 'error writing resources to disk' + ) + return callback(error) + } + logger.log( + { + project_id: request.project_id, + user_id: request.user_id, + time_taken: Date.now() - timer.start, + }, + 'written files to disk' + ) + const syncStage = timer.done() + + const injectDraftModeIfRequired = function (callback) { + if (request.draft) { + return DraftModeManager.injectDraftMode( + Path.join(compileDir, request.rootResourcePath), + callback + ) + } else { + return callback() + } + } + + const createTikzFileIfRequired = callback => + TikzManager.checkMainFile( + compileDir, + request.rootResourcePath, + resourceList, + function (error, needsMainFile) { + if (error != null) { + return callback(error) + } + if (needsMainFile) { + return TikzManager.injectOutputFile( + compileDir, + request.rootResourcePath, + callback + ) + } else { + return callback() + } + } + ) + // set up environment variables for chktex + const env = {} + if (Settings.texliveOpenoutAny && Settings.texliveOpenoutAny !== '') { + // override default texlive openout_any environment variable + env.openout_any = Settings.texliveOpenoutAny + } + // only run chktex on LaTeX files (not knitr .Rtex files or any others) + const isLaTeXFile = + request.rootResourcePath != null + ? request.rootResourcePath.match(/\.tex$/i) + : undefined + if (request.check != null && isLaTeXFile) { + env.CHKTEX_OPTIONS = '-nall -e9 -e10 -w15 -w16' + env.CHKTEX_ULIMIT_OPTIONS = '-t 5 -v 64000' + if (request.check === 'error') { + env.CHKTEX_EXIT_ON_ERROR = 1 + } + if (request.check === 'validate') { + env.CHKTEX_VALIDATE = 1 + } + } + + // apply a series of file modifications/creations for draft mode and tikz + return async.series( + [injectDraftModeIfRequired, createTikzFileIfRequired], + function (error) { + if (error != null) { + return callback(error) + } + timer = new Metrics.Timer('run-compile') + // find the image tag to log it as a metric, e.g. 2015.1 (convert . to - for graphite) + let tag = + __guard__( + __guard__( + request.imageName != null + ? request.imageName.match(/:(.*)/) + : undefined, + x1 => x1[1] + ), + x => x.replace(/\./g, '-') + ) || 'default' + if (!request.project_id.match(/^[0-9a-f]{24}$/)) { + tag = 'other' + } // exclude smoke test + Metrics.inc('compiles') + Metrics.inc(`compiles-with-image.${tag}`) + const compileName = getCompileName( + request.project_id, + request.user_id + ) + return LatexRunner.runLatex( + compileName, + { + directory: compileDir, + mainFile: request.rootResourcePath, + compiler: request.compiler, + timeout: request.timeout, + image: request.imageName, + flags: request.flags, + environment: env, + compileGroup: request.compileGroup, + }, + function (error, output, stats, timings) { + // request was for validation only + let metric_key, metric_value + if (request.check === 'validate') { + const result = (error != null ? error.code : undefined) + ? 'fail' + : 'pass' + error = new Error('validation') + error.validate = result + } + // request was for compile, and failed on validation + if ( + request.check === 'error' && + (error != null ? error.message : undefined) === 'exited' + ) { + error = new Error('compilation') + error.validate = 'fail' + } + // compile was killed by user, was a validation, or a compile which failed validation + if ( + (error != null ? error.terminated : undefined) || + (error != null ? error.validate : undefined) || + (error != null ? error.timedout : undefined) + ) { + OutputFileFinder.findOutputFiles( + resourceList, + compileDir, + function (err, outputFiles) { + if (err != null) { + return callback(err) + } + error.outputFiles = outputFiles // return output files so user can check logs + return callback(error) + } + ) + return + } + // compile completed normally + if (error != null) { + return callback(error) + } + Metrics.inc('compiles-succeeded') + stats = stats || {} + const object = stats || {} + for (metric_key in object) { + metric_value = object[metric_key] + Metrics.count(metric_key, metric_value) + } + timings = timings || {} + const object1 = timings || {} + for (metric_key in object1) { + metric_value = object1[metric_key] + Metrics.timing(metric_key, metric_value) + } + const loadavg = + typeof os.loadavg === 'function' ? os.loadavg() : undefined + if (loadavg != null) { + Metrics.gauge('load-avg', loadavg[0]) + } + const ts = timer.done() + logger.log( + { + project_id: request.project_id, + user_id: request.user_id, + time_taken: ts, + stats, + timings, + loadavg, + }, + 'done compile' + ) + if ((stats != null ? stats['latex-runs'] : undefined) > 0) { + Metrics.timing( + 'run-compile-per-pass', + ts / stats['latex-runs'] + ) + } + if ( + (stats != null ? stats['latex-runs'] : undefined) > 0 && + (timings != null ? timings['cpu-time'] : undefined) > 0 + ) { + Metrics.timing( + 'run-compile-cpu-time-per-pass', + timings['cpu-time'] / stats['latex-runs'] + ) + } + // Emit compile time. + timings.compile = ts + + timer = new Metrics.Timer('process-output-files') + + return OutputFileFinder.findOutputFiles( + resourceList, + compileDir, + function (error, outputFiles) { + if (error != null) { + return callback(error) + } + return OutputCacheManager.saveOutputFiles( + { request, stats, timings }, + outputFiles, + compileDir, + outputDir, + (err, newOutputFiles) => { + if (err) { + const { project_id: projectId, user_id: userId } = + request + logger.err( + { projectId, userId, err }, + 'failed to save output files' + ) + } + + const outputStage = timer.done() + timings.sync = syncStage + timings.output = outputStage + + // Emit e2e compile time. + timings.compileE2E = timerE2E.done() + + if (stats['pdf-size']) { + emitPdfStats(stats, timings) + } + + callback(null, newOutputFiles, stats, timings) + } + ) + } + ) + } + ) + } + ) + } + ) + }, + + stopCompile(project_id, user_id, callback) { + if (callback == null) { + callback = function (error) {} + } + const compileName = getCompileName(project_id, user_id) + return LatexRunner.killLatex(compileName, callback) + }, + + clearProject(project_id, user_id, _callback) { + if (_callback == null) { + _callback = function (error) {} + } + const callback = function (error) { + _callback(error) + return (_callback = function () {}) + } + + const compileDir = getCompileDir(project_id, user_id) + const outputDir = getOutputDir(project_id, user_id) + + return CompileManager._checkDirectory(compileDir, function (err, exists) { + if (err != null) { + return callback(err) + } + if (!exists) { + return callback() + } // skip removal if no directory present + + const proc = child_process.spawn('rm', [ + '-r', + '-f', + '--', + compileDir, + outputDir, + ]) + + proc.on('error', callback) + + let stderr = '' + proc.stderr.setEncoding('utf8').on('data', chunk => (stderr += chunk)) + + return proc.on('close', function (code) { + if (code === 0) { + return callback(null) + } else { + return callback( + new Error(`rm -r ${compileDir} ${outputDir} failed: ${stderr}`) + ) + } + }) + }) + }, + + _findAllDirs(callback) { + if (callback == null) { + callback = function (error, allDirs) {} + } + const root = Settings.path.compilesDir + return fs.readdir(root, function (err, files) { + if (err != null) { + return callback(err) + } + const allDirs = Array.from(files).map(file => Path.join(root, file)) + return callback(null, allDirs) + }) + }, + + clearExpiredProjects(max_cache_age_ms, callback) { + if (callback == null) { + callback = function (error) {} + } + const now = Date.now() + // action for each directory + const expireIfNeeded = (checkDir, cb) => + fs.stat(checkDir, function (err, stats) { + if (err != null) { + return cb() + } // ignore errors checking directory + const age = now - stats.mtime + const hasExpired = age > max_cache_age_ms + if (hasExpired) { + return fse.remove(checkDir, cb) + } else { + return cb() + } + }) + // iterate over all project directories + return CompileManager._findAllDirs(function (error, allDirs) { + if (error != null) { + return callback() + } + return async.eachSeries(allDirs, expireIfNeeded, callback) + }) + }, + + _checkDirectory(compileDir, callback) { + if (callback == null) { + callback = function (error, exists) {} + } + return fs.lstat(compileDir, function (err, stats) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + return callback(null, false) // directory does not exist + } else if (err != null) { + logger.err( + { dir: compileDir, err }, + 'error on stat of project directory for removal' + ) + return callback(err) + } else if (!(stats != null ? stats.isDirectory() : undefined)) { + logger.err( + { dir: compileDir, stats }, + 'bad project directory for removal' + ) + return callback(new Error('project directory is not directory')) + } else { + return callback(null, true) + } + }) + }, // directory exists + + syncFromCode( + project_id, + user_id, + file_name, + line, + column, + imageName, + callback + ) { + // If LaTeX was run in a virtual environment, the file path that synctex expects + // might not match the file path on the host. The .synctex.gz file however, will be accessed + // wherever it is on the host. + if (callback == null) { + callback = function (error, pdfPositions) {} + } + const compileName = getCompileName(project_id, user_id) + const base_dir = Settings.path.synctexBaseDir(compileName) + const file_path = base_dir + '/' + file_name + const compileDir = getCompileDir(project_id, user_id) + const synctex_path = `${base_dir}/output.pdf` + const command = ['code', synctex_path, file_path, line, column] + CompileManager._runSynctex( + project_id, + user_id, + command, + imageName, + function (error, stdout) { + if (error != null) { + return callback(error) + } + logger.log( + { project_id, user_id, file_name, line, column, command, stdout }, + 'synctex code output' + ) + return callback( + null, + CompileManager._parseSynctexFromCodeOutput(stdout) + ) + } + ) + }, + + syncFromPdf(project_id, user_id, page, h, v, imageName, callback) { + if (callback == null) { + callback = function (error, filePositions) {} + } + const compileName = getCompileName(project_id, user_id) + const compileDir = getCompileDir(project_id, user_id) + const base_dir = Settings.path.synctexBaseDir(compileName) + const synctex_path = `${base_dir}/output.pdf` + const command = ['pdf', synctex_path, page, h, v] + CompileManager._runSynctex( + project_id, + user_id, + command, + imageName, + function (error, stdout) { + if (error != null) { + return callback(error) + } + logger.log( + { project_id, user_id, page, h, v, stdout }, + 'synctex pdf output' + ) + return callback( + null, + CompileManager._parseSynctexFromPdfOutput(stdout, base_dir) + ) + } + ) + }, + + _checkFileExists(dir, filename, callback) { + if (callback == null) { + callback = function (error) {} + } + const file = Path.join(dir, filename) + return fs.stat(dir, function (error, stats) { + if ((error != null ? error.code : undefined) === 'ENOENT') { + return callback(new Errors.NotFoundError('no output directory')) + } + if (error != null) { + return callback(error) + } + return fs.stat(file, function (error, stats) { + if ((error != null ? error.code : undefined) === 'ENOENT') { + return callback(new Errors.NotFoundError('no output file')) + } + if (error != null) { + return callback(error) + } + if (!(stats != null ? stats.isFile() : undefined)) { + return callback(new Error('not a file')) + } + return callback() + }) + }) + }, + + _runSynctex(project_id, user_id, command, imageName, callback) { + if (callback == null) { + callback = function (error, stdout) {} + } + const seconds = 1000 + + command.unshift('/opt/synctex') + + const directory = getCompileDir(project_id, user_id) + const timeout = 60 * 1000 // increased to allow for large projects + const compileName = getCompileName(project_id, user_id) + const compileGroup = 'synctex' + CompileManager._checkFileExists(directory, 'output.synctex.gz', error => { + if (error) { + return callback(error) + } + return CommandRunner.run( + compileName, + command, + directory, + imageName || + (Settings.clsi && Settings.clsi.docker + ? Settings.clsi.docker.image + : undefined), + timeout, + {}, + compileGroup, + function (error, output) { + if (error != null) { + logger.err( + { err: error, command, project_id, user_id }, + 'error running synctex' + ) + return callback(error) + } + return callback(null, output.stdout) + } + ) + }) + }, + + _parseSynctexFromCodeOutput(output) { + const results = [] + for (const line of Array.from(output.split('\n'))) { + const [node, page, h, v, width, height] = Array.from(line.split('\t')) + if (node === 'NODE') { + results.push({ + page: parseInt(page, 10), + h: parseFloat(h), + v: parseFloat(v), + height: parseFloat(height), + width: parseFloat(width), + }) + } + } + return results + }, + + _parseSynctexFromPdfOutput(output, base_dir) { + const results = [] + for (let line of Array.from(output.split('\n'))) { + let column, file_path, node + ;[node, file_path, line, column] = Array.from(line.split('\t')) + if (node === 'NODE') { + const file = file_path.slice(base_dir.length + 1) + results.push({ + file, + line: parseInt(line, 10), + column: parseInt(column, 10), + }) + } + } + return results + }, + + wordcount(project_id, user_id, file_name, image, callback) { + if (callback == null) { + callback = function (error, pdfPositions) {} + } + logger.log({ project_id, user_id, file_name, image }, 'running wordcount') + const file_path = `$COMPILE_DIR/${file_name}` + const command = [ + 'texcount', + '-nocol', + '-inc', + file_path, + `-out=${file_path}.wc`, + ] + const compileDir = getCompileDir(project_id, user_id) + const timeout = 60 * 1000 + const compileName = getCompileName(project_id, user_id) + const compileGroup = 'wordcount' + return fse.ensureDir(compileDir, function (error) { + if (error != null) { + logger.err( + { error, project_id, user_id, file_name }, + 'error ensuring dir for sync from code' + ) + return callback(error) + } + return CommandRunner.run( + compileName, + command, + compileDir, + image, + timeout, + {}, + compileGroup, + function (error) { + if (error != null) { + return callback(error) + } + return fs.readFile( + compileDir + '/' + file_name + '.wc', + 'utf-8', + function (err, stdout) { + if (err != null) { + // call it node_err so sentry doesn't use random path error as unique id so it can't be ignored + logger.err( + { node_err: err, command, compileDir, project_id, user_id }, + 'error reading word count output' + ) + return callback(err) + } + const results = CompileManager._parseWordcountFromOutput(stdout) + logger.log( + { project_id, user_id, wordcount: results }, + 'word count results' + ) + return callback(null, results) + } + ) + } + ) + }) + }, + + _parseWordcountFromOutput(output) { + const results = { + encode: '', + textWords: 0, + headWords: 0, + outside: 0, + headers: 0, + elements: 0, + mathInline: 0, + mathDisplay: 0, + errors: 0, + messages: '', + } + for (const line of Array.from(output.split('\n'))) { + const [data, info] = Array.from(line.split(':')) + if (data.indexOf('Encoding') > -1) { + results.encode = info.trim() + } + if (data.indexOf('in text') > -1) { + results.textWords = parseInt(info, 10) + } + if (data.indexOf('in head') > -1) { + results.headWords = parseInt(info, 10) + } + if (data.indexOf('outside') > -1) { + results.outside = parseInt(info, 10) + } + if (data.indexOf('of head') > -1) { + results.headers = parseInt(info, 10) + } + if (data.indexOf('Number of floats/tables/figures') > -1) { + results.elements = parseInt(info, 10) + } + if (data.indexOf('Number of math inlines') > -1) { + results.mathInline = parseInt(info, 10) + } + if (data.indexOf('Number of math displayed') > -1) { + results.mathDisplay = parseInt(info, 10) + } + if (data === '(errors') { + // errors reported as (errors:123) + results.errors = parseInt(info, 10) + } + if (line.indexOf('!!! ') > -1) { + // errors logged as !!! message !!! + results.messages += line + '\n' + } + } + return results + }, +} + +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/ContentCacheManager.js b/services/clsi/app/js/ContentCacheManager.js new file mode 100644 index 0000000000..2036057064 --- /dev/null +++ b/services/clsi/app/js/ContentCacheManager.js @@ -0,0 +1,273 @@ +/** + * ContentCacheManager - maintains a cache of stream hashes from a PDF file + */ + +const { callbackify } = require('util') +const fs = require('fs') +const crypto = require('crypto') +const Path = require('path') +const Settings = require('@overleaf/settings') +const OError = require('@overleaf/o-error') +const pLimit = require('p-limit') +const { parseXrefTable } = require('../lib/pdfjs/parseXrefTable') +const { TimedOutError } = require('./Errors') + +/** + * + * @param {String} contentDir path to directory where content hash files are cached + * @param {String} filePath the pdf file to scan for streams + * @param {number} size the pdf size + * @param {number} compileTime + */ +async function update(contentDir, filePath, size, compileTime) { + const checkDeadline = getDeadlineChecker(compileTime) + const ranges = [] + const newRanges = [] + // keep track of hashes expire old ones when they reach a generation > N. + const tracker = await HashFileTracker.from(contentDir) + tracker.updateAge() + + checkDeadline('after init HashFileTracker') + + const rawTable = await parseXrefTable(filePath, size, checkDeadline) + rawTable.sort((a, b) => { + return a.offset - b.offset + }) + rawTable.forEach((obj, idx) => { + obj.idx = idx + }) + + checkDeadline('after parsing') + + const uncompressedObjects = [] + for (const object of rawTable) { + if (!object.uncompressed) { + continue + } + const nextObject = rawTable[object.idx + 1] + if (!nextObject) { + // Ignore this possible edge case. + // The last object should be part of the xRef table. + continue + } else { + object.endOffset = nextObject.offset + } + const size = object.endOffset - object.offset + object.size = size + if (size < Settings.pdfCachingMinChunkSize) { + continue + } + uncompressedObjects.push({ object, idx: uncompressedObjects.length }) + } + + checkDeadline('after finding uncompressed') + + const handle = await fs.promises.open(filePath) + try { + for (const { object, idx } of uncompressedObjects) { + let buffer = Buffer.alloc(object.size, 0) + const { bytesRead } = await handle.read( + buffer, + 0, + object.size, + object.offset + ) + checkDeadline('after read ' + idx) + if (bytesRead !== object.size) { + throw new OError('could not read full chunk', { + object, + bytesRead, + }) + } + const idxObj = buffer.indexOf('obj') + if (idxObj > 100) { + throw new OError('objectId is too large', { + object, + idxObj, + }) + } + const objectIdRaw = buffer.subarray(0, idxObj) + buffer = buffer.subarray(objectIdRaw.byteLength) + + const hash = pdfStreamHash(buffer) + checkDeadline('after hash ' + idx) + const range = { + objectId: objectIdRaw.toString(), + start: object.offset + objectIdRaw.byteLength, + end: object.endOffset, + hash, + } + ranges.push(range) + + // Optimization: Skip writing of duplicate streams. + if (tracker.track(range)) continue + + await writePdfStream(contentDir, hash, buffer) + checkDeadline('after write ' + idx) + newRanges.push(range) + } + } finally { + await handle.close() + } + + // NOTE: Bailing out below does not make sense. + // Let the next compile use the already written ranges. + const reclaimedSpace = await tracker.deleteStaleHashes(5) + await tracker.flush() + return [ranges, newRanges, reclaimedSpace] +} + +function getStatePath(contentDir) { + return Path.join(contentDir, '.state.v0.json') +} + +class HashFileTracker { + constructor(contentDir, { hashAge = [], hashSize = [] }) { + this.contentDir = contentDir + this.hashAge = new Map(hashAge) + this.hashSize = new Map(hashSize) + } + + static async from(contentDir) { + const statePath = getStatePath(contentDir) + let state = {} + try { + const blob = await fs.promises.readFile(statePath) + state = JSON.parse(blob) + } catch (e) {} + return new HashFileTracker(contentDir, state) + } + + track(range) { + const exists = this.hashAge.has(range.hash) + if (!exists) { + this.hashSize.set(range.hash, range.end - range.start) + } + this.hashAge.set(range.hash, 0) + return exists + } + + updateAge() { + for (const [hash, age] of this.hashAge) { + this.hashAge.set(hash, age + 1) + } + return this + } + + findStale(maxAge) { + const stale = [] + for (const [hash, age] of this.hashAge) { + if (age > maxAge) { + stale.push(hash) + } + } + return stale + } + + async flush() { + const statePath = getStatePath(this.contentDir) + const blob = JSON.stringify({ + hashAge: Array.from(this.hashAge.entries()), + hashSize: Array.from(this.hashSize.entries()), + }) + const atomicWrite = statePath + '~' + try { + await fs.promises.writeFile(atomicWrite, blob) + } catch (err) { + try { + await fs.promises.unlink(atomicWrite) + } catch (e) {} + throw err + } + try { + await fs.promises.rename(atomicWrite, statePath) + } catch (err) { + try { + await fs.promises.unlink(atomicWrite) + } catch (e) {} + throw err + } + } + + async deleteStaleHashes(n) { + // delete any hash file older than N generations + const hashes = this.findStale(n) + + let reclaimedSpace = 0 + if (hashes.length === 0) { + return reclaimedSpace + } + + await promiseMapWithLimit(10, hashes, async hash => { + await fs.promises.unlink(Path.join(this.contentDir, hash)) + this.hashAge.delete(hash) + reclaimedSpace += this.hashSize.get(hash) + this.hashSize.delete(hash) + }) + return reclaimedSpace + } +} + +function pdfStreamHash(buffer) { + const hash = crypto.createHash('sha256') + hash.update(buffer) + return hash.digest('hex') +} + +async function writePdfStream(dir, hash, buffer) { + const filename = Path.join(dir, hash) + const atomicWriteFilename = filename + '~' + if (Settings.enablePdfCachingDark) { + // Write an empty file in dark mode. + buffer = Buffer.alloc(0) + } + try { + await fs.promises.writeFile(atomicWriteFilename, buffer) + await fs.promises.rename(atomicWriteFilename, filename) + } catch (err) { + try { + await fs.promises.unlink(atomicWriteFilename) + } catch (_) { + throw err + } + } +} + +function getDeadlineChecker(compileTime) { + const maxOverhead = Math.min( + // Adding 10s to a 40s compile time is OK. + // Adding 1s to a 3s compile time is OK. + Math.max(compileTime / 4, 1000), + // Adding 30s to a 120s compile time is not OK, limit to 10s. + Settings.pdfCachingMaxProcessingTime + ) + + const deadline = Date.now() + maxOverhead + let lastStage = { stage: 'start', now: Date.now() } + let completedStages = 0 + return function (stage) { + const now = Date.now() + if (now > deadline) { + throw new TimedOutError(stage, { + completedStages, + lastStage: lastStage.stage, + diffToLastStage: now - lastStage.now, + }) + } + completedStages++ + lastStage = { stage, now } + } +} + +function promiseMapWithLimit(concurrency, array, fn) { + const limit = pLimit(concurrency) + return Promise.all(array.map(x => limit(() => fn(x)))) +} + +module.exports = { + HASH_REGEX: /^[0-9a-f]{64}$/, + update: callbackify(update), + promises: { + update, + }, +} diff --git a/services/clsi/app/js/ContentCacheMetrics.js b/services/clsi/app/js/ContentCacheMetrics.js new file mode 100644 index 0000000000..3550de70e8 --- /dev/null +++ b/services/clsi/app/js/ContentCacheMetrics.js @@ -0,0 +1,115 @@ +const logger = require('logger-sharelatex') +const Metrics = require('./Metrics') +const os = require('os') + +let CACHED_LOAD = { + expires: -1, + load: [0, 0, 0], +} +function getSystemLoad() { + if (CACHED_LOAD.expires < Date.now()) { + CACHED_LOAD = { + expires: Date.now() + 10 * 1000, + load: os.loadavg(), + } + } + return CACHED_LOAD.load +} + +const ONE_MB = 1024 * 1024 + +function emitPdfStats(stats, timings) { + if (stats['pdf-caching-timed-out']) { + Metrics.inc('pdf-caching-timed-out') + } + if (timings['compute-pdf-caching']) { + emitPdfCachingStats(stats, timings) + } else { + // How much bandwidth will the pdf incur when downloaded in full? + Metrics.summary('pdf-bandwidth', stats['pdf-size']) + } +} + +function emitPdfCachingStats(stats, timings) { + if (!stats['pdf-size']) return // double check + + // How much extra time did we spent in PDF.js? + Metrics.timing('compute-pdf-caching', timings['compute-pdf-caching']) + + // How large is the overhead of hashing up-front? + const fraction = + timings.compileE2E - timings['compute-pdf-caching'] !== 0 + ? timings.compileE2E / + (timings.compileE2E - timings['compute-pdf-caching']) + : 1 + if (fraction > 1.5 && timings.compileE2E > 10 * 1000) { + logger.warn( + { + stats, + timings, + load: getSystemLoad(), + }, + 'slow pdf caching' + ) + } + Metrics.summary('overhead-compute-pdf-ranges', fraction * 100 - 100) + + // How does the hashing scale to pdf size in MB? + Metrics.timing( + 'compute-pdf-caching-relative-to-pdf-size', + timings['compute-pdf-caching'] / (stats['pdf-size'] / ONE_MB) + ) + if (stats['pdf-caching-total-ranges-size']) { + // How does the hashing scale to total ranges size in MB? + Metrics.timing( + 'compute-pdf-caching-relative-to-total-ranges-size', + timings['compute-pdf-caching'] / + (stats['pdf-caching-total-ranges-size'] / ONE_MB) + ) + // How fast is the hashing per range on average? + Metrics.timing( + 'compute-pdf-caching-relative-to-ranges-count', + timings['compute-pdf-caching'] / stats['pdf-caching-n-ranges'] + ) + + // How many ranges are new? + Metrics.summary( + 'new-pdf-ranges-relative-to-total-ranges', + (stats['pdf-caching-n-new-ranges'] / stats['pdf-caching-n-ranges']) * 100 + ) + } + + // How much content is cacheable? + Metrics.summary( + 'cacheable-ranges-to-pdf-size', + (stats['pdf-caching-total-ranges-size'] / stats['pdf-size']) * 100 + ) + + const sizeWhenDownloadedInFull = + // All of the pdf + stats['pdf-size'] - + // These ranges are potentially cached. + stats['pdf-caching-total-ranges-size'] + + // These ranges are not cached. + stats['pdf-caching-new-ranges-size'] + + // How much bandwidth can we save when downloading the pdf in full? + Metrics.summary( + 'pdf-bandwidth-savings', + 100 - (sizeWhenDownloadedInFull / stats['pdf-size']) * 100 + ) + + // How much bandwidth will the pdf incur when downloaded in full? + Metrics.summary('pdf-bandwidth', sizeWhenDownloadedInFull) + + // How much space do the ranges use? + // This will accumulate the ranges size over time, skipping already written ranges. + Metrics.summary( + 'pdf-ranges-disk-size', + stats['pdf-caching-new-ranges-size'] - stats['pdf-caching-reclaimed-space'] + ) +} + +module.exports = { + emitPdfStats, +} diff --git a/services/clsi/app/js/ContentController.js b/services/clsi/app/js/ContentController.js new file mode 100644 index 0000000000..b154bea175 --- /dev/null +++ b/services/clsi/app/js/ContentController.js @@ -0,0 +1,24 @@ +const Path = require('path') +const send = require('send') +const Settings = require('@overleaf/settings') +const OutputCacheManager = require('./OutputCacheManager') + +const ONE_DAY_S = 24 * 60 * 60 +const ONE_DAY_MS = ONE_DAY_S * 1000 + +function getPdfRange(req, res, next) { + const { projectId, userId, contentId, hash } = req.params + const perUserDir = userId ? `${projectId}-${userId}` : projectId + const path = Path.join( + Settings.path.outputDir, + perUserDir, + OutputCacheManager.CONTENT_SUBDIR, + contentId, + hash + ) + res.setHeader('cache-control', `public, max-age=${ONE_DAY_S}`) + res.setHeader('expires', new Date(Date.now() + ONE_DAY_MS).toUTCString()) + send(req, path).pipe(res) +} + +module.exports = { getPdfRange } diff --git a/services/clsi/app/js/ContentTypeMapper.js b/services/clsi/app/js/ContentTypeMapper.js new file mode 100644 index 0000000000..6301dce489 --- /dev/null +++ b/services/clsi/app/js/ContentTypeMapper.js @@ -0,0 +1,38 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +let ContentTypeMapper +const Path = require('path') + +// here we coerce html, css and js to text/plain, +// otherwise choose correct mime type based on file extension, +// falling back to octet-stream +module.exports = ContentTypeMapper = { + map(path) { + switch (Path.extname(path)) { + case '.txt': + case '.html': + case '.js': + case '.css': + case '.svg': + return 'text/plain' + case '.csv': + return 'text/csv' + case '.pdf': + return 'application/pdf' + case '.png': + return 'image/png' + case '.jpg': + case '.jpeg': + return 'image/jpeg' + case '.tiff': + return 'image/tiff' + case '.gif': + return 'image/gif' + default: + return 'application/octet-stream' + } + }, +} diff --git a/services/clsi/app/js/DbQueue.js b/services/clsi/app/js/DbQueue.js new file mode 100644 index 0000000000..ca2155d230 --- /dev/null +++ b/services/clsi/app/js/DbQueue.js @@ -0,0 +1,18 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const async = require('async') +const Settings = require('@overleaf/settings') +const logger = require('logger-sharelatex') +const queue = async.queue( + (task, cb) => task(cb), + Settings.parallelSqlQueryLimit +) + +queue.drain = () => logger.debug('all items have been processed') + +module.exports = { queue } diff --git a/services/clsi/app/js/DockerLockManager.js b/services/clsi/app/js/DockerLockManager.js new file mode 100644 index 0000000000..d785ee46cb --- /dev/null +++ b/services/clsi/app/js/DockerLockManager.js @@ -0,0 +1,113 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let LockManager +const logger = require('logger-sharelatex') + +const LockState = {} // locks for docker container operations, by container name + +module.exports = LockManager = { + MAX_LOCK_HOLD_TIME: 15000, // how long we can keep a lock + MAX_LOCK_WAIT_TIME: 10000, // how long we wait for a lock + LOCK_TEST_INTERVAL: 1000, // retry time + + tryLock(key, callback) { + let lockValue + if (callback == null) { + callback = function (err, gotLock) {} + } + const existingLock = LockState[key] + if (existingLock != null) { + // the lock is already taken, check how old it is + const lockAge = Date.now() - existingLock.created + if (lockAge < LockManager.MAX_LOCK_HOLD_TIME) { + return callback(null, false) // we didn't get the lock, bail out + } else { + logger.error( + { key, lock: existingLock, age: lockAge }, + 'taking old lock by force' + ) + } + } + // take the lock + LockState[key] = lockValue = { created: Date.now() } + return callback(null, true, lockValue) + }, + + getLock(key, callback) { + let attempt + if (callback == null) { + callback = function (error, lockValue) {} + } + const startTime = Date.now() + return (attempt = () => + LockManager.tryLock(key, function (error, gotLock, lockValue) { + if (error != null) { + return callback(error) + } + if (gotLock) { + return callback(null, lockValue) + } else if (Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME) { + const e = new Error('Lock timeout') + e.key = key + return callback(e) + } else { + return setTimeout(attempt, LockManager.LOCK_TEST_INTERVAL) + } + }))() + }, + + releaseLock(key, lockValue, callback) { + if (callback == null) { + callback = function (error) {} + } + const existingLock = LockState[key] + if (existingLock === lockValue) { + // lockValue is an object, so we can test by reference + delete LockState[key] // our lock, so we can free it + return callback() + } else if (existingLock != null) { + // lock exists but doesn't match ours + logger.error( + { key, lock: existingLock }, + 'tried to release lock taken by force' + ) + return callback() + } else { + logger.error( + { key, lock: existingLock }, + 'tried to release lock that has gone' + ) + return callback() + } + }, + + runWithLock(key, runner, callback) { + if (callback == null) { + callback = function (error) {} + } + return LockManager.getLock(key, function (error, lockValue) { + if (error != null) { + return callback(error) + } + return runner((error1, ...args) => + LockManager.releaseLock(key, lockValue, function (error2) { + error = error1 || error2 + if (error != null) { + return callback(error) + } + return callback(null, ...Array.from(args)) + }) + ) + }) + }, +} diff --git a/services/clsi/app/js/DockerRunner.js b/services/clsi/app/js/DockerRunner.js new file mode 100644 index 0000000000..28d7636f0a --- /dev/null +++ b/services/clsi/app/js/DockerRunner.js @@ -0,0 +1,625 @@ +const Settings = require('@overleaf/settings') +const logger = require('logger-sharelatex') +const Docker = require('dockerode') +const dockerode = new Docker() +const crypto = require('crypto') +const async = require('async') +const LockManager = require('./DockerLockManager') +const fs = require('fs') +const Path = require('path') +const _ = require('lodash') + +const ONE_HOUR_IN_MS = 60 * 60 * 1000 +logger.info('using docker runner') + +function usingSiblingContainers() { + return ( + Settings != null && + Settings.path != null && + Settings.path.sandboxedCompilesHostDir != null + ) +} + +let containerMonitorTimeout +let containerMonitorInterval + +const DockerRunner = { + run( + projectId, + command, + directory, + image, + timeout, + environment, + compileGroup, + callback + ) { + if (usingSiblingContainers()) { + const _newPath = Settings.path.sandboxedCompilesHostDir + logger.log( + { path: _newPath }, + 'altering bind path for sibling containers' + ) + // Server Pro, example: + // '/var/lib/sharelatex/data/compiles/' + // ... becomes ... + // '/opt/sharelatex_data/data/compiles/' + directory = Path.join( + Settings.path.sandboxedCompilesHostDir, + Path.basename(directory) + ) + } + + const volumes = { [directory]: '/compile' } + + command = command.map(arg => + arg.toString().replace('$COMPILE_DIR', '/compile') + ) + if (image == null) { + image = Settings.clsi.docker.image + } + + if ( + Settings.clsi.docker.allowedImages && + !Settings.clsi.docker.allowedImages.includes(image) + ) { + return callback(new Error('image not allowed')) + } + + if (Settings.texliveImageNameOveride != null) { + const img = image.split('/') + image = `${Settings.texliveImageNameOveride}/${img[2]}` + } + + const options = DockerRunner._getContainerOptions( + command, + image, + volumes, + timeout, + environment, + compileGroup + ) + const fingerprint = DockerRunner._fingerprintContainer(options) + const name = `project-${projectId}-${fingerprint}` + options.name = name + + // logOptions = _.clone(options) + // logOptions?.HostConfig?.SecurityOpt = "secomp used, removed in logging" + logger.log({ projectId }, 'running docker container') + DockerRunner._runAndWaitForContainer( + options, + volumes, + timeout, + (error, output) => { + if (error && error.statusCode === 500) { + logger.log( + { err: error, projectId }, + 'error running container so destroying and retrying' + ) + DockerRunner.destroyContainer(name, null, true, error => { + if (error != null) { + return callback(error) + } + DockerRunner._runAndWaitForContainer( + options, + volumes, + timeout, + callback + ) + }) + } else { + callback(error, output) + } + } + ) + + // pass back the container name to allow it to be killed + return name + }, + + kill(containerId, callback) { + logger.log({ containerId }, 'sending kill signal to container') + const container = dockerode.getContainer(containerId) + container.kill(error => { + if ( + error != null && + error.message != null && + error.message.match(/Cannot kill container .* is not running/) + ) { + logger.warn( + { err: error, containerId }, + 'container not running, continuing' + ) + error = null + } + if (error != null) { + logger.error({ err: error, containerId }, 'error killing container') + callback(error) + } else { + callback() + } + }) + }, + + _runAndWaitForContainer(options, volumes, timeout, _callback) { + const callback = _.once(_callback) + const { name } = options + + let streamEnded = false + let containerReturned = false + let output = {} + + function callbackIfFinished() { + if (streamEnded && containerReturned) { + callback(null, output) + } + } + + function attachStreamHandler(error, _output) { + if (error != null) { + return callback(error) + } + output = _output + streamEnded = true + callbackIfFinished() + } + + DockerRunner.startContainer( + options, + volumes, + attachStreamHandler, + (error, containerId) => { + if (error != null) { + return callback(error) + } + + DockerRunner.waitForContainer(name, timeout, (error, exitCode) => { + if (error != null) { + return callback(error) + } + if (exitCode === 137) { + // exit status from kill -9 + const err = new Error('terminated') + err.terminated = true + return callback(err) + } + if (exitCode === 1) { + // exit status from chktex + const err = new Error('exited') + err.code = exitCode + return callback(err) + } + containerReturned = true + if (options != null && options.HostConfig != null) { + options.HostConfig.SecurityOpt = null + } + logger.log({ exitCode, options }, 'docker container has exited') + callbackIfFinished() + }) + } + ) + }, + + _getContainerOptions( + command, + image, + volumes, + timeout, + environment, + compileGroup + ) { + const timeoutInSeconds = timeout / 1000 + + const dockerVolumes = {} + for (const hostVol in volumes) { + const dockerVol = volumes[hostVol] + dockerVolumes[dockerVol] = {} + + if (volumes[hostVol].slice(-3).indexOf(':r') === -1) { + volumes[hostVol] = `${dockerVol}:rw` + } + } + + // merge settings and environment parameter + const env = {} + for (const src of [Settings.clsi.docker.env, environment || {}]) { + for (const key in src) { + const value = src[key] + env[key] = value + } + } + // set the path based on the image year + const match = image.match(/:([0-9]+)\.[0-9]+/) + const year = match ? match[1] : '2014' + env.PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/${year}/bin/x86_64-linux/` + const options = { + Cmd: command, + Image: image, + Volumes: dockerVolumes, + WorkingDir: '/compile', + NetworkDisabled: true, + Memory: 1024 * 1024 * 1024 * 1024, // 1 Gb + User: Settings.clsi.docker.user, + Env: Object.entries(env).map(([key, value]) => `${key}=${value}`), + HostConfig: { + Binds: Object.entries(volumes).map( + ([hostVol, dockerVol]) => `${hostVol}:${dockerVol}` + ), + LogConfig: { Type: 'none', Config: {} }, + Ulimits: [ + { + Name: 'cpu', + Soft: timeoutInSeconds + 5, + Hard: timeoutInSeconds + 10, + }, + ], + CapDrop: 'ALL', + SecurityOpt: ['no-new-privileges'], + }, + } + + if (Settings.path != null && Settings.path.synctexBinHostPath != null) { + options.HostConfig.Binds.push( + `${Settings.path.synctexBinHostPath}:/opt/synctex:ro` + ) + } + + if (Settings.clsi.docker.seccomp_profile != null) { + options.HostConfig.SecurityOpt.push( + `seccomp=${Settings.clsi.docker.seccomp_profile}` + ) + } + + if (Settings.clsi.docker.apparmor_profile != null) { + options.HostConfig.SecurityOpt.push( + `apparmor=${Settings.clsi.docker.apparmor_profile}` + ) + } + + if (Settings.clsi.docker.runtime) { + options.HostConfig.Runtime = Settings.clsi.docker.runtime + } + + if (Settings.clsi.docker.Readonly) { + options.HostConfig.ReadonlyRootfs = true + options.HostConfig.Tmpfs = { '/tmp': 'rw,noexec,nosuid,size=65536k' } + options.Volumes['/home/tex'] = {} + } + + // Allow per-compile group overriding of individual settings + if ( + Settings.clsi.docker.compileGroupConfig && + Settings.clsi.docker.compileGroupConfig[compileGroup] + ) { + const override = Settings.clsi.docker.compileGroupConfig[compileGroup] + for (const key in override) { + _.set(options, key, override[key]) + } + } + + return options + }, + + _fingerprintContainer(containerOptions) { + // Yay, Hashing! + const json = JSON.stringify(containerOptions) + return crypto.createHash('md5').update(json).digest('hex') + }, + + startContainer(options, volumes, attachStreamHandler, callback) { + LockManager.runWithLock( + options.name, + releaseLock => + // Check that volumes exist before starting the container. + // When a container is started with volume pointing to a + // non-existent directory then docker creates the directory but + // with root ownership. + DockerRunner._checkVolumes(options, volumes, err => { + if (err != null) { + return releaseLock(err) + } + DockerRunner._startContainer( + options, + volumes, + attachStreamHandler, + releaseLock + ) + }), + + callback + ) + }, + + // Check that volumes exist and are directories + _checkVolumes(options, volumes, callback) { + if (usingSiblingContainers()) { + // Server Pro, with sibling-containers active, skip checks + return callback(null) + } + + const checkVolume = (path, cb) => + fs.stat(path, (err, stats) => { + if (err != null) { + return cb(err) + } + if (!stats.isDirectory()) { + return cb(new Error('not a directory')) + } + cb() + }) + const jobs = [] + for (const vol in volumes) { + jobs.push(cb => checkVolume(vol, cb)) + } + async.series(jobs, callback) + }, + + _startContainer(options, volumes, attachStreamHandler, callback) { + callback = _.once(callback) + const { name } = options + + logger.log({ container_name: name }, 'starting container') + const container = dockerode.getContainer(name) + + function createAndStartContainer() { + dockerode.createContainer(options, (error, container) => { + if (error != null) { + return callback(error) + } + startExistingContainer() + }) + } + + function startExistingContainer() { + DockerRunner.attachToContainer( + options.name, + attachStreamHandler, + error => { + if (error != null) { + return callback(error) + } + container.start(error => { + if (error != null && error.statusCode !== 304) { + callback(error) + } else { + // already running + callback() + } + }) + } + ) + } + + container.inspect((error, stats) => { + if (error != null && error.statusCode === 404) { + createAndStartContainer() + } else if (error != null) { + logger.err( + { container_name: name, error }, + 'unable to inspect container to start' + ) + callback(error) + } else { + startExistingContainer() + } + }) + }, + + attachToContainer(containerId, attachStreamHandler, attachStartCallback) { + const container = dockerode.getContainer(containerId) + container.attach({ stdout: 1, stderr: 1, stream: 1 }, (error, stream) => { + if (error != null) { + logger.error( + { err: error, containerId }, + 'error attaching to container' + ) + return attachStartCallback(error) + } else { + attachStartCallback() + } + + logger.log({ containerId }, 'attached to container') + + const MAX_OUTPUT = 1024 * 1024 // limit output to 1MB + function createStringOutputStream(name) { + return { + data: '', + overflowed: false, + write(data) { + if (this.overflowed) { + return + } + if (this.data.length < MAX_OUTPUT) { + this.data += data + } else { + logger.error( + { + containerId, + length: this.data.length, + maxLen: MAX_OUTPUT, + }, + `${name} exceeds max size` + ) + this.data += `(...truncated at ${MAX_OUTPUT} chars...)` + this.overflowed = true + } + }, + // kill container if too much output + // docker.containers.kill(containerId, () ->) + } + } + + const stdout = createStringOutputStream('stdout') + const stderr = createStringOutputStream('stderr') + + container.modem.demuxStream(stream, stdout, stderr) + + stream.on('error', err => + logger.error( + { err, containerId }, + 'error reading from container stream' + ) + ) + + stream.on('end', () => + attachStreamHandler(null, { stdout: stdout.data, stderr: stderr.data }) + ) + }) + }, + + waitForContainer(containerId, timeout, _callback) { + const callback = _.once(_callback) + + const container = dockerode.getContainer(containerId) + + let timedOut = false + const timeoutId = setTimeout(() => { + timedOut = true + logger.log({ containerId }, 'timeout reached, killing container') + container.kill(err => { + logger.warn({ err, containerId }, 'failed to kill container') + }) + }, timeout) + + logger.log({ containerId }, 'waiting for docker container') + container.wait((error, res) => { + if (error != null) { + clearTimeout(timeoutId) + logger.error({ err: error, containerId }, 'error waiting for container') + return callback(error) + } + if (timedOut) { + logger.log({ containerId }, 'docker container timed out') + error = new Error('container timed out') + error.timedout = true + callback(error) + } else { + clearTimeout(timeoutId) + logger.log( + { containerId, exitCode: res.StatusCode }, + 'docker container returned' + ) + callback(null, res.StatusCode) + } + }) + }, + + destroyContainer(containerName, containerId, shouldForce, callback) { + // We want the containerName for the lock and, ideally, the + // containerId to delete. There is a bug in the docker.io module + // where if you delete by name and there is an error, it throws an + // async exception, but if you delete by id it just does a normal + // error callback. We fall back to deleting by name if no id is + // supplied. + LockManager.runWithLock( + containerName, + releaseLock => + DockerRunner._destroyContainer( + containerId || containerName, + shouldForce, + releaseLock + ), + callback + ) + }, + + _destroyContainer(containerId, shouldForce, callback) { + logger.log({ containerId }, 'destroying docker container') + const container = dockerode.getContainer(containerId) + container.remove({ force: shouldForce === true, v: true }, error => { + if (error != null && error.statusCode === 404) { + logger.warn( + { err: error, containerId }, + 'container not found, continuing' + ) + error = null + } + if (error != null) { + logger.error({ err: error, containerId }, 'error destroying container') + } else { + logger.log({ containerId }, 'destroyed container') + } + callback(error) + }) + }, + + // handle expiry of docker containers + + MAX_CONTAINER_AGE: Settings.clsi.docker.maxContainerAge || ONE_HOUR_IN_MS, + + examineOldContainer(container, callback) { + const name = container.Name || (container.Names && container.Names[0]) + const created = container.Created * 1000 // creation time is returned in seconds + const now = Date.now() + const age = now - created + const maxAge = DockerRunner.MAX_CONTAINER_AGE + const ttl = maxAge - age + logger.log( + { containerName: name, created, now, age, maxAge, ttl }, + 'checking whether to destroy container' + ) + return { name, id: container.Id, ttl } + }, + + destroyOldContainers(callback) { + dockerode.listContainers({ all: true }, (error, containers) => { + if (error != null) { + return callback(error) + } + const jobs = [] + for (const container of containers) { + const { name, id, ttl } = DockerRunner.examineOldContainer(container) + if (name.slice(0, 9) === '/project-' && ttl <= 0) { + // strip the / prefix + // the LockManager uses the plain container name + const plainName = name.slice(1) + jobs.push(cb => + DockerRunner.destroyContainer(plainName, id, false, () => cb()) + ) + } + } + // Ignore errors because some containers get stuck but + // will be destroyed next time + async.series(jobs, callback) + }) + }, + + startContainerMonitor() { + logger.log( + { maxAge: DockerRunner.MAX_CONTAINER_AGE }, + 'starting container expiry' + ) + + // guarantee only one monitor is running + DockerRunner.stopContainerMonitor() + + // randomise the start time + const randomDelay = Math.floor(Math.random() * 5 * 60 * 1000) + containerMonitorTimeout = setTimeout(() => { + containerMonitorInterval = setInterval( + () => + DockerRunner.destroyOldContainers(err => { + if (err) { + logger.error({ err }, 'failed to destroy old containers') + } + }), + ONE_HOUR_IN_MS + ) + }, randomDelay) + }, + + stopContainerMonitor() { + if (containerMonitorTimeout) { + clearTimeout(containerMonitorTimeout) + containerMonitorTimeout = undefined + } + if (containerMonitorInterval) { + clearInterval(containerMonitorInterval) + containerMonitorInterval = undefined + } + }, +} + +DockerRunner.startContainerMonitor() + +module.exports = DockerRunner diff --git a/services/clsi/app/js/DraftModeManager.js b/services/clsi/app/js/DraftModeManager.js new file mode 100644 index 0000000000..9be65e7afd --- /dev/null +++ b/services/clsi/app/js/DraftModeManager.js @@ -0,0 +1,57 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-useless-escape, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let DraftModeManager +const fs = require('fs') +const logger = require('logger-sharelatex') + +module.exports = DraftModeManager = { + injectDraftMode(filename, callback) { + if (callback == null) { + callback = function (error) {} + } + return fs.readFile(filename, 'utf8', function (error, content) { + if (error != null) { + return callback(error) + } + // avoid adding draft mode more than once + if ( + (content != null + ? content.indexOf('\\documentclass[draft') + : undefined) >= 0 + ) { + return callback() + } + const modified_content = DraftModeManager._injectDraftOption(content) + logger.log( + { + content: content.slice(0, 1024), // \documentclass is normally v near the top + modified_content: modified_content.slice(0, 1024), + filename, + }, + 'injected draft class' + ) + return fs.writeFile(filename, modified_content, callback) + }) + }, + + _injectDraftOption(content) { + return ( + content + // With existing options (must be first, otherwise both are applied) + .replace(/\\documentclass\[/g, '\\documentclass[draft,') + // Without existing options + .replace(/\\documentclass\{/g, '\\documentclass[draft]{') + ) + }, +} diff --git a/services/clsi/app/js/Errors.js b/services/clsi/app/js/Errors.js new file mode 100644 index 0000000000..6b66c23421 --- /dev/null +++ b/services/clsi/app/js/Errors.js @@ -0,0 +1,41 @@ +/* eslint-disable + no-proto, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +const OError = require('@overleaf/o-error') + +let Errors +var NotFoundError = function (message) { + const error = new Error(message) + error.name = 'NotFoundError' + error.__proto__ = NotFoundError.prototype + return error +} +NotFoundError.prototype.__proto__ = Error.prototype + +var FilesOutOfSyncError = function (message) { + const error = new Error(message) + error.name = 'FilesOutOfSyncError' + error.__proto__ = FilesOutOfSyncError.prototype + return error +} +FilesOutOfSyncError.prototype.__proto__ = Error.prototype + +var AlreadyCompilingError = function (message) { + const error = new Error(message) + error.name = 'AlreadyCompilingError' + error.__proto__ = AlreadyCompilingError.prototype + return error +} +AlreadyCompilingError.prototype.__proto__ = Error.prototype + +class TimedOutError extends OError {} + +module.exports = Errors = { + TimedOutError, + NotFoundError, + FilesOutOfSyncError, + AlreadyCompilingError, +} diff --git a/services/clsi/app/js/LatexRunner.js b/services/clsi/app/js/LatexRunner.js new file mode 100644 index 0000000000..7c288cef08 --- /dev/null +++ b/services/clsi/app/js/LatexRunner.js @@ -0,0 +1,237 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let LatexRunner +const Path = require('path') +const Settings = require('@overleaf/settings') +const logger = require('logger-sharelatex') +const Metrics = require('./Metrics') +const CommandRunner = require('./CommandRunner') +const fs = require('fs') + +const ProcessTable = {} // table of currently running jobs (pids or docker container names) + +const TIME_V_METRICS = Object.entries({ + 'cpu-percent': /Percent of CPU this job got: (\d+)/m, + 'cpu-time': /User time.*: (\d+.\d+)/m, + 'sys-time': /System time.*: (\d+.\d+)/m, +}) + +module.exports = LatexRunner = { + runLatex(project_id, options, callback) { + let command + if (callback == null) { + callback = function (error) {} + } + let { + directory, + mainFile, + compiler, + timeout, + image, + environment, + flags, + compileGroup, + } = options + if (!compiler) { + compiler = 'pdflatex' + } + if (!timeout) { + timeout = 60000 + } // milliseconds + + logger.log( + { + directory, + compiler, + timeout, + mainFile, + environment, + flags, + compileGroup, + }, + 'starting compile' + ) + + // We want to run latexmk on the tex file which we will automatically + // generate from the Rtex/Rmd/md file. + mainFile = mainFile.replace(/\.(Rtex|md|Rmd)$/, '.tex') + + if (compiler === 'pdflatex') { + command = LatexRunner._pdflatexCommand(mainFile, flags) + } else if (compiler === 'latex') { + command = LatexRunner._latexCommand(mainFile, flags) + } else if (compiler === 'xelatex') { + command = LatexRunner._xelatexCommand(mainFile, flags) + } else if (compiler === 'lualatex') { + command = LatexRunner._lualatexCommand(mainFile, flags) + } else { + return callback(new Error(`unknown compiler: ${compiler}`)) + } + + if (Settings.clsi != null ? Settings.clsi.strace : undefined) { + command = ['strace', '-o', 'strace', '-ff'].concat(command) + } + + const id = `${project_id}` // record running project under this id + + return (ProcessTable[id] = CommandRunner.run( + project_id, + command, + directory, + image, + timeout, + environment, + compileGroup, + function (error, output) { + delete ProcessTable[id] + if (error != null) { + return callback(error) + } + const runs = + __guard__( + __guard__(output != null ? output.stderr : undefined, x1 => + x1.match(/^Run number \d+ of .*latex/gm) + ), + x => x.length + ) || 0 + const failed = + __guard__(output != null ? output.stdout : undefined, x2 => + x2.match(/^Latexmk: Errors/m) + ) != null + ? 1 + : 0 + // counters from latexmk output + const stats = {} + stats['latexmk-errors'] = failed + stats['latex-runs'] = runs + stats['latex-runs-with-errors'] = failed ? runs : 0 + stats[`latex-runs-${runs}`] = 1 + stats[`latex-runs-with-errors-${runs}`] = failed ? 1 : 0 + // timing information from /usr/bin/time + const timings = {} + const stderr = (output && output.stderr) || '' + if (stderr.includes('Command being timed:')) { + // Add metrics for runs with `$ time -v ...` + for (const [timing, matcher] of TIME_V_METRICS) { + const match = stderr.match(matcher) + if (match) { + timings[timing] = parseFloat(match[1]) + } + } + } + // record output files + LatexRunner.writeLogOutput(project_id, directory, output, () => { + return callback(error, output, stats, timings) + }) + } + )) + }, + + writeLogOutput(project_id, directory, output, callback) { + if (!output) { + return callback() + } + // internal method for writing non-empty log files + function _writeFile(file, content, cb) { + if (content && content.length > 0) { + fs.writeFile(file, content, err => { + if (err) { + logger.error({ project_id, file }, 'error writing log file') // don't fail on error + } + cb() + }) + } else { + cb() + } + } + // write stdout and stderr, ignoring errors + _writeFile(Path.join(directory, 'output.stdout'), output.stdout, () => { + _writeFile(Path.join(directory, 'output.stderr'), output.stderr, () => { + callback() + }) + }) + }, + + killLatex(project_id, callback) { + if (callback == null) { + callback = function (error) {} + } + const id = `${project_id}` + logger.log({ id }, 'killing running compile') + if (ProcessTable[id] == null) { + logger.warn({ id }, 'no such project to kill') + return callback(null) + } else { + return CommandRunner.kill(ProcessTable[id], callback) + } + }, + + _latexmkBaseCommand(flags) { + let args = [ + 'latexmk', + '-cd', + '-f', + '-jobname=output', + '-auxdir=$COMPILE_DIR', + '-outdir=$COMPILE_DIR', + '-synctex=1', + '-interaction=batchmode', + ] + if (flags) { + args = args.concat(flags) + } + return ( + __guard__( + Settings != null ? Settings.clsi : undefined, + x => x.latexmkCommandPrefix + ) || [] + ).concat(args) + }, + + _pdflatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ + '-pdf', + Path.join('$COMPILE_DIR', mainFile), + ]) + }, + + _latexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ + '-pdfdvi', + Path.join('$COMPILE_DIR', mainFile), + ]) + }, + + _xelatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ + '-xelatex', + Path.join('$COMPILE_DIR', mainFile), + ]) + }, + + _lualatexCommand(mainFile, flags) { + return LatexRunner._latexmkBaseCommand(flags).concat([ + '-lualatex', + Path.join('$COMPILE_DIR', mainFile), + ]) + }, +} + +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/LocalCommandRunner.js b/services/clsi/app/js/LocalCommandRunner.js new file mode 100644 index 0000000000..1e4236a579 --- /dev/null +++ b/services/clsi/app/js/LocalCommandRunner.js @@ -0,0 +1,103 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let CommandRunner +const { spawn } = require('child_process') +const _ = require('lodash') +const logger = require('logger-sharelatex') + +logger.info('using standard command runner') + +module.exports = CommandRunner = { + run( + project_id, + command, + directory, + image, + timeout, + environment, + compileGroup, + callback + ) { + let key, value + if (callback == null) { + callback = function (error) {} + } else { + callback = _.once(callback) + } + command = Array.from(command).map(arg => + arg.toString().replace('$COMPILE_DIR', directory) + ) + logger.log({ project_id, command, directory }, 'running command') + logger.warn('timeouts and sandboxing are not enabled with CommandRunner') + + // merge environment settings + const env = {} + for (key in process.env) { + value = process.env[key] + env[key] = value + } + for (key in environment) { + value = environment[key] + env[key] = value + } + + // run command as detached process so it has its own process group (which can be killed if needed) + const proc = spawn(command[0], command.slice(1), { cwd: directory, env }) + + let stdout = '' + proc.stdout.setEncoding('utf8').on('data', data => (stdout += data)) + + proc.on('error', function (err) { + logger.err( + { err, project_id, command, directory }, + 'error running command' + ) + return callback(err) + }) + + proc.on('close', function (code, signal) { + let err + logger.info({ code, signal, project_id }, 'command exited') + if (signal === 'SIGTERM') { + // signal from kill method below + err = new Error('terminated') + err.terminated = true + return callback(err) + } else if (code === 1) { + // exit status from chktex + err = new Error('exited') + err.code = code + return callback(err) + } else { + return callback(null, { stdout: stdout }) + } + }) + + return proc.pid + }, // return process id to allow job to be killed if necessary + + kill(pid, callback) { + if (callback == null) { + callback = function (error) {} + } + try { + process.kill(-pid) // kill all processes in group + } catch (err) { + return callback(err) + } + return callback() + }, +} diff --git a/services/clsi/app/js/LockManager.js b/services/clsi/app/js/LockManager.js new file mode 100644 index 0000000000..2dc09b336c --- /dev/null +++ b/services/clsi/app/js/LockManager.js @@ -0,0 +1,72 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let LockManager +const Settings = require('@overleaf/settings') +const logger = require('logger-sharelatex') +const Lockfile = require('lockfile') // from https://github.com/npm/lockfile +const Errors = require('./Errors') +const fs = require('fs') +const Path = require('path') +module.exports = LockManager = { + LOCK_TEST_INTERVAL: 1000, // 50ms between each test of the lock + MAX_LOCK_WAIT_TIME: 15000, // 10s maximum time to spend trying to get the lock + LOCK_STALE: 5 * 60 * 1000, // 5 mins time until lock auto expires + + runWithLock(path, runner, callback) { + if (callback == null) { + callback = function (error) {} + } + const lockOpts = { + wait: this.MAX_LOCK_WAIT_TIME, + pollPeriod: this.LOCK_TEST_INTERVAL, + stale: this.LOCK_STALE, + } + return Lockfile.lock(path, lockOpts, function (error) { + if ((error != null ? error.code : undefined) === 'EEXIST') { + return callback(new Errors.AlreadyCompilingError('compile in progress')) + } else if (error != null) { + return fs.lstat(path, (statLockErr, statLock) => + fs.lstat(Path.dirname(path), (statDirErr, statDir) => + fs.readdir(Path.dirname(path), function (readdirErr, readdirDir) { + logger.err( + { + error, + path, + statLock, + statLockErr, + statDir, + statDirErr, + readdirErr, + readdirDir, + }, + 'unable to get lock' + ) + return callback(error) + }) + ) + ) + } else { + return runner((error1, ...args) => + Lockfile.unlock(path, function (error2) { + error = error1 || error2 + if (error != null) { + return callback(error) + } + return callback(null, ...Array.from(args)) + }) + ) + } + }) + }, +} diff --git a/services/clsi/app/js/Metrics.js b/services/clsi/app/js/Metrics.js new file mode 100644 index 0000000000..f0e57794fd --- /dev/null +++ b/services/clsi/app/js/Metrics.js @@ -0,0 +1,3 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +module.exports = require('@overleaf/metrics') diff --git a/services/clsi/app/js/OutputCacheManager.js b/services/clsi/app/js/OutputCacheManager.js new file mode 100644 index 0000000000..af85f49661 --- /dev/null +++ b/services/clsi/app/js/OutputCacheManager.js @@ -0,0 +1,563 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS104: Avoid inline assignments + * DS204: Change includes calls to have a more natural evaluation order + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let OutputCacheManager +const async = require('async') +const fs = require('fs') +const fse = require('fs-extra') +const Path = require('path') +const logger = require('logger-sharelatex') +const _ = require('lodash') +const Settings = require('@overleaf/settings') +const crypto = require('crypto') +const Metrics = require('./Metrics') + +const OutputFileOptimiser = require('./OutputFileOptimiser') +const ContentCacheManager = require('./ContentCacheManager') +const { TimedOutError } = require('./Errors') + +module.exports = OutputCacheManager = { + CONTENT_SUBDIR: 'content', + CACHE_SUBDIR: 'generated-files', + ARCHIVE_SUBDIR: 'archived-logs', + // build id is HEXDATE-HEXRANDOM from Date.now()and RandomBytes + // for backwards compatibility, make the randombytes part optional + BUILD_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/, + CONTENT_REGEX: /^[0-9a-f]+(-[0-9a-f]+)?$/, + CACHE_LIMIT: 2, // maximum number of cache directories + CACHE_AGE: 60 * 60 * 1000, // up to one hour old + + path(buildId, file) { + // used by static server, given build id return '.cache/clsi/buildId' + if (buildId.match(OutputCacheManager.BUILD_REGEX)) { + return Path.join(OutputCacheManager.CACHE_SUBDIR, buildId, file) + } else { + // for invalid build id, return top level + return file + } + }, + + generateBuildId(callback) { + // generate a secure build id from Date.now() and 8 random bytes in hex + if (callback == null) { + callback = function (error, buildId) {} + } + return crypto.randomBytes(8, function (err, buf) { + if (err != null) { + return callback(err) + } + const random = buf.toString('hex') + const date = Date.now().toString(16) + return callback(err, `${date}-${random}`) + }) + }, + + saveOutputFiles( + { request, stats, timings }, + outputFiles, + compileDir, + outputDir, + callback + ) { + if (callback == null) { + callback = function (error) {} + } + return OutputCacheManager.generateBuildId(function (err, buildId) { + if (err != null) { + return callback(err) + } + return OutputCacheManager.saveOutputFilesInBuildDir( + outputFiles, + compileDir, + outputDir, + buildId, + function (err, result) { + if (err != null) { + return callback(err) + } + OutputCacheManager.collectOutputPdfSize( + result, + outputDir, + stats, + (err, result) => { + if (err) return callback(err, result) + + if (!Settings.enablePdfCaching || !request.enablePdfCaching) { + return callback(null, result) + } + + OutputCacheManager.saveStreamsInContentDir( + { stats, timings }, + result, + compileDir, + outputDir, + callback + ) + } + ) + } + ) + }) + }, + + saveOutputFilesInBuildDir( + outputFiles, + compileDir, + outputDir, + buildId, + callback + ) { + // make a compileDir/CACHE_SUBDIR/build_id directory and + // copy all the output files into it + if (callback == null) { + callback = function (error) {} + } + const cacheRoot = Path.join(outputDir, OutputCacheManager.CACHE_SUBDIR) + // Put the files into a new cache subdirectory + const cacheDir = Path.join( + outputDir, + OutputCacheManager.CACHE_SUBDIR, + buildId + ) + // Is it a per-user compile? check if compile directory is PROJECTID-USERID + const perUser = Path.basename(compileDir).match( + /^[0-9a-f]{24}-[0-9a-f]{24}$/ + ) + + // Archive logs in background + if ( + (Settings.clsi != null ? Settings.clsi.archive_logs : undefined) || + (Settings.clsi != null ? Settings.clsi.strace : undefined) + ) { + OutputCacheManager.archiveLogs( + outputFiles, + compileDir, + outputDir, + buildId, + function (err) { + if (err != null) { + return logger.warn({ err }, 'erroring archiving log files') + } + } + ) + } + + // make the new cache directory + return fse.ensureDir(cacheDir, function (err) { + if (err != null) { + logger.error( + { err, directory: cacheDir }, + 'error creating cache directory' + ) + return callback(err, outputFiles) + } else { + // copy all the output files into the new cache directory + const results = [] + return async.mapSeries( + outputFiles, + function (file, cb) { + // don't send dot files as output, express doesn't serve them + if (OutputCacheManager._fileIsHidden(file.path)) { + logger.debug( + { compileDir, path: file.path }, + 'ignoring dotfile in output' + ) + return cb() + } + // copy other files into cache directory if valid + const newFile = _.clone(file) + const [src, dst] = Array.from([ + Path.join(compileDir, file.path), + Path.join(cacheDir, file.path), + ]) + return OutputCacheManager._checkFileIsSafe( + src, + function (err, isSafe) { + if (err != null) { + return cb(err) + } + if (!isSafe) { + return cb() + } + return OutputCacheManager._checkIfShouldCopy( + src, + function (err, shouldCopy) { + if (err != null) { + return cb(err) + } + if (!shouldCopy) { + return cb() + } + return OutputCacheManager._copyFile( + src, + dst, + function (err) { + if (err != null) { + return cb(err) + } + newFile.build = buildId // attach a build id if we cached the file + results.push(newFile) + return cb() + } + ) + } + ) + } + ) + }, + function (err) { + if (err != null) { + // pass back the original files if we encountered *any* error + callback(err, outputFiles) + // clean up the directory we just created + return fse.remove(cacheDir, function (err) { + if (err != null) { + return logger.error( + { err, dir: cacheDir }, + 'error removing cache dir after failure' + ) + } + }) + } else { + // pass back the list of new files in the cache + callback(err, results) + // let file expiry run in the background, expire all previous files if per-user + return OutputCacheManager.expireOutputFiles(cacheRoot, { + keep: buildId, + limit: perUser ? 1 : null, + }) + } + } + ) + } + }) + }, + + collectOutputPdfSize(outputFiles, outputDir, stats, callback) { + const outputFile = outputFiles.find(x => x.path === 'output.pdf') + if (!outputFile) return callback(null, outputFiles) + const outputFilePath = Path.join( + outputDir, + OutputCacheManager.path(outputFile.build, outputFile.path) + ) + fs.stat(outputFilePath, (err, stat) => { + if (err) return callback(err, outputFiles) + + outputFile.size = stat.size + stats['pdf-size'] = outputFile.size + callback(null, outputFiles) + }) + }, + + saveStreamsInContentDir( + { stats, timings }, + outputFiles, + compileDir, + outputDir, + callback + ) { + const cacheRoot = Path.join(outputDir, OutputCacheManager.CONTENT_SUBDIR) + // check if content dir exists + OutputCacheManager.ensureContentDir(cacheRoot, function (err, contentDir) { + if (err) return callback(err, outputFiles) + + const outputFile = outputFiles.find(x => x.path === 'output.pdf') + if (outputFile) { + // possibly we should copy the file from the build dir here + const outputFilePath = Path.join( + outputDir, + OutputCacheManager.path(outputFile.build, outputFile.path) + ) + const pdfSize = outputFile.size + const timer = new Metrics.Timer('compute-pdf-ranges') + ContentCacheManager.update( + contentDir, + outputFilePath, + pdfSize, + timings.compile, + function (err, result) { + if (err && err instanceof TimedOutError) { + logger.warn( + { err, outputDir, stats, timings }, + 'pdf caching timed out' + ) + stats['pdf-caching-timed-out'] = 1 + return callback(null, outputFiles) + } + if (err) return callback(err, outputFiles) + const [contentRanges, newContentRanges, reclaimedSpace] = result + + if (Settings.enablePdfCachingDark) { + // In dark mode we are doing the computation only and do not emit + // any ranges to the frontend. + } else { + outputFile.contentId = Path.basename(contentDir) + outputFile.ranges = contentRanges + } + + timings['compute-pdf-caching'] = timer.done() + stats['pdf-caching-n-ranges'] = contentRanges.length + stats['pdf-caching-total-ranges-size'] = contentRanges.reduce( + (sum, next) => sum + (next.end - next.start), + 0 + ) + stats['pdf-caching-n-new-ranges'] = newContentRanges.length + stats['pdf-caching-new-ranges-size'] = newContentRanges.reduce( + (sum, next) => sum + (next.end - next.start), + 0 + ) + stats['pdf-caching-reclaimed-space'] = reclaimedSpace + callback(null, outputFiles) + } + ) + } else { + callback(null, outputFiles) + } + }) + }, + + ensureContentDir(contentRoot, callback) { + fse.ensureDir(contentRoot, function (err) { + if (err != null) { + return callback(err) + } + fs.readdir(contentRoot, function (err, results) { + const dirs = results.sort() + const contentId = dirs.find(dir => + OutputCacheManager.BUILD_REGEX.test(dir) + ) + if (contentId) { + callback(null, Path.join(contentRoot, contentId)) + } else { + // make a content directory + OutputCacheManager.generateBuildId(function (err, contentId) { + if (err) { + return callback(err) + } + const contentDir = Path.join(contentRoot, contentId) + fse.ensureDir(contentDir, function (err) { + if (err) { + return callback(err) + } + return callback(null, contentDir) + }) + }) + } + }) + }) + }, + + archiveLogs(outputFiles, compileDir, outputDir, buildId, callback) { + if (callback == null) { + callback = function (error) {} + } + const archiveDir = Path.join( + outputDir, + OutputCacheManager.ARCHIVE_SUBDIR, + buildId + ) + logger.log({ dir: archiveDir }, 'archiving log files for project') + return fse.ensureDir(archiveDir, function (err) { + if (err != null) { + return callback(err) + } + return async.mapSeries( + outputFiles, + function (file, cb) { + const [src, dst] = Array.from([ + Path.join(compileDir, file.path), + Path.join(archiveDir, file.path), + ]) + return OutputCacheManager._checkFileIsSafe( + src, + function (err, isSafe) { + if (err != null) { + return cb(err) + } + if (!isSafe) { + return cb() + } + return OutputCacheManager._checkIfShouldArchive( + src, + function (err, shouldArchive) { + if (err != null) { + return cb(err) + } + if (!shouldArchive) { + return cb() + } + return OutputCacheManager._copyFile(src, dst, cb) + } + ) + } + ) + }, + callback + ) + }) + }, + + expireOutputFiles(cacheRoot, options, callback) { + // look in compileDir for build dirs and delete if > N or age of mod time > T + if (callback == null) { + callback = function (error) {} + } + return fs.readdir(cacheRoot, function (err, results) { + if (err != null) { + if (err.code === 'ENOENT') { + return callback(null) + } // cache directory is empty + logger.error({ err, project_id: cacheRoot }, 'error clearing cache') + return callback(err) + } + + const dirs = results.sort().reverse() + const currentTime = Date.now() + + const isExpired = function (dir, index) { + if ((options != null ? options.keep : undefined) === dir) { + return false + } + // remove any directories over the requested (non-null) limit + if ( + (options != null ? options.limit : undefined) != null && + index > options.limit + ) { + return true + } + // remove any directories over the hard limit + if (index > OutputCacheManager.CACHE_LIMIT) { + return true + } + // we can get the build time from the first part of the directory name DDDD-RRRR + // DDDD is date and RRRR is random bytes + const dirTime = parseInt( + __guard__(dir.split('-'), x => x[0]), + 16 + ) + const age = currentTime - dirTime + return age > OutputCacheManager.CACHE_AGE + } + + const toRemove = _.filter(dirs, isExpired) + + const removeDir = (dir, cb) => + fse.remove(Path.join(cacheRoot, dir), function (err, result) { + logger.log({ cache: cacheRoot, dir }, 'removed expired cache dir') + if (err != null) { + logger.error({ err, dir }, 'cache remove error') + } + return cb(err, result) + }) + return async.eachSeries( + toRemove, + (dir, cb) => removeDir(dir, cb), + callback + ) + }) + }, + + _fileIsHidden(path) { + return (path != null ? path.match(/^\.|\/\./) : undefined) != null + }, + + _checkFileIsSafe(src, callback) { + // check if we have a valid file to copy into the cache + if (callback == null) { + callback = function (error, isSafe) {} + } + return fs.stat(src, function (err, stats) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + logger.warn( + { err, file: src }, + 'file has disappeared before copying to build cache' + ) + return callback(err, false) + } else if (err != null) { + // some other problem reading the file + logger.error({ err, file: src }, 'stat error for file in cache') + return callback(err, false) + } else if (!stats.isFile()) { + // other filetype - reject it + logger.warn( + { src, stat: stats }, + 'nonfile output - refusing to copy to cache' + ) + return callback(null, false) + } else { + // it's a plain file, ok to copy + return callback(null, true) + } + }) + }, + + _copyFile(src, dst, callback) { + // copy output file into the cache + return fse.copy(src, dst, function (err) { + if ((err != null ? err.code : undefined) === 'ENOENT') { + logger.warn( + { err, file: src }, + 'file has disappeared when copying to build cache' + ) + return callback(err, false) + } else if (err != null) { + logger.error({ err, src, dst }, 'copy error for file in cache') + return callback(err) + } else { + if ( + Settings.clsi != null ? Settings.clsi.optimiseInDocker : undefined + ) { + // don't run any optimisations on the pdf when they are done + // in the docker container + return callback() + } else { + // call the optimiser for the file too + return OutputFileOptimiser.optimiseFile(src, dst, callback) + } + } + }) + }, + + _checkIfShouldCopy(src, callback) { + if (callback == null) { + callback = function (err, shouldCopy) {} + } + return callback(null, !Path.basename(src).match(/^strace/)) + }, + + _checkIfShouldArchive(src, callback) { + let needle + if (callback == null) { + callback = function (err, shouldCopy) {} + } + if (Path.basename(src).match(/^strace/)) { + return callback(null, true) + } + if ( + (Settings.clsi != null ? Settings.clsi.archive_logs : undefined) && + ((needle = Path.basename(src)), + ['output.log', 'output.blg'].includes(needle)) + ) { + return callback(null, true) + } + return callback(null, false) + }, +} + +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/OutputFileFinder.js b/services/clsi/app/js/OutputFileFinder.js new file mode 100644 index 0000000000..9088215d1a --- /dev/null +++ b/services/clsi/app/js/OutputFileFinder.js @@ -0,0 +1,78 @@ +let OutputFileFinder +const Path = require('path') +const _ = require('lodash') +const { spawn } = require('child_process') +const logger = require('logger-sharelatex') + +module.exports = OutputFileFinder = { + findOutputFiles(resources, directory, callback) { + const incomingResources = new Set(resources.map(resource => resource.path)) + + OutputFileFinder._getAllFiles(directory, function (error, allFiles) { + if (allFiles == null) { + allFiles = [] + } + if (error) { + logger.err({ err: error }, 'error finding all output files') + return callback(error) + } + const outputFiles = [] + for (const file of allFiles) { + if (!incomingResources.has(file)) { + outputFiles.push({ + path: file, + type: Path.extname(file).replace(/^\./, '') || undefined, + }) + } + } + callback(null, outputFiles, allFiles) + }) + }, + + _getAllFiles(directory, callback) { + callback = _.once(callback) + // don't include clsi-specific files/directories in the output list + const EXCLUDE_DIRS = [ + '-name', + '.cache', + '-o', + '-name', + '.archive', + '-o', + '-name', + '.project-*', + ] + const args = [ + directory, + '(', + ...EXCLUDE_DIRS, + ')', + '-prune', + '-o', + '-type', + 'f', + '-print', + ] + logger.log({ args }, 'running find command') + + const proc = spawn('find', args) + let stdout = '' + proc.stdout.setEncoding('utf8').on('data', chunk => (stdout += chunk)) + proc.on('error', callback) + proc.on('close', function (code) { + if (code !== 0) { + logger.warn( + { directory, code }, + "find returned error, directory likely doesn't exist" + ) + return callback(null, []) + } + let fileList = stdout.trim().split('\n') + fileList = fileList.map(function (file) { + // Strip leading directory + return Path.relative(directory, file) + }) + callback(null, fileList) + }) + }, +} diff --git a/services/clsi/app/js/OutputFileOptimiser.js b/services/clsi/app/js/OutputFileOptimiser.js new file mode 100644 index 0000000000..979697b07c --- /dev/null +++ b/services/clsi/app/js/OutputFileOptimiser.js @@ -0,0 +1,103 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, + no-undef, + no-unused-vars, + node/no-deprecated-api, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let OutputFileOptimiser +const fs = require('fs') +const Path = require('path') +const { spawn } = require('child_process') +const logger = require('logger-sharelatex') +const Metrics = require('./Metrics') +const _ = require('lodash') + +module.exports = OutputFileOptimiser = { + optimiseFile(src, dst, callback) { + // check output file (src) and see if we can optimise it, storing + // the result in the build directory (dst) + if (callback == null) { + callback = function (error) {} + } + if (src.match(/\/output\.pdf$/)) { + return OutputFileOptimiser.checkIfPDFIsOptimised( + src, + function (err, isOptimised) { + if (err != null || isOptimised) { + return callback(null) + } + return OutputFileOptimiser.optimisePDF(src, dst, callback) + } + ) + } else { + return callback(null) + } + }, + + checkIfPDFIsOptimised(file, callback) { + const SIZE = 16 * 1024 // check the header of the pdf + const result = Buffer.alloc(SIZE) // fills with zeroes by default + return fs.open(file, 'r', function (err, fd) { + if (err != null) { + return callback(err) + } + return fs.read(fd, result, 0, SIZE, 0, (errRead, bytesRead, buffer) => + fs.close(fd, function (errClose) { + if (errRead != null) { + return callback(errRead) + } + if (typeof errReadClose !== 'undefined' && errReadClose !== null) { + return callback(errClose) + } + const isOptimised = + buffer.toString('ascii').indexOf('/Linearized 1') >= 0 + return callback(null, isOptimised) + }) + ) + }) + }, + + optimisePDF(src, dst, callback) { + if (callback == null) { + callback = function (error) {} + } + const tmpOutput = dst + '.opt' + const args = ['--linearize', '--newline-before-endstream', src, tmpOutput] + logger.log({ args }, 'running qpdf command') + + const timer = new Metrics.Timer('qpdf') + const proc = spawn('qpdf', args) + let stdout = '' + proc.stdout.setEncoding('utf8').on('data', chunk => (stdout += chunk)) + callback = _.once(callback) // avoid double call back for error and close event + proc.on('error', function (err) { + logger.warn({ err, args }, 'qpdf failed') + return callback(null) + }) // ignore the error + return proc.on('close', function (code) { + timer.done() + if (code !== 0) { + logger.warn({ code, args }, 'qpdf returned error') + return callback(null) // ignore the error + } + return fs.rename(tmpOutput, dst, function (err) { + if (err != null) { + logger.warn( + { tmpOutput, dst }, + 'failed to rename output of qpdf command' + ) + } + return callback(null) + }) + }) + }, // ignore the error +} diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js new file mode 100644 index 0000000000..c4abb69b6e --- /dev/null +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -0,0 +1,207 @@ +/* eslint-disable + camelcase, + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let ProjectPersistenceManager +const Metrics = require('./Metrics') +const UrlCache = require('./UrlCache') +const CompileManager = require('./CompileManager') +const db = require('./db') +const dbQueue = require('./DbQueue') +const async = require('async') +const logger = require('logger-sharelatex') +const oneDay = 24 * 60 * 60 * 1000 +const Settings = require('@overleaf/settings') +const diskusage = require('diskusage') +const { callbackify } = require('util') + +async function refreshExpiryTimeout() { + const paths = [ + Settings.path.compilesDir, + Settings.path.outputDir, + Settings.path.clsiCacheDir, + ] + for (const path of paths) { + try { + const stats = await diskusage.check(path) + const lowDisk = stats.available / stats.total < 0.1 + + const lowerExpiry = ProjectPersistenceManager.EXPIRY_TIMEOUT * 0.9 + if (lowDisk && Settings.project_cache_length_ms / 2 < lowerExpiry) { + logger.warn( + { + stats, + newExpiryTimeoutInDays: (lowerExpiry / oneDay).toFixed(2), + }, + 'disk running low on space, modifying EXPIRY_TIMEOUT' + ) + ProjectPersistenceManager.EXPIRY_TIMEOUT = lowerExpiry + break + } + } catch (err) { + logger.err({ err, path }, 'error getting disk usage') + } + } +} + +module.exports = ProjectPersistenceManager = { + EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, + + promises: { + refreshExpiryTimeout, + }, + + refreshExpiryTimeout: callbackify(refreshExpiryTimeout), + markProjectAsJustAccessed(project_id, callback) { + if (callback == null) { + callback = function (error) {} + } + const timer = new Metrics.Timer('db-bump-last-accessed') + const job = cb => + db.Project.findOrCreate({ where: { project_id } }) + .spread((project, created) => + project + .update({ lastAccessed: new Date() }) + .then(() => cb()) + .error(cb) + ) + .error(cb) + dbQueue.queue.push(job, error => { + timer.done() + callback(error) + }) + }, + + clearExpiredProjects(callback) { + if (callback == null) { + callback = function (error) {} + } + return ProjectPersistenceManager._findExpiredProjectIds(function ( + error, + project_ids + ) { + if (error != null) { + return callback(error) + } + logger.log({ project_ids }, 'clearing expired projects') + const jobs = Array.from(project_ids || []).map(project_id => + ( + project_id => callback => + ProjectPersistenceManager.clearProjectFromCache( + project_id, + function (err) { + if (err != null) { + logger.error({ err, project_id }, 'error clearing project') + } + return callback() + } + ) + )(project_id) + ) + return async.series(jobs, function (error) { + if (error != null) { + return callback(error) + } + return CompileManager.clearExpiredProjects( + ProjectPersistenceManager.EXPIRY_TIMEOUT, + error => callback() + ) + }) + }) + }, // ignore any errors from deleting directories + + clearProject(project_id, user_id, callback) { + if (callback == null) { + callback = function (error) {} + } + logger.log({ project_id, user_id }, 'clearing project for user') + return CompileManager.clearProject(project_id, user_id, function (error) { + if (error != null) { + return callback(error) + } + return ProjectPersistenceManager.clearProjectFromCache( + project_id, + function (error) { + if (error != null) { + return callback(error) + } + return callback() + } + ) + }) + }, + + clearProjectFromCache(project_id, callback) { + if (callback == null) { + callback = function (error) {} + } + logger.log({ project_id }, 'clearing project from cache') + return UrlCache.clearProject(project_id, function (error) { + if (error != null) { + logger.err({ error, project_id }, 'error clearing project from cache') + return callback(error) + } + return ProjectPersistenceManager._clearProjectFromDatabase( + project_id, + function (error) { + if (error != null) { + logger.err( + { error, project_id }, + 'error clearing project from database' + ) + } + return callback(error) + } + ) + }) + }, + + _clearProjectFromDatabase(project_id, callback) { + if (callback == null) { + callback = function (error) {} + } + logger.log({ project_id }, 'clearing project from database') + const job = cb => + db.Project.destroy({ where: { project_id } }) + .then(() => cb()) + .error(cb) + return dbQueue.queue.push(job, callback) + }, + + _findExpiredProjectIds(callback) { + if (callback == null) { + callback = function (error, project_ids) {} + } + const job = function (cb) { + const keepProjectsFrom = new Date( + Date.now() - ProjectPersistenceManager.EXPIRY_TIMEOUT + ) + const q = {} + q[db.op.lt] = keepProjectsFrom + return db.Project.findAll({ where: { lastAccessed: q } }) + .then(projects => + cb( + null, + projects.map(project => project.project_id) + ) + ) + .error(cb) + } + + return dbQueue.queue.push(job, callback) + }, +} + +logger.log( + { EXPIRY_TIMEOUT: ProjectPersistenceManager.EXPIRY_TIMEOUT }, + 'project assets kept timeout' +) diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js new file mode 100644 index 0000000000..04502e422c --- /dev/null +++ b/services/clsi/app/js/RequestParser.js @@ -0,0 +1,239 @@ +/* eslint-disable + handle-callback-err, + no-control-regex, + no-throw-literal, + no-unused-vars, + no-useless-escape, + valid-typeof, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let RequestParser +const settings = require('@overleaf/settings') + +module.exports = RequestParser = { + VALID_COMPILERS: ['pdflatex', 'latex', 'xelatex', 'lualatex'], + MAX_TIMEOUT: 600, + + parse(body, callback) { + let resource + if (callback == null) { + callback = function (error, data) {} + } + const response = {} + + if (body.compile == null) { + return callback('top level object should have a compile attribute') + } + + const { compile } = body + if (!compile.options) { + compile.options = {} + } + + try { + response.compiler = this._parseAttribute( + 'compiler', + compile.options.compiler, + { + validValues: this.VALID_COMPILERS, + default: 'pdflatex', + type: 'string', + } + ) + response.enablePdfCaching = this._parseAttribute( + 'enablePdfCaching', + compile.options.enablePdfCaching, + { + default: false, + type: 'boolean', + } + ) + response.timeout = this._parseAttribute( + 'timeout', + compile.options.timeout, + { + default: RequestParser.MAX_TIMEOUT, + type: 'number', + } + ) + response.imageName = this._parseAttribute( + 'imageName', + compile.options.imageName, + { + type: 'string', + validValues: + settings.clsi && + settings.clsi.docker && + settings.clsi.docker.allowedImages, + } + ) + response.draft = this._parseAttribute('draft', compile.options.draft, { + default: false, + type: 'boolean', + }) + response.check = this._parseAttribute('check', compile.options.check, { + type: 'string', + }) + response.flags = this._parseAttribute('flags', compile.options.flags, { + default: [], + type: 'object', + }) + if (settings.allowedCompileGroups) { + response.compileGroup = this._parseAttribute( + 'compileGroup', + compile.options.compileGroup, + { + validValues: settings.allowedCompileGroups, + default: '', + type: 'string', + } + ) + } + // The syncType specifies whether the request contains all + // resources (full) or only those resources to be updated + // in-place (incremental). + response.syncType = this._parseAttribute( + 'syncType', + compile.options.syncType, + { + validValues: ['full', 'incremental'], + type: 'string', + } + ) + + // The syncState is an identifier passed in with the request + // which has the property that it changes when any resource is + // added, deleted, moved or renamed. + // + // on syncType full the syncState identifier is passed in and + // stored + // + // on syncType incremental the syncState identifier must match + // the stored value + response.syncState = this._parseAttribute( + 'syncState', + compile.options.syncState, + { type: 'string' } + ) + + if (response.timeout > RequestParser.MAX_TIMEOUT) { + response.timeout = RequestParser.MAX_TIMEOUT + } + response.timeout = response.timeout * 1000 // milliseconds + + response.resources = (() => { + const result = [] + for (resource of Array.from(compile.resources || [])) { + result.push(this._parseResource(resource)) + } + return result + })() + + const rootResourcePath = this._parseAttribute( + 'rootResourcePath', + compile.rootResourcePath, + { + default: 'main.tex', + type: 'string', + } + ) + const originalRootResourcePath = rootResourcePath + const sanitizedRootResourcePath = + RequestParser._sanitizePath(rootResourcePath) + response.rootResourcePath = RequestParser._checkPath( + sanitizedRootResourcePath + ) + + for (resource of Array.from(response.resources)) { + if (resource.path === originalRootResourcePath) { + resource.path = sanitizedRootResourcePath + } + } + } catch (error1) { + const error = error1 + return callback(error) + } + + return callback(null, response) + }, + + _parseResource(resource) { + let modified + if (resource.path == null || typeof resource.path !== 'string') { + throw 'all resources should have a path attribute' + } + + if (resource.modified != null) { + modified = new Date(resource.modified) + if (isNaN(modified.getTime())) { + throw `resource modified date could not be understood: ${resource.modified}` + } + } + + if (resource.url == null && resource.content == null) { + throw 'all resources should have either a url or content attribute' + } + if (resource.content != null && typeof resource.content !== 'string') { + throw 'content attribute should be a string' + } + if (resource.url != null && typeof resource.url !== 'string') { + throw 'url attribute should be a string' + } + + return { + path: resource.path, + modified, + url: resource.url, + content: resource.content, + } + }, + + _parseAttribute(name, attribute, options) { + if (attribute != null) { + if (options.validValues != null) { + if (options.validValues.indexOf(attribute) === -1) { + throw `${name} attribute should be one of: ${options.validValues.join( + ', ' + )}` + } + } + if (options.type != null) { + if (typeof attribute !== options.type) { + throw `${name} attribute should be a ${options.type}` + } + } + } else { + if (options.default != null) { + return options.default + } + } + return attribute + }, + + _sanitizePath(path) { + // See http://php.net/manual/en/function.escapeshellcmd.php + return path.replace( + /[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, + '' + ) + }, + + _checkPath(path) { + // check that the request does not use a relative path + for (const dir of Array.from(path.split('/'))) { + if (dir === '..') { + throw 'relative path in root resource' + } + } + return path + }, +} diff --git a/services/clsi/app/js/ResourceStateManager.js b/services/clsi/app/js/ResourceStateManager.js new file mode 100644 index 0000000000..7ae3557d51 --- /dev/null +++ b/services/clsi/app/js/ResourceStateManager.js @@ -0,0 +1,116 @@ +const Path = require('path') +const fs = require('fs') +const logger = require('logger-sharelatex') +const Errors = require('./Errors') +const SafeReader = require('./SafeReader') + +module.exports = { + // The sync state is an identifier which must match for an + // incremental update to be allowed. + // + // The initial value is passed in and stored on a full + // compile, along with the list of resources.. + // + // Subsequent incremental compiles must come with the same value - if + // not they will be rejected with a 409 Conflict response. The + // previous list of resources is returned. + // + // An incremental compile can only update existing files with new + // content. The sync state identifier must change if any docs or + // files are moved, added, deleted or renamed. + + SYNC_STATE_FILE: '.project-sync-state', + SYNC_STATE_MAX_SIZE: 128 * 1024, + + saveProjectState(state, resources, basePath, callback) { + const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) + if (state == null) { + // remove the file if no state passed in + logger.log({ state, basePath }, 'clearing sync state') + fs.unlink(stateFile, function (err) { + if (err && err.code !== 'ENOENT') { + return callback(err) + } else { + return callback() + } + }) + } else { + logger.log({ state, basePath }, 'writing sync state') + const resourceList = resources.map(resource => resource.path) + fs.writeFile( + stateFile, + [...resourceList, `stateHash:${state}`].join('\n'), + callback + ) + } + }, + + checkProjectStateMatches(state, basePath, callback) { + const stateFile = Path.join(basePath, this.SYNC_STATE_FILE) + const size = this.SYNC_STATE_MAX_SIZE + SafeReader.readFile( + stateFile, + size, + 'utf8', + function (err, result, bytesRead) { + if (err) { + return callback(err) + } + if (bytesRead === size) { + logger.error( + { file: stateFile, size, bytesRead }, + 'project state file truncated' + ) + } + const array = result ? result.toString().split('\n') : [] + const adjustedLength = Math.max(array.length, 1) + const resourceList = array.slice(0, adjustedLength - 1) + const oldState = array[adjustedLength - 1] + const newState = `stateHash:${state}` + logger.log( + { state, oldState, basePath, stateMatches: newState === oldState }, + 'checking sync state' + ) + if (newState !== oldState) { + return callback( + new Errors.FilesOutOfSyncError( + 'invalid state for incremental update' + ) + ) + } else { + const resources = resourceList.map(path => ({ path })) + callback(null, resources) + } + } + ) + }, + + checkResourceFiles(resources, allFiles, basePath, callback) { + // check the paths are all relative to current directory + const containsRelativePath = resource => { + const dirs = resource.path.split('/') + return dirs.indexOf('..') !== -1 + } + if (resources.some(containsRelativePath)) { + return callback(new Error('relative path in resource file list')) + } + // check if any of the input files are not present in list of files + const seenFiles = new Set(allFiles) + const missingFiles = resources + .map(resource => resource.path) + .filter(path => !seenFiles.has(path)) + if (missingFiles.length > 0) { + logger.err( + { missingFiles, basePath, allFiles, resources }, + 'missing input files for project' + ) + return callback( + new Errors.FilesOutOfSyncError( + 'resource files missing in incremental update' + ) + ) + } else { + callback() + } + }, +} diff --git a/services/clsi/app/js/ResourceWriter.js b/services/clsi/app/js/ResourceWriter.js new file mode 100644 index 0000000000..d7cdc5027f --- /dev/null +++ b/services/clsi/app/js/ResourceWriter.js @@ -0,0 +1,354 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, + no-unused-vars, + no-useless-escape, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let ResourceWriter +const UrlCache = require('./UrlCache') +const Path = require('path') +const fs = require('fs') +const async = require('async') +const OutputFileFinder = require('./OutputFileFinder') +const ResourceStateManager = require('./ResourceStateManager') +const Metrics = require('./Metrics') +const logger = require('logger-sharelatex') +const settings = require('@overleaf/settings') + +const parallelFileDownloads = settings.parallelFileDownloads || 1 + +module.exports = ResourceWriter = { + syncResourcesToDisk(request, basePath, callback) { + if (callback == null) { + callback = function (error, resourceList) {} + } + if (request.syncType === 'incremental') { + logger.log( + { project_id: request.project_id, user_id: request.user_id }, + 'incremental sync' + ) + return ResourceStateManager.checkProjectStateMatches( + request.syncState, + basePath, + function (error, resourceList) { + if (error != null) { + return callback(error) + } + return ResourceWriter._removeExtraneousFiles( + resourceList, + basePath, + function (error, outputFiles, allFiles) { + if (error != null) { + return callback(error) + } + return ResourceStateManager.checkResourceFiles( + resourceList, + allFiles, + basePath, + function (error) { + if (error != null) { + return callback(error) + } + return ResourceWriter.saveIncrementalResourcesToDisk( + request.project_id, + request.resources, + basePath, + function (error) { + if (error != null) { + return callback(error) + } + return callback(null, resourceList) + } + ) + } + ) + } + ) + } + ) + } else { + logger.log( + { project_id: request.project_id, user_id: request.user_id }, + 'full sync' + ) + return this.saveAllResourcesToDisk( + request.project_id, + request.resources, + basePath, + function (error) { + if (error != null) { + return callback(error) + } + return ResourceStateManager.saveProjectState( + request.syncState, + request.resources, + basePath, + function (error) { + if (error != null) { + return callback(error) + } + return callback(null, request.resources) + } + ) + } + ) + } + }, + + saveIncrementalResourcesToDisk(project_id, resources, basePath, callback) { + if (callback == null) { + callback = function (error) {} + } + return this._createDirectory(basePath, error => { + if (error != null) { + return callback(error) + } + const jobs = Array.from(resources).map(resource => + (resource => { + return callback => + this._writeResourceToDisk(project_id, resource, basePath, callback) + })(resource) + ) + return async.parallelLimit(jobs, parallelFileDownloads, callback) + }) + }, + + saveAllResourcesToDisk(project_id, resources, basePath, callback) { + if (callback == null) { + callback = function (error) {} + } + return this._createDirectory(basePath, error => { + if (error != null) { + return callback(error) + } + return this._removeExtraneousFiles(resources, basePath, error => { + if (error != null) { + return callback(error) + } + const jobs = Array.from(resources).map(resource => + (resource => { + return callback => + this._writeResourceToDisk( + project_id, + resource, + basePath, + callback + ) + })(resource) + ) + return async.parallelLimit(jobs, parallelFileDownloads, callback) + }) + }) + }, + + _createDirectory(basePath, callback) { + if (callback == null) { + callback = function (error) {} + } + return fs.mkdir(basePath, function (err) { + if (err != null) { + if (err.code === 'EEXIST') { + return callback() + } else { + logger.log({ err, dir: basePath }, 'error creating directory') + return callback(err) + } + } else { + return callback() + } + }) + }, + + _removeExtraneousFiles(resources, basePath, _callback) { + if (_callback == null) { + _callback = function (error, outputFiles, allFiles) {} + } + const timer = new Metrics.Timer('unlink-output-files') + const callback = function (error, ...result) { + timer.done() + return _callback(error, ...Array.from(result)) + } + + return OutputFileFinder.findOutputFiles( + resources, + basePath, + function (error, outputFiles, allFiles) { + if (error != null) { + return callback(error) + } + + const jobs = [] + for (const file of Array.from(outputFiles || [])) { + ;(function (file) { + const { path } = file + let should_delete = true + if ( + path.match(/^output\./) || + path.match(/\.aux$/) || + path.match(/^cache\//) + ) { + // knitr cache + should_delete = false + } + if (path.match(/^output-.*/)) { + // Tikz cached figures (default case) + should_delete = false + } + if (path.match(/\.(pdf|dpth|md5)$/)) { + // Tikz cached figures (by extension) + should_delete = false + } + if ( + path.match(/\.(pygtex|pygstyle)$/) || + path.match(/(^|\/)_minted-[^\/]+\//) + ) { + // minted files/directory + should_delete = false + } + if ( + path.match(/\.md\.tex$/) || + path.match(/(^|\/)_markdown_[^\/]+\//) + ) { + // markdown files/directory + should_delete = false + } + if (path.match(/-eps-converted-to\.pdf$/)) { + // Epstopdf generated files + should_delete = false + } + if ( + path === 'output.pdf' || + path === 'output.dvi' || + path === 'output.log' || + path === 'output.xdv' || + path === 'output.stdout' || + path === 'output.stderr' + ) { + should_delete = true + } + if (path === 'output.tex') { + // created by TikzManager if present in output files + should_delete = true + } + if (should_delete) { + return jobs.push(callback => + ResourceWriter._deleteFileIfNotDirectory( + Path.join(basePath, path), + callback + ) + ) + } + })(file) + } + + return async.series(jobs, function (error) { + if (error != null) { + return callback(error) + } + return callback(null, outputFiles, allFiles) + }) + } + ) + }, + + _deleteFileIfNotDirectory(path, callback) { + if (callback == null) { + callback = function (error) {} + } + return fs.stat(path, function (error, stat) { + if (error != null && error.code === 'ENOENT') { + return callback() + } else if (error != null) { + logger.err( + { err: error, path }, + 'error stating file in deleteFileIfNotDirectory' + ) + return callback(error) + } else if (stat.isFile()) { + return fs.unlink(path, function (error) { + if (error != null) { + logger.err( + { err: error, path }, + 'error removing file in deleteFileIfNotDirectory' + ) + return callback(error) + } else { + return callback() + } + }) + } else { + return callback() + } + }) + }, + + _writeResourceToDisk(project_id, resource, basePath, callback) { + if (callback == null) { + callback = function (error) {} + } + return ResourceWriter.checkPath( + basePath, + resource.path, + function (error, path) { + if (error != null) { + return callback(error) + } + return fs.mkdir( + Path.dirname(path), + { recursive: true }, + function (error) { + if (error != null) { + return callback(error) + } + // TODO: Don't overwrite file if it hasn't been modified + if (resource.url != null) { + return UrlCache.downloadUrlToFile( + project_id, + resource.url, + path, + resource.modified, + function (err) { + if (err != null) { + logger.err( + { + err, + project_id, + path, + resource_url: resource.url, + modified: resource.modified, + }, + 'error downloading file for resources' + ) + Metrics.inc('download-failed') + } + return callback() + } + ) // try and continue compiling even if http resource can not be downloaded at this time + } else { + fs.writeFile(path, resource.content, callback) + } + } + ) + } + ) + }, + + checkPath(basePath, resourcePath, callback) { + const path = Path.normalize(Path.join(basePath, resourcePath)) + if (path.slice(0, basePath.length + 1) !== basePath + '/') { + return callback(new Error('resource path is outside root directory')) + } else { + return callback(null, path) + } + }, +} diff --git a/services/clsi/app/js/SafeReader.js b/services/clsi/app/js/SafeReader.js new file mode 100644 index 0000000000..760a7725b9 --- /dev/null +++ b/services/clsi/app/js/SafeReader.js @@ -0,0 +1,63 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, + node/no-deprecated-api, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let SafeReader +const fs = require('fs') +const logger = require('logger-sharelatex') + +module.exports = SafeReader = { + // safely read up to size bytes from a file and return result as a + // string + + readFile(file, size, encoding, callback) { + if (callback == null) { + callback = function (error, result) {} + } + return fs.open(file, 'r', function (err, fd) { + if (err != null && err.code === 'ENOENT') { + return callback() + } + if (err != null) { + return callback(err) + } + + // safely return always closing the file + const callbackWithClose = (err, ...result) => + fs.close(fd, function (err1) { + if (err != null) { + return callback(err) + } + if (err1 != null) { + return callback(err1) + } + return callback(null, ...Array.from(result)) + }) + const buff = Buffer.alloc(size) // fills with zeroes by default + return fs.read( + fd, + buff, + 0, + buff.length, + 0, + function (err, bytesRead, buffer) { + if (err != null) { + return callbackWithClose(err) + } + const result = buffer.toString(encoding, 0, bytesRead) + return callbackWithClose(null, result, bytesRead) + } + ) + }) + }, +} diff --git a/services/clsi/app/js/StaticServerForbidSymlinks.js b/services/clsi/app/js/StaticServerForbidSymlinks.js new file mode 100644 index 0000000000..f810d2b8ab --- /dev/null +++ b/services/clsi/app/js/StaticServerForbidSymlinks.js @@ -0,0 +1,94 @@ +/* eslint-disable + camelcase, + no-cond-assign, + no-unused-vars, + node/no-deprecated-api, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let ForbidSymlinks +const Path = require('path') +const fs = require('fs') +const Settings = require('@overleaf/settings') +const logger = require('logger-sharelatex') +const url = require('url') + +module.exports = ForbidSymlinks = function (staticFn, root, options) { + const expressStatic = staticFn(root, options) + const basePath = Path.resolve(root) + return function (req, res, next) { + let file, project_id, result + const path = __guard__(url.parse(req.url), x => x.pathname) + // check that the path is of the form /project_id_or_name/path/to/file.log + if ((result = path.match(/^\/?([a-zA-Z0-9_-]+)\/(.*)/))) { + project_id = result[1] + file = result[2] + } else { + logger.warn({ path }, 'unrecognized file request') + return res.sendStatus(404) + } + // check that the file does not use a relative path + for (const dir of Array.from(file.split('/'))) { + if (dir === '..') { + logger.warn({ path }, 'attempt to use a relative path') + return res.sendStatus(404) + } + } + // check that the requested path is normalized + const requestedFsPath = `${basePath}/${project_id}/${file}` + if (requestedFsPath !== Path.normalize(requestedFsPath)) { + logger.error( + { path: requestedFsPath }, + 'requestedFsPath is not normalized' + ) + return res.sendStatus(404) + } + // check that the requested path is not a symlink + return fs.realpath(requestedFsPath, function (err, realFsPath) { + if (err != null) { + if (err.code === 'ENOENT') { + return res.sendStatus(404) + } else { + logger.error( + { + err, + requestedFsPath, + realFsPath, + path: req.params[0], + project_id: req.params.project_id, + }, + 'error checking file access' + ) + return res.sendStatus(500) + } + } else if (requestedFsPath !== realFsPath) { + logger.warn( + { + requestedFsPath, + realFsPath, + path: req.params[0], + project_id: req.params.project_id, + }, + 'trying to access a different file (symlink), aborting' + ) + return res.sendStatus(404) + } else { + return expressStatic(req, res, next) + } + }) + } +} + +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/app/js/TikzManager.js b/services/clsi/app/js/TikzManager.js new file mode 100644 index 0000000000..ac62ddac5a --- /dev/null +++ b/services/clsi/app/js/TikzManager.js @@ -0,0 +1,101 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let TikzManager +const fs = require('fs') +const Path = require('path') +const ResourceWriter = require('./ResourceWriter') +const SafeReader = require('./SafeReader') +const logger = require('logger-sharelatex') + +// for \tikzexternalize or pstool to work the main file needs to match the +// jobname. Since we set the -jobname to output, we have to create a +// copy of the main file as 'output.tex'. + +module.exports = TikzManager = { + checkMainFile(compileDir, mainFile, resources, callback) { + // if there's already an output.tex file, we don't want to touch it + if (callback == null) { + callback = function (error, needsMainFile) {} + } + for (const resource of Array.from(resources)) { + if (resource.path === 'output.tex') { + logger.log({ compileDir, mainFile }, 'output.tex already in resources') + return callback(null, false) + } + } + // if there's no output.tex, see if we are using tikz/pgf or pstool in the main file + return ResourceWriter.checkPath( + compileDir, + mainFile, + function (error, path) { + if (error != null) { + return callback(error) + } + return SafeReader.readFile( + path, + 65536, + 'utf8', + function (error, content) { + if (error != null) { + return callback(error) + } + const usesTikzExternalize = + (content != null + ? content.indexOf('\\tikzexternalize') + : undefined) >= 0 + const usesPsTool = + (content != null ? content.indexOf('{pstool}') : undefined) >= 0 + logger.log( + { compileDir, mainFile, usesTikzExternalize, usesPsTool }, + 'checked for packages needing main file as output.tex' + ) + const needsMainFile = usesTikzExternalize || usesPsTool + return callback(null, needsMainFile) + } + ) + } + ) + }, + + injectOutputFile(compileDir, mainFile, callback) { + if (callback == null) { + callback = function (error) {} + } + return ResourceWriter.checkPath( + compileDir, + mainFile, + function (error, path) { + if (error != null) { + return callback(error) + } + return fs.readFile(path, 'utf8', function (error, content) { + if (error != null) { + return callback(error) + } + logger.log( + { compileDir, mainFile }, + 'copied file to output.tex as project uses packages which require it' + ) + // use wx flag to ensure that output file does not already exist + return fs.writeFile( + Path.join(compileDir, 'output.tex'), + content, + { flag: 'wx' }, + callback + ) + }) + } + ) + }, +} diff --git a/services/clsi/app/js/UrlCache.js b/services/clsi/app/js/UrlCache.js new file mode 100644 index 0000000000..70535b6155 --- /dev/null +++ b/services/clsi/app/js/UrlCache.js @@ -0,0 +1,281 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let UrlCache +const db = require('./db') +const dbQueue = require('./DbQueue') +const UrlFetcher = require('./UrlFetcher') +const Settings = require('@overleaf/settings') +const crypto = require('crypto') +const fs = require('fs') +const logger = require('logger-sharelatex') +const async = require('async') +const Metrics = require('./Metrics') + +module.exports = UrlCache = { + downloadUrlToFile(project_id, url, destPath, lastModified, callback) { + if (callback == null) { + callback = function (error) {} + } + return UrlCache._ensureUrlIsInCache( + project_id, + url, + lastModified, + (error, pathToCachedUrl) => { + if (error != null) { + return callback(error) + } + return fs.copyFile(pathToCachedUrl, destPath, function (error) { + if (error != null) { + logger.error( + { err: error, from: pathToCachedUrl, to: destPath }, + 'error copying file from cache' + ) + return UrlCache._clearUrlDetails(project_id, url, () => + callback(error) + ) + } else { + return callback(error) + } + }) + } + ) + }, + + clearProject(project_id, callback) { + if (callback == null) { + callback = function (error) {} + } + return UrlCache._findAllUrlsInProject(project_id, function (error, urls) { + logger.log( + { project_id, url_count: urls.length }, + 'clearing project URLs' + ) + if (error != null) { + return callback(error) + } + const jobs = Array.from(urls || []).map(url => + ( + url => callback => + UrlCache._clearUrlFromCache(project_id, url, function (error) { + if (error != null) { + logger.error( + { err: error, project_id, url }, + 'error clearing project URL' + ) + } + return callback() + }) + )(url) + ) + return async.series(jobs, callback) + }) + }, + + _ensureUrlIsInCache(project_id, url, lastModified, callback) { + if (callback == null) { + callback = function (error, pathOnDisk) {} + } + if (lastModified != null) { + // MYSQL only stores dates to an accuracy of a second but the incoming lastModified might have milliseconds. + // So round down to seconds + lastModified = new Date(Math.floor(lastModified.getTime() / 1000) * 1000) + } + return UrlCache._doesUrlNeedDownloading( + project_id, + url, + lastModified, + (error, needsDownloading) => { + if (error != null) { + return callback(error) + } + if (needsDownloading) { + logger.log({ url, lastModified }, 'downloading URL') + return UrlFetcher.pipeUrlToFileWithRetry( + url, + UrlCache._cacheFilePathForUrl(project_id, url), + error => { + if (error != null) { + return callback(error) + } + return UrlCache._updateOrCreateUrlDetails( + project_id, + url, + lastModified, + error => { + if (error != null) { + return callback(error) + } + return callback( + null, + UrlCache._cacheFilePathForUrl(project_id, url) + ) + } + ) + } + ) + } else { + logger.log({ url, lastModified }, 'URL is up to date in cache') + return callback(null, UrlCache._cacheFilePathForUrl(project_id, url)) + } + } + ) + }, + + _doesUrlNeedDownloading(project_id, url, lastModified, callback) { + if (callback == null) { + callback = function (error, needsDownloading) {} + } + if (lastModified == null) { + return callback(null, true) + } + return UrlCache._findUrlDetails( + project_id, + url, + function (error, urlDetails) { + if (error != null) { + return callback(error) + } + if ( + urlDetails == null || + urlDetails.lastModified == null || + urlDetails.lastModified.getTime() < lastModified.getTime() + ) { + return callback(null, true) + } else { + return callback(null, false) + } + } + ) + }, + + _cacheFileNameForUrl(project_id, url) { + return project_id + ':' + crypto.createHash('md5').update(url).digest('hex') + }, + + _cacheFilePathForUrl(project_id, url) { + return `${Settings.path.clsiCacheDir}/${UrlCache._cacheFileNameForUrl( + project_id, + url + )}` + }, + + _clearUrlFromCache(project_id, url, callback) { + if (callback == null) { + callback = function (error) {} + } + return UrlCache._clearUrlDetails(project_id, url, function (error) { + if (error != null) { + return callback(error) + } + return UrlCache._deleteUrlCacheFromDisk( + project_id, + url, + function (error) { + if (error != null) { + return callback(error) + } + return callback(null) + } + ) + }) + }, + + _deleteUrlCacheFromDisk(project_id, url, callback) { + if (callback == null) { + callback = function (error) {} + } + return fs.unlink( + UrlCache._cacheFilePathForUrl(project_id, url), + function (error) { + if (error != null && error.code !== 'ENOENT') { + // no error if the file isn't present + return callback(error) + } else { + return callback() + } + } + ) + }, + + _findUrlDetails(project_id, url, callback) { + if (callback == null) { + callback = function (error, urlDetails) {} + } + const timer = new Metrics.Timer('db-find-url-details') + const job = cb => + db.UrlCache.findOne({ where: { url, project_id } }) + .then(urlDetails => cb(null, urlDetails)) + .error(cb) + dbQueue.queue.push(job, (error, urlDetails) => { + timer.done() + callback(error, urlDetails) + }) + }, + + _updateOrCreateUrlDetails(project_id, url, lastModified, callback) { + if (callback == null) { + callback = function (error) {} + } + const timer = new Metrics.Timer('db-update-or-create-url-details') + const job = cb => + db.UrlCache.findOrCreate({ where: { url, project_id } }) + .spread((urlDetails, created) => + urlDetails + .update({ lastModified }) + .then(() => cb()) + .error(cb) + ) + .error(cb) + dbQueue.queue.push(job, error => { + timer.done() + callback(error) + }) + }, + + _clearUrlDetails(project_id, url, callback) { + if (callback == null) { + callback = function (error) {} + } + const timer = new Metrics.Timer('db-clear-url-details') + const job = cb => + db.UrlCache.destroy({ where: { url, project_id } }) + .then(() => cb(null)) + .error(cb) + dbQueue.queue.push(job, error => { + timer.done() + callback(error) + }) + }, + + _findAllUrlsInProject(project_id, callback) { + if (callback == null) { + callback = function (error, urls) {} + } + const timer = new Metrics.Timer('db-find-urls-in-project') + const job = cb => + db.UrlCache.findAll({ where: { project_id } }) + .then(urlEntries => + cb( + null, + urlEntries.map(entry => entry.url) + ) + ) + .error(cb) + dbQueue.queue.push(job, (err, urls) => { + timer.done() + callback(err, urls) + }) + }, +} diff --git a/services/clsi/app/js/UrlFetcher.js b/services/clsi/app/js/UrlFetcher.js new file mode 100644 index 0000000000..4d3c3d5ebe --- /dev/null +++ b/services/clsi/app/js/UrlFetcher.js @@ -0,0 +1,131 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, + no-unused-vars, + node/no-deprecated-api, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let UrlFetcher +const request = require('request').defaults({ jar: false }) +const fs = require('fs') +const logger = require('logger-sharelatex') +const settings = require('@overleaf/settings') +const URL = require('url') +const async = require('async') + +const oneMinute = 60 * 1000 + +module.exports = UrlFetcher = { + pipeUrlToFileWithRetry(url, filePath, callback) { + const doDownload = function (cb) { + UrlFetcher.pipeUrlToFile(url, filePath, cb) + } + async.retry(3, doDownload, callback) + }, + + pipeUrlToFile(url, filePath, _callback) { + if (_callback == null) { + _callback = function (error) {} + } + const callbackOnce = function (error) { + if (timeoutHandler != null) { + clearTimeout(timeoutHandler) + } + _callback(error) + return (_callback = function () {}) + } + + const u = URL.parse(url) + if ( + settings.filestoreDomainOveride && + u.host !== settings.apis.clsiPerf.host + ) { + url = `${settings.filestoreDomainOveride}${u.path}` + } + var timeoutHandler = setTimeout( + function () { + timeoutHandler = null + logger.error({ url, filePath }, 'Timed out downloading file to cache') + return callbackOnce( + new Error(`Timed out downloading file to cache ${url}`) + ) + }, + // FIXME: maybe need to close fileStream here + 3 * oneMinute + ) + + logger.log({ url, filePath }, 'started downloading url to cache') + const urlStream = request.get({ url, timeout: oneMinute }) + urlStream.pause() // stop data flowing until we are ready + + // attach handlers before setting up pipes + urlStream.on('error', function (error) { + logger.error({ err: error, url, filePath }, 'error downloading url') + return callbackOnce( + error || new Error(`Something went wrong downloading the URL ${url}`) + ) + }) + + urlStream.on('end', () => + logger.log({ url, filePath }, 'finished downloading file into cache') + ) + + return urlStream.on('response', function (res) { + if (res.statusCode >= 200 && res.statusCode < 300) { + const fileStream = fs.createWriteStream(filePath) + + // attach handlers before setting up pipes + fileStream.on('error', function (error) { + logger.error( + { err: error, url, filePath }, + 'error writing file into cache' + ) + return fs.unlink(filePath, function (err) { + if (err != null) { + logger.err({ err, filePath }, 'error deleting file from cache') + } + return callbackOnce(error) + }) + }) + + fileStream.on('finish', function () { + logger.log({ url, filePath }, 'finished writing file into cache') + return callbackOnce() + }) + + fileStream.on('pipe', () => + logger.log({ url, filePath }, 'piping into filestream') + ) + + urlStream.pipe(fileStream) + return urlStream.resume() // now we are ready to handle the data + } else { + logger.error( + { statusCode: res.statusCode, url, filePath }, + 'unexpected status code downloading url to cache' + ) + // https://nodejs.org/api/http.html#http_class_http_clientrequest + // If you add a 'response' event handler, then you must consume + // the data from the response object, either by calling + // response.read() whenever there is a 'readable' event, or by + // adding a 'data' handler, or by calling the .resume() + // method. Until the data is consumed, the 'end' event will not + // fire. Also, until the data is read it will consume memory + // that can eventually lead to a 'process out of memory' error. + urlStream.resume() // discard the data + return callbackOnce( + new Error( + `URL returned non-success status code: ${res.statusCode} ${url}` + ) + ) + } + }) + }, +} diff --git a/services/clsi/app/js/db.js b/services/clsi/app/js/db.js new file mode 100644 index 0000000000..856f1da08b --- /dev/null +++ b/services/clsi/app/js/db.js @@ -0,0 +1,67 @@ +/* eslint-disable + no-console, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Sequelize = require('sequelize') +const Settings = require('@overleaf/settings') +const _ = require('lodash') +const logger = require('logger-sharelatex') + +const options = _.extend({ logging: false }, Settings.mysql.clsi) + +logger.log({ dbPath: Settings.mysql.clsi.storage }, 'connecting to db') + +const sequelize = new Sequelize( + Settings.mysql.clsi.database, + Settings.mysql.clsi.username, + Settings.mysql.clsi.password, + options +) + +if (Settings.mysql.clsi.dialect === 'sqlite') { + logger.log('running PRAGMA journal_mode=WAL;') + sequelize.query('PRAGMA journal_mode=WAL;') + sequelize.query('PRAGMA synchronous=OFF;') + sequelize.query('PRAGMA read_uncommitted = true;') +} + +module.exports = { + UrlCache: sequelize.define( + 'UrlCache', + { + url: Sequelize.STRING, + project_id: Sequelize.STRING, + lastModified: Sequelize.DATE, + }, + { + indexes: [{ fields: ['url', 'project_id'] }, { fields: ['project_id'] }], + } + ), + + Project: sequelize.define( + 'Project', + { + project_id: { type: Sequelize.STRING, primaryKey: true }, + lastAccessed: Sequelize.DATE, + }, + { + indexes: [{ fields: ['lastAccessed'] }], + } + ), + + op: Sequelize.Op, + + sync() { + logger.log({ dbPath: Settings.mysql.clsi.storage }, 'syncing db schema') + return sequelize + .sync() + .then(() => logger.log('db sync complete')) + .catch(err => console.log(err, 'error syncing')) + }, +} diff --git a/services/clsi/app/lib/pdfjs/FSPdfManager.js b/services/clsi/app/lib/pdfjs/FSPdfManager.js new file mode 100644 index 0000000000..692576b2ce --- /dev/null +++ b/services/clsi/app/lib/pdfjs/FSPdfManager.js @@ -0,0 +1,43 @@ +const { PDFDocument } = require('pdfjs-dist/lib/core/document') +const { LocalPdfManager } = require('pdfjs-dist/lib/core/pdf_manager') +const { MissingDataException } = require('pdfjs-dist/lib/core/core_utils') +const { FSStream } = require('./FSStream') + +class FSPdfManager extends LocalPdfManager { + constructor(docId, { fh, size, checkDeadline }) { + const nonEmptyDummyBuffer = Buffer.alloc(1, 0) + super(docId, nonEmptyDummyBuffer) + this.stream = new FSStream(fh, 0, size, null, null, checkDeadline) + this.pdfDocument = new PDFDocument(this, this.stream) + } + + async ensure(obj, prop, args) { + try { + const value = obj[prop] + if (typeof value === 'function') { + return value.apply(obj, args) + } + return value + } catch (ex) { + if (!(ex instanceof MissingDataException)) { + throw ex + } + await this.requestRange(ex.begin, ex.end) + return this.ensure(obj, prop, args) + } + } + + requestRange(begin, end) { + return this.stream.requestRange(begin, end) + } + + requestLoadedStream() {} + + onLoadedStream() {} + + terminate(reason) {} +} + +module.exports = { + FSPdfManager, +} diff --git a/services/clsi/app/lib/pdfjs/FSStream.js b/services/clsi/app/lib/pdfjs/FSStream.js new file mode 100644 index 0000000000..e3e3ac0243 --- /dev/null +++ b/services/clsi/app/lib/pdfjs/FSStream.js @@ -0,0 +1,148 @@ +const { Stream } = require('pdfjs-dist/lib/core/stream') +const { MissingDataException } = require('pdfjs-dist/lib/core/core_utils') + +const BUF_SIZE = 1024 // read from the file in 1024 byte pages + +class FSStream extends Stream { + constructor(fh, start, length, dict, cachedBytes, checkDeadline) { + const nonEmptyDummyBuffer = Buffer.alloc(1, 0) + super(nonEmptyDummyBuffer, start, length, dict) + delete this.bytes + this.fh = fh + this.checkDeadline = checkDeadline + this.cachedBytes = cachedBytes || [] + } + + get length() { + return this.end - this.start + } + + get isEmpty() { + return this.length === 0 + } + + // Manage cached reads from the file + + requestRange(begin, end) { + this.checkDeadline(`request range ${begin} - ${end}`) + // expand small ranges to read a larger amount + if (end - begin < BUF_SIZE) { + end = begin + BUF_SIZE + } + end = Math.min(end, this.length) + // keep a cache of previous reads with {begin,end,buffer} values + const result = { + begin: begin, + end: end, + buffer: Buffer.alloc(end - begin, 0), + } + this.cachedBytes.push(result) + return this.fh.read(result.buffer, 0, end - begin, begin) + } + + _ensureGetPos(pos) { + const found = this.cachedBytes.find(x => { + return x.begin <= pos && pos < x.end + }) + if (!found) { + throw new MissingDataException(pos, pos + 1) + } + return found + } + + _ensureGetRange(begin, end) { + end = Math.min(end, this.length) // BG: handle overflow case + const found = this.cachedBytes.find(x => { + return x.begin <= begin && end <= x.end + }) + if (!found) { + throw new MissingDataException(begin, end) + } + return found + } + + _readByte(found, pos) { + return found.buffer[pos - found.begin] + } + + _readBytes(found, pos, end) { + return found.buffer.subarray(pos - found.begin, end - found.begin) + } + + // handle accesses to the bytes + + ensureByte(pos) { + this._ensureGetPos(pos) // may throw a MissingDataException + } + + getByte() { + const pos = this.pos + if (this.pos >= this.end) { + return -1 + } + const found = this._ensureGetPos(pos) + return this._readByte(found, this.pos++) + } + + // BG: for a range, end is not included (see Buffer.subarray for example) + + ensureBytes(length, forceClamped = false) { + const pos = this.pos + this._ensureGetRange(pos, pos + length) + } + + getBytes(length, forceClamped = false) { + const pos = this.pos + const strEnd = this.end + + const found = this._ensureGetRange(pos, pos + length) + if (!length) { + const subarray = this._readBytes(found, pos, strEnd) + // `this.bytes` is always a `Uint8Array` here. + return forceClamped ? new Uint8ClampedArray(subarray) : subarray + } + let end = pos + length + if (end > strEnd) { + end = strEnd + } + this.pos = end + const subarray = this._readBytes(found, pos, end) + // `this.bytes` is always a `Uint8Array` here. + return forceClamped ? new Uint8ClampedArray(subarray) : subarray + } + + getByteRange() { + // BG: this isn't needed as far as I can tell + throw new Error('not implemented') + } + + reset() { + this.pos = this.start + } + + moveStart() { + this.start = this.pos + } + + makeSubStream(start, length, dict = null) { + this.checkDeadline(`make sub stream start=${start}/length=${length}`) + // BG: had to add this check for null length, it is being called with only + // the start value at one point in the xref decoding. The intent is clear + // enough + // - a null length means "to the end of the file" -- not sure how it is + // working in the existing pdfjs code without this. + if (!length) { + length = this.end - start + } + return new FSStream( + this.fh, + start, + length, + dict, + this.cachedBytes, + this.checkDeadline + ) + } +} + +module.exports = { FSStream } diff --git a/services/clsi/app/lib/pdfjs/parseXrefTable.js b/services/clsi/app/lib/pdfjs/parseXrefTable.js new file mode 100644 index 0000000000..1ecfd2397d --- /dev/null +++ b/services/clsi/app/lib/pdfjs/parseXrefTable.js @@ -0,0 +1,27 @@ +const fs = require('fs') +const { FSPdfManager } = require('./FSPdfManager') + +async function parseXrefTable(path, size, checkDeadline) { + if (size === 0) { + return [] + } + + const file = await fs.promises.open(path) + try { + const manager = new FSPdfManager(0, { fh: file, size, checkDeadline }) + + await manager.ensureDoc('checkHeader') + checkDeadline('pdfjs: after checkHeader') + await manager.ensureDoc('parseStartXRef') + checkDeadline('pdfjs: after parseStartXRef') + await manager.ensureDoc('parse') + checkDeadline('pdfjs: after parse') + return manager.pdfDocument.catalog.xref.entries + } finally { + file.close() + } +} + +module.exports = { + parseXrefTable, +} diff --git a/services/clsi/bin/.gitignore b/services/clsi/bin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/services/clsi/bin/acceptance_test b/services/clsi/bin/acceptance_test new file mode 100644 index 0000000000..fd2e5137b5 --- /dev/null +++ b/services/clsi/bin/acceptance_test @@ -0,0 +1,4 @@ +#!/bin/bash +set -e; +MOCHA="node_modules/.bin/mocha --recursive --reporter spec --timeout 15000" +$MOCHA "$@" diff --git a/services/clsi/bin/synctex b/services/clsi/bin/synctex new file mode 100755 index 0000000000..89b8cc6988 Binary files /dev/null and b/services/clsi/bin/synctex differ diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt new file mode 100644 index 0000000000..9b755dfe46 --- /dev/null +++ b/services/clsi/buildscript.txt @@ -0,0 +1,9 @@ +clsi +--data-dirs=cache,compiles,db,output +--dependencies= +--docker-repos=gcr.io/overleaf-ops +--env-add=ENABLE_PDF_CACHING="true" +--env-pass-through=TEXLIVE_IMAGE +--node-version=12.22.3 +--public-repo=True +--script-version=3.11.0 diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js new file mode 100644 index 0000000000..e36334df1b --- /dev/null +++ b/services/clsi/config/settings.defaults.js @@ -0,0 +1,170 @@ +const Path = require('path') + +module.exports = { + // Options are passed to Sequelize. + // See http://sequelizejs.com/documentation#usage-options for details + mysql: { + clsi: { + database: 'clsi', + username: 'clsi', + dialect: 'sqlite', + storage: + process.env.SQLITE_PATH || Path.resolve(__dirname, '../db/db.sqlite'), + pool: { + max: 1, + min: 1, + }, + retry: { + max: 10, + }, + }, + }, + + compileSizeLimit: process.env.COMPILE_SIZE_LIMIT || '7mb', + + processLifespanLimitMs: + parseInt(process.env.PROCESS_LIFE_SPAN_LIMIT_MS) || 60 * 60 * 24 * 1000 * 2, + + catchErrors: process.env.CATCH_ERRORS === 'true', + + path: { + compilesDir: Path.resolve(__dirname, '../compiles'), + outputDir: Path.resolve(__dirname, '../output'), + clsiCacheDir: Path.resolve(__dirname, '../cache'), + synctexBaseDir(projectId) { + return Path.join(this.compilesDir, projectId) + }, + }, + + internal: { + clsi: { + port: 3013, + host: process.env.LISTEN_ADDRESS || 'localhost', + }, + + load_balancer_agent: { + report_load: true, + load_port: 3048, + local_port: 3049, + }, + }, + apis: { + clsi: { + url: `http://${process.env.CLSI_HOST || 'localhost'}:3013`, + }, + clsiPerf: { + host: `${process.env.CLSI_PERF_HOST || 'localhost'}:${ + process.env.CLSI_PERF_PORT || '3043' + }`, + }, + }, + + smokeTest: process.env.SMOKE_TEST || false, + project_cache_length_ms: 1000 * 60 * 60 * 24, + parallelFileDownloads: process.env.FILESTORE_PARALLEL_FILE_DOWNLOADS || 1, + parallelSqlQueryLimit: process.env.FILESTORE_PARALLEL_SQL_QUERY_LIMIT || 1, + filestoreDomainOveride: process.env.FILESTORE_DOMAIN_OVERRIDE, + texliveImageNameOveride: process.env.TEX_LIVE_IMAGE_NAME_OVERRIDE, + texliveOpenoutAny: process.env.TEXLIVE_OPENOUT_ANY, + sentry: { + dsn: process.env.SENTRY_DSN, + }, + + enablePdfCaching: process.env.ENABLE_PDF_CACHING === 'true', + enablePdfCachingDark: process.env.ENABLE_PDF_CACHING_DARK === 'true', + pdfCachingMinChunkSize: + parseInt(process.env.PDF_CACHING_MIN_CHUNK_SIZE, 10) || 1024, + pdfCachingMaxProcessingTime: + parseInt(process.env.PDF_CACHING_MAX_PROCESSING_TIME, 10) || 10 * 1000, +} + +if (process.env.ALLOWED_COMPILE_GROUPS) { + try { + module.exports.allowedCompileGroups = + process.env.ALLOWED_COMPILE_GROUPS.split(' ') + } catch (error) { + console.error(error, 'could not apply allowed compile group setting') + process.exit(1) + } +} + +if (process.env.DOCKER_RUNNER) { + let seccompProfilePath + module.exports.clsi = { + dockerRunner: process.env.DOCKER_RUNNER === 'true', + docker: { + runtime: process.env.DOCKER_RUNTIME, + image: + process.env.TEXLIVE_IMAGE || 'quay.io/sharelatex/texlive-full:2017.1', + env: { + HOME: '/tmp', + }, + socketPath: '/var/run/docker.sock', + user: process.env.TEXLIVE_IMAGE_USER || 'tex', + }, + optimiseInDocker: true, + expireProjectAfterIdleMs: 24 * 60 * 60 * 1000, + checkProjectsIntervalMs: 10 * 60 * 1000, + } + + try { + // Override individual docker settings using path-based keys, e.g.: + // compileGroupDockerConfigs = { + // priority: { 'HostConfig.CpuShares': 100 } + // beta: { 'dotted.path.here', 'value'} + // } + const compileGroupConfig = JSON.parse( + process.env.COMPILE_GROUP_DOCKER_CONFIGS || '{}' + ) + // Automatically clean up wordcount and synctex containers + const defaultCompileGroupConfig = { + wordcount: { 'HostConfig.AutoRemove': true }, + synctex: { 'HostConfig.AutoRemove': true }, + } + module.exports.clsi.docker.compileGroupConfig = Object.assign( + defaultCompileGroupConfig, + compileGroupConfig + ) + } catch (error) { + console.error(error, 'could not apply compile group docker configs') + process.exit(1) + } + + try { + seccompProfilePath = Path.resolve(__dirname, '../seccomp/clsi-profile.json') + module.exports.clsi.docker.seccomp_profile = JSON.stringify( + JSON.parse(require('fs').readFileSync(seccompProfilePath)) + ) + } catch (error) { + console.error( + error, + `could not load seccomp profile from ${seccompProfilePath}` + ) + process.exit(1) + } + + if (process.env.APPARMOR_PROFILE) { + try { + module.exports.clsi.docker.apparmor_profile = process.env.APPARMOR_PROFILE + } catch (error) { + console.error(error, 'could not apply apparmor profile setting') + process.exit(1) + } + } + + if (process.env.ALLOWED_IMAGES) { + try { + module.exports.clsi.docker.allowedImages = + process.env.ALLOWED_IMAGES.split(' ') + } catch (error) { + console.error(error, 'could not apply allowed images setting') + process.exit(1) + } + } + + module.exports.path.synctexBaseDir = () => '/compile' + + module.exports.path.sandboxedCompilesHostDir = process.env.COMPILES_HOST_DIR + + module.exports.path.synctexBinHostPath = process.env.SYNCTEX_BIN_HOST_PATH +} diff --git a/services/clsi/db/.gitignore b/services/clsi/db/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/services/clsi/db/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/services/clsi/debug b/services/clsi/debug new file mode 100755 index 0000000000..fcc371c36e --- /dev/null +++ b/services/clsi/debug @@ -0,0 +1,5 @@ +#!/bin/bash +echo "hello world" +sleep 3 +echo "awake" +/opt/synctex pdf /compile/output.pdf 1 100 200 diff --git a/services/clsi/docker-compose-config.yml b/services/clsi/docker-compose-config.yml new file mode 100644 index 0000000000..0c141eaa18 --- /dev/null +++ b/services/clsi/docker-compose-config.yml @@ -0,0 +1,34 @@ +version: "2.3" + +services: + dev: + environment: + ALLOWED_IMAGES: "quay.io/sharelatex/texlive-full:2017.1" + TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 + TEXLIVE_IMAGE_USER: "tex" + SHARELATEX_CONFIG: /app/config/settings.defaults.js + DOCKER_RUNNER: "true" + COMPILES_HOST_DIR: $PWD/compiles + SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./compiles:/app/compiles + - ./cache:/app/cache + - ./bin/synctex:/app/bin/synctex + + + ci: + environment: + ALLOWED_IMAGES: ${TEXLIVE_IMAGE} + TEXLIVE_IMAGE: quay.io/sharelatex/texlive-full:2017.1 + TEXLIVE_IMAGE_USER: "tex" + SHARELATEX_CONFIG: /app/config/settings.defaults.js + DOCKER_RUNNER: "true" + COMPILES_HOST_DIR: $PWD/compiles + SYNCTEX_BIN_HOST_PATH: $PWD/bin/synctex + SQLITE_PATH: /app/compiles/db.sqlite + volumes: + - /var/run/docker.sock:/var/run/docker.sock:rw + - ./compiles:/app/compiles + - ./cache:/app/cache + - ./bin/synctex:/app/bin/synctex diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml new file mode 100644 index 0000000000..245059ad98 --- /dev/null +++ b/services/clsi/docker-compose.ci.yml @@ -0,0 +1,42 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment + +version: "2.3" + +services: + test_unit: + image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + command: npm run test:unit:_run + environment: + NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" + + + test_acceptance: + build: . + image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + extends: + file: docker-compose-config.yml + service: ci + environment: + ELASTIC_SEARCH_DSN: es:9200 + REDIS_HOST: redis + QUEUES_REDIS_HOST: redis + MONGO_HOST: mongo + POSTGRES_HOST: postgres + MOCHA_GREP: ${MOCHA_GREP} + NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" + TEXLIVE_IMAGE: + ENABLE_PDF_CACHING: "true" + command: npm run test:acceptance:_run + + + tar: + build: . + image: ci/$PROJECT_NAME:$BRANCH_NAME-$BUILD_NUMBER + volumes: + - ./:/tmp/build/ + command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . + user: root diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml new file mode 100644 index 0000000000..ee68a7c0ee --- /dev/null +++ b/services/clsi/docker-compose.yml @@ -0,0 +1,43 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment + +version: "2.3" + +services: + test_unit: + build: + context: . + target: base + volumes: + - .:/app + working_dir: /app + environment: + MOCHA_GREP: ${MOCHA_GREP} + NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" + command: npm run --silent test:unit + + test_acceptance: + build: + context: . + target: base + volumes: + - .:/app + working_dir: /app + extends: + file: docker-compose-config.yml + service: dev + environment: + ELASTIC_SEARCH_DSN: es:9200 + REDIS_HOST: redis + QUEUES_REDIS_HOST: redis + MONGO_HOST: mongo + POSTGRES_HOST: postgres + MOCHA_GREP: ${MOCHA_GREP} + LOG_LEVEL: ERROR + NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" + ENABLE_PDF_CACHING: "true" + command: npm run --silent test:acceptance + diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh new file mode 100755 index 0000000000..b05a773ea1 --- /dev/null +++ b/services/clsi/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +docker --version >&2 + +# add the node user to the docker group on the host +DOCKER_GROUP=$(stat -c '%g' /var/run/docker.sock) +groupadd --non-unique --gid ${DOCKER_GROUP} dockeronhost +usermod -aG dockeronhost node + +# compatibility: initial volume setup +mkdir -p /app/cache && chown node:node /app/cache +mkdir -p /app/compiles && chown node:node /app/compiles +mkdir -p /app/db && chown node:node /app/db +mkdir -p /app/output && chown node:node /app/output + +# make synctex available for remount in compiles +cp /app/bin/synctex /app/bin/synctex-mount/synctex + +exec runuser -u node -- "$@" diff --git a/services/clsi/install_deps.sh b/services/clsi/install_deps.sh new file mode 100755 index 0000000000..f0cc386ff5 --- /dev/null +++ b/services/clsi/install_deps.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -ex + +apt-get update +apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + +curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo \ + "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \ + $(lsb_release -cs) stable" \ + > /etc/apt/sources.list.d/docker.list +apt-get update + +apt-get install -y \ + docker-ce-cli \ + poppler-utils \ + ghostscript \ + +rm -rf /var/lib/apt/lists/* diff --git a/services/clsi/kube.yaml b/services/clsi/kube.yaml new file mode 100644 index 0000000000..d3fb04291e --- /dev/null +++ b/services/clsi/kube.yaml @@ -0,0 +1,41 @@ +apiVersion: v1 +kind: Service +metadata: + name: clsi + namespace: default +spec: + type: LoadBalancer + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + run: clsi +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: clsi + namespace: default +spec: + replicas: 2 + template: + metadata: + labels: + run: clsi + spec: + containers: + - name: clsi + image: gcr.io/henry-terraform-admin/clsi + imagePullPolicy: Always + readinessProbe: + httpGet: + path: status + port: 80 + periodSeconds: 5 + initialDelaySeconds: 0 + failureThreshold: 3 + successThreshold: 1 + + + diff --git a/services/clsi/nodemon.json b/services/clsi/nodemon.json new file mode 100644 index 0000000000..e3e8817d90 --- /dev/null +++ b/services/clsi/nodemon.json @@ -0,0 +1,17 @@ +{ + "ignore": [ + ".git", + "node_modules/" + ], + "verbose": true, + "legacyWatch": true, + "execMap": { + "js": "npm run start" + }, + "watch": [ + "app/js/", + "app.js", + "config/" + ], + "ext": "js" +} diff --git a/services/clsi/package-lock.json b/services/clsi/package-lock.json new file mode 100644 index 0000000000..86789cafbc --- /dev/null +++ b/services/clsi/package-lock.json @@ -0,0 +1,6775 @@ +{ + "name": "node-clsi", + "version": "0.1.4", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@eslint/eslintrc": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + } + } + }, + "@google-cloud/common": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", + "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", + "requires": { + "@google-cloud/projectify": "^1.0.0", + "@google-cloud/promisify": "^1.0.0", + "arrify": "^2.0.0", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^5.5.0", + "retry-request": "^4.0.0", + "teeny-request": "^6.0.0" + }, + "dependencies": { + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + } + } + } + }, + "@google-cloud/debug-agent": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@google-cloud/debug-agent/-/debug-agent-5.1.3.tgz", + "integrity": "sha512-WbzeEz4MvPlM7DX2QBsPcWgF62u7LSQv/oMYPl0L+TddTebqjDKiVXwxpzWk61NIfcKiet3dyCbPIt3N5o8XPQ==", + "requires": { + "@google-cloud/common": "^3.0.0", + "acorn": "^8.0.0", + "coffeescript": "^2.0.0", + "console-log-level": "^1.4.0", + "extend": "^3.0.2", + "findit2": "^2.2.3", + "gcp-metadata": "^4.0.0", + "p-limit": "^3.0.1", + "semver": "^7.0.0", + "source-map": "^0.6.1", + "split": "^1.0.0" + }, + "dependencies": { + "@google-cloud/common": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", + "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", + "requires": { + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^6.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + } + }, + "@google-cloud/projectify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==" + }, + "@google-cloud/promisify": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "gaxios": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.1.0.tgz", + "integrity": "sha512-vb0to8xzGnA2qcgywAjtshOKKVDf2eQhJoiL6fHhgW5tVN7wNk7egnYIO9zotfn3lQ3De1VPdf7V5/BWfCtCmg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", + "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "@google-cloud/logging": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-7.3.0.tgz", + "integrity": "sha512-xTW1V4MKpYC0mjSugyuiyUoZ9g6A42IhrrO3z7Tt3SmAb2IRj2Gf4RLoguKKncs340ooZFXrrVN/++t2Aj5zgg==", + "requires": { + "@google-cloud/common": "^2.2.2", + "@google-cloud/paginator": "^2.0.0", + "@google-cloud/projectify": "^1.0.0", + "@google-cloud/promisify": "^1.0.0", + "@opencensus/propagation-stackdriver": "0.0.20", + "arrify": "^2.0.0", + "dot-prop": "^5.1.0", + "eventid": "^1.0.0", + "extend": "^3.0.2", + "gcp-metadata": "^3.1.0", + "google-auth-library": "^5.2.2", + "google-gax": "^1.11.0", + "is": "^3.3.0", + "on-finished": "^2.3.0", + "pumpify": "^2.0.0", + "snakecase-keys": "^3.0.0", + "stream-events": "^1.0.4", + "through2": "^3.0.0", + "type-fest": "^0.12.0" + }, + "dependencies": { + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + } + }, + "type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==" + } + } + }, + "@google-cloud/logging-bunyan": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-3.0.0.tgz", + "integrity": "sha512-ZLVXEejNQ27ktGcA3S/sd7GPefp7kywbn+/KoBajdb1Syqcmtc98jhXpYQBXVtNP2065iyu77s4SBaiYFbTC5A==", + "requires": { + "@google-cloud/logging": "^7.0.0", + "google-auth-library": "^6.0.0" + } + }, + "@google-cloud/paginator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", + "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/profiler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-4.1.0.tgz", + "integrity": "sha512-9e1zXRctLSUHAoAsFGwE4rS28fr0siiG+jXl5OpwTK8ZAUlxb70aosHaZGdsv8YXrYKjuiufjRZ/OXCs0XLI9g==", + "requires": { + "@google-cloud/common": "^3.0.0", + "@types/console-log-level": "^1.4.0", + "@types/semver": "^7.0.0", + "console-log-level": "^1.4.0", + "delay": "^4.0.1", + "extend": "^3.0.2", + "gcp-metadata": "^4.0.0", + "parse-duration": "^0.4.4", + "pprof": "3.0.0", + "pretty-ms": "^7.0.0", + "protobufjs": "~6.10.0", + "semver": "^7.0.0", + "teeny-request": "^7.0.0" + }, + "dependencies": { + "@google-cloud/common": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", + "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", + "requires": { + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^6.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + } + }, + "@google-cloud/projectify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==" + }, + "@google-cloud/promisify": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" + }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "@types/node": { + "version": "13.13.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.42.tgz", + "integrity": "sha512-g+w2QgbW7k2CWLOXzQXbO37a7v5P9ObPvYahKphdBLV5aqpbVZRhTpWCT0SMRqX1i30Aig791ZmIM2fJGL2S8A==" + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "gaxios": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.1.0.tgz", + "integrity": "sha512-vb0to8xzGnA2qcgywAjtshOKKVDf2eQhJoiL6fHhgW5tVN7wNk7egnYIO9zotfn3lQ3De1VPdf7V5/BWfCtCmg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", + "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "protobufjs": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", + "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "@google-cloud/projectify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", + "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==" + }, + "@google-cloud/promisify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", + "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==" + }, + "@google-cloud/trace-agent": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-5.1.3.tgz", + "integrity": "sha512-f+5DX7n6QpDlHA+4kr81z69SLAdrlvd9T8skqCMgnYvtXx14AwzXZyzEDf3jppOYzYoqPPJv8XYiyYHHmYD0BA==", + "requires": { + "@google-cloud/common": "^3.0.0", + "@opencensus/propagation-stackdriver": "0.0.22", + "builtin-modules": "^3.0.0", + "console-log-level": "^1.4.0", + "continuation-local-storage": "^3.2.1", + "extend": "^3.0.2", + "gcp-metadata": "^4.0.0", + "google-auth-library": "^7.0.0", + "hex2dec": "^1.0.1", + "is": "^3.2.0", + "methods": "^1.1.1", + "require-in-the-middle": "^5.0.0", + "semver": "^7.0.0", + "shimmer": "^1.2.0", + "source-map-support": "^0.5.16", + "uuid": "^8.0.0" + }, + "dependencies": { + "@google-cloud/common": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.5.0.tgz", + "integrity": "sha512-10d7ZAvKhq47L271AqvHEd8KzJqGU45TY+rwM2Z3JHuB070FeTi7oJJd7elfrnKaEvaktw3hH2wKnRWxk/3oWQ==", + "requires": { + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^6.1.1", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + }, + "dependencies": { + "google-auth-library": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.1.6.tgz", + "integrity": "sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + } + } + }, + "@google-cloud/projectify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==" + }, + "@google-cloud/promisify": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==" + }, + "@opencensus/core": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.22.tgz", + "integrity": "sha512-ErazJtivjceNoOZI1bG9giQ6cWS45J4i6iPUtlp7dLNu58OLs/v+CD0FsaPCh47XgPxAI12vbBE8Ec09ViwHNA==", + "requires": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^7.0.0", + "shimmer": "^1.2.0", + "uuid": "^8.0.0" + } + }, + "@opencensus/propagation-stackdriver": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.22.tgz", + "integrity": "sha512-eBvf/ihb1mN8Yz/ASkz8nHzuMKqygu77+VNnUeR0yEh3Nj+ykB8VVR6lK+NAFXo1Rd1cOsTmgvuXAZgDAGleQQ==", + "requires": { + "@opencensus/core": "^0.0.22", + "hex2dec": "^1.0.1", + "uuid": "^8.0.0" + } + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" + }, + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "gaxios": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.1.0.tgz", + "integrity": "sha512-vb0to8xzGnA2qcgywAjtshOKKVDf2eQhJoiL6fHhgW5tVN7wNk7egnYIO9zotfn3lQ3De1VPdf7V5/BWfCtCmg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.0.2.tgz", + "integrity": "sha512-vjyNZR3pDLC0u7GHLfj+Hw9tGprrJwoMwkYGqURCXYITjCrP9HprOyxVV+KekdLgATtWGuDkQG2MTh0qpUPUgg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", + "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "teeny-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "@grpc/grpc-js": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz", + "integrity": "sha512-Hm+xOiqAhcpT9RYM8lc15dbQD7aQurM7ZU8ulmulepiPlN7iwBXXwP3vSBUimoFoApRqz7pSIisXU8pZaCB4og==", + "requires": { + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@grpc/proto-loader": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz", + "integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==", + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", + "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", + "dev": true + }, + "@opencensus/core": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@opencensus/core/-/core-0.0.20.tgz", + "integrity": "sha512-vqOuTd2yuMpKohp8TNNGUAPjWEGjlnGfB9Rh5e3DKqeyR94YgierNs4LbMqxKtsnwB8Dm2yoEtRuUgoe5vD9DA==", + "requires": { + "continuation-local-storage": "^3.2.1", + "log-driver": "^1.2.7", + "semver": "^6.0.0", + "shimmer": "^1.2.0", + "uuid": "^3.2.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "@opencensus/propagation-stackdriver": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.20.tgz", + "integrity": "sha512-P8yuHSLtce+yb+2EZjtTVqG7DQ48laC+IuOWi3X9q78s1Gni5F9+hmbmyP6Nb61jb5BEvXQX1s2rtRI6bayUWA==", + "requires": { + "@opencensus/core": "^0.0.20", + "hex2dec": "^1.0.1", + "uuid": "^3.2.1" + } + }, + "@overleaf/metrics": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@overleaf/metrics/-/metrics-3.5.1.tgz", + "integrity": "sha512-RLHxkMF7Y3725L3QwXo9cIn2gGobsMYUGuxKxg7PVMrPTMsomHEMeG7StOxCO7ML1Z/BwB/9nsVYNrsRdAJtKg==", + "requires": { + "@google-cloud/debug-agent": "^5.1.2", + "@google-cloud/profiler": "^4.0.3", + "@google-cloud/trace-agent": "^5.1.1", + "compression": "^1.7.4", + "prom-client": "^11.1.3", + "underscore": "~1.6.0", + "yn": "^3.1.1" + } + }, + "@overleaf/o-error": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-3.3.1.tgz", + "integrity": "sha512-1FRBYZO0lbJ0U+FRGZVS8ou6RhEw3e2B86WW/NbtBw554g0h5iC8ESf+juIfPMU/WDf/JDIFbg3eB/LnP2RSow==", + "requires": { + "core-js": "^3.8.3" + } + }, + "@overleaf/settings": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@overleaf/settings/-/settings-2.1.1.tgz", + "integrity": "sha512-vcJwqCGFKmQxTP/syUqCeMaSRjHmBcQgKOACR9He2uJcErg2GZPa1go+nGvszMbkElM4HfRKm/MfxvqHhoN4TQ==" + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@sinonjs/commons": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", + "integrity": "sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.0.tgz", + "integrity": "sha512-atR1J/jRXvQAb47gfzSK8zavXy7BcpnYq21ALon0U99etu99vsir0trzIO3wpeLtW+LLVY6X7EkfVTbjGSH8Ww==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/formatio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", + "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^5.0.2" + } + }, + "@sinonjs/samsam": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz", + "integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" + }, + "@types/console-log-level": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@types/console-log-level/-/console-log-level-1.4.0.tgz", + "integrity": "sha512-x+OscEQwcx5Biair4enH7ov9W+clcqUWaZRaxn5IkT4yNWWjRr2oiYDkY/x1uXSTVZOQ2xlbFQySaQGB+VdXGQ==" + }, + "@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "requires": { + "@types/node": "*" + } + }, + "@types/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==" + }, + "@types/node": { + "version": "10.12.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.15.tgz", + "integrity": "sha1-IOhWUbYv2GZW5Xycm8dxqxVwvFk=" + }, + "@types/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==" + }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz", + "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==" + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "requires": { + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha1-SzXClE8GKov82mZBB2A1D+nd/CE=", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-includes": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.5" + } + }, + "array.prototype.flat": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", + "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" + }, + "is-string": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==" + } + } + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "async-listener": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", + "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", + "requires": { + "semver": "^5.3.0", + "shimmer": "^1.1.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bintrees": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz", + "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" + }, + "bl": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", + "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==" + }, + "bunyan": { + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", + "requires": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "chownr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", + "integrity": "sha1-VHJri4//TfBTxCGH6AH7RBLfFJQ=" + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "wrap-ansi": "^7.0.0" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "cls-bluebird": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", + "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", + "requires": { + "is-bluebird": "^1.0.2", + "shimmer": "^1.1.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "coffeescript": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", + "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "console-log-level": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", + "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "continuation-local-storage": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", + "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", + "requires": { + "async-listener": "^0.6.0", + "emitter-listener": "^1.1.1" + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-js": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.13.0.tgz", + "integrity": "sha512-iWDbiyha1M5vFwPFmQnvRv+tJzGbFAm6XimJUT0NgHYW3xZEs1SkCAcasWSVFxpI2Xb/V1DDJckq3v90+bQnog==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "d64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d64/-/d64-1.0.0.tgz", + "integrity": "sha1-QAKofoUMv8n52XBrYPymE6MzbpA=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw=" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delay": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/delay/-/delay-4.4.1.tgz", + "integrity": "sha512-aL3AhqtfhOlT/3ai6sWXeqwnw63ATNpnUiN4HL7x9q+My5QtHlO3OIkasmug9LKzpheLdmUKGRKnYXYAS7FQkQ==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "diskusage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/diskusage/-/diskusage-1.1.3.tgz", + "integrity": "sha512-EAyaxl8hy4Ph07kzlzGTfpbZMNAAAHXSZtNEMwdlnSd1noHzvA6HsgKt4fEMSvaEXQYLSphe5rPMxN4WOj0hcQ==", + "requires": { + "es6-promise": "^4.2.5", + "nan": "^2.14.0" + } + }, + "docker-modem": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-2.1.1.tgz", + "integrity": "sha512-zSFwYN4AP38LJhTIOpZMjiDbAqSJbv8+u9i/Xq5XABIeTzgp83VF63epu6sVHWxe+6tfhMXqgV+sYjZWh/UzSQ==", + "requires": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^0.8.7" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "dockerode": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.1.0.tgz", + "integrity": "sha512-E0KknBBTlIVEvtt2XJRZ3he59u2UN8Yr1A08Sey/BKIox+WlwnJp5fL5SKyhPgNmSXgamPEuKYCJxMi31uj0Nw==", + "requires": { + "concat-stream": "~2.0.0", + "docker-modem": "^2.1.0", + "tar-fs": "~2.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "dottie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", + "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" + }, + "dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "optional": true, + "requires": { + "nan": "^2.14.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "emitter-listener": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", + "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", + "requires": { + "shimmer": "^1.2.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", + "requires": { + "once": "^1.4.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "object-inspect": "^1.10.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "7.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.30.0.tgz", + "integrity": "sha512-VLqz80i3as3NdloY44BQSJpFw534L9Oh+6zJOUaViV4JPd+DaHwutqP7tcpkW3YiXbK6s05RZl7yl7cQn+lijg==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true + }, + "eslint-config-standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-module-utils": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.1.tgz", + "integrity": "sha512-ZXI9B8cxAJIH4nfkhTwcRTEAnrVfobYqwjWy/QMCZ8rHkZHFjf9yO4BzpiF9kCSfNlMG54eKigISHpX0+AaT4A==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "eslint-plugin-chai-expect": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-expect/-/eslint-plugin-chai-expect-2.2.0.tgz", + "integrity": "sha512-ExTJKhgeYMfY8wDj3UiZmgpMKJOUHGNHmWMlxT49JUDB1vTnw0sSNfXJSxnX+LcebyBD/gudXzjzD136WqPJrQ==", + "dev": true + }, + "eslint-plugin-chai-friendly": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.6.0.tgz", + "integrity": "sha512-Uvvv1gkbRGp/qfN15B0kQyQWg+oFA8buDSqrwmW3egNSk/FpqH2MjQqKOuKwmEL6w4QIQrIjDp+gg6kGGmD3oQ==", + "dev": true + }, + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.23.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz", + "integrity": "sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.3", + "array.prototype.flat": "^1.2.4", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.1", + "find-up": "^2.0.0", + "has": "^1.0.3", + "is-core-module": "^2.4.0", + "minimatch": "^3.0.4", + "object.values": "^1.1.3", + "pkg-up": "^2.0.0", + "read-pkg-up": "^3.0.0", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.9.0" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-plugin-mocha": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-8.2.0.tgz", + "integrity": "sha512-8oOR47Ejt+YJPNQzedbiklDqS1zurEaNrxXpRs+Uk4DMDPVmKNagShFeUaYsfvWP55AhI+P1non5QZAHV6K78A==", + "dev": true, + "requires": { + "eslint-utils": "^2.1.0", + "ramda": "^0.27.1" + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", + "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "eventid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventid/-/eventid-1.0.0.tgz", + "integrity": "sha512-4upSDsvpxhWPsmw4fsJCp0zj8S7I0qh1lCDTmZXP8V3TtryQKDI8CgQPN+e5JakbWwzaAX3lrdp2b3KSoMSUpw==", + "requires": { + "d64": "^1.0.0", + "uuid": "^3.0.1" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "findit2": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", + "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0" + } + }, + "flatted": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.1.tgz", + "integrity": "sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg==", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-extra": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha1-BsJ3IYRU7CiN93raVKA7hwKqy50=", + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "gaxios": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", + "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", + "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", + "requires": { + "gaxios": "^2.1.0", + "json-bigint": "^0.3.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha1-OWCDLT8VdBCDQtr9OmezMsCWnfE=", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "dev": true, + "requires": { + "ini": "1.3.7" + }, + "dependencies": { + "ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "dev": true + } + } + }, + "globals": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.10.0.tgz", + "integrity": "sha512-piHC3blgLGFjvOuMmWZX60f+na1lXFDhQXBf1UYp2fXPXqvEUbOhNwi6BsQ0bQishwedgnjkwv1d9zKf+MWw3g==", + "dev": true + }, + "google-auth-library": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.6.tgz", + "integrity": "sha512-fWYdRdg55HSJoRq9k568jJA1lrhg9i2xgfhVIMJbskUmbDpJGHsbv9l41DGhCDXM21F9Kn4kUwdysgxSYBYJUw==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^3.0.0", + "gcp-metadata": "^4.1.0", + "gtoken": "^5.0.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "gaxios": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.1.0.tgz", + "integrity": "sha512-DDTn3KXVJJigtz+g0J3vhcfbDbKtAroSTxauWsdnP57sM5KZ3d2c/3D9RKFJ86s43hfw6WULg6TXYw/AYiBlpA==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.4.tgz", + "integrity": "sha512-5J/GIH0yWt/56R3dNaNWPGQ/zXsZOddYECfJaqxFWgrZ9HC2Kvc5vl9upOgUUHKzURjAVf2N+f6tEJiojqXUuA==", + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-p12-pem": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.2.tgz", + "integrity": "sha512-tbjzndQvSIHGBLzHnhDs3cL4RBjLbLXc2pYvGH+imGVu5b4RMAttUTdnmW2UH0t11QeBTXZ7wlXPS7hrypO/tg==", + "requires": { + "node-forge": "^0.9.0" + } + }, + "gtoken": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.3.tgz", + "integrity": "sha512-Nyd1wZCMRc2dj/mAD0LlfQLcAO06uKdpKJXvK85SGrF5+5+Bpfil9u/2aw35ltvEHjvl0h5FMKN5knEU+9JrOg==", + "requires": { + "gaxios": "^3.0.0", + "google-p12-pem": "^3.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "google-gax": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz", + "integrity": "sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ==", + "requires": { + "@grpc/grpc-js": "~1.0.3", + "@grpc/proto-loader": "^0.5.1", + "@types/fs-extra": "^8.0.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^3.6.0", + "google-auth-library": "^5.0.0", + "is-stream-ended": "^0.1.4", + "lodash.at": "^4.6.0", + "lodash.has": "^4.5.2", + "node-fetch": "^2.6.0", + "protobufjs": "^6.8.9", + "retry-request": "^4.0.0", + "semver": "^6.0.0", + "walkdir": "^0.4.0" + }, + "dependencies": { + "@types/node": { + "version": "13.13.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.15.tgz", + "integrity": "sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw==" + }, + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + } + }, + "protobufjs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", + "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + } + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "google-p12-pem": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", + "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", + "requires": { + "node-forge": "^0.9.0" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "gtoken": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", + "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", + "requires": { + "gaxios": "^2.1.0", + "google-p12-pem": "^2.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "heapdump": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.15.tgz", + "integrity": "sha512-n8aSFscI9r3gfhOcAECAtXFaQ1uy4QSke6bnaL+iymYZ/dWs9cqDqHM+rALfsHUwukUbxsdlECZ0pKmJdQ/4OA==", + "requires": { + "nan": "^2.13.2" + } + }, + "hex2dec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/hex2dec/-/hex2dec-1.1.2.tgz", + "integrity": "sha512-Yu+q/XWr2fFQ11tHxPq4p4EiNkb2y+lAacJNhAdRXVfRIcDH6gi7htWFnnlIzvqHMHoWeIsfXlNAjZInpAOJDA==" + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha1-qD5i59JyrA47VRqqgoMaGbafgvg=", + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflection": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", + "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-bluebird": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", + "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" + }, + "is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", + "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dev": true, + "requires": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + } + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "is-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-bigint": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "requires": { + "bignumber.js": "^7.0.0" + } + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-extend": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "dev": true + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "requires": { + "package-json": "^6.3.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lockfile": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "integrity": "sha1-B/gZ0lrkj4flOOZXi2lkpJgaVgk=", + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.at": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", + "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==" + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "logger-sharelatex": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-2.2.0.tgz", + "integrity": "sha512-ko+OmE25XHJJCiz1R9EgwlfM7J/5olpunUfR3WcfuqOQrcUqsdBrDA2sOytngT0ViwjCR0Fh4qZVPwEWfmrvwA==", + "requires": { + "@google-cloud/logging-bunyan": "^3.0.0", + "@overleaf/o-error": "^3.0.0", + "bunyan": "^1.8.14", + "node-fetch": "^2.6.0", + "raven": "^2.6.4", + "yn": "^4.0.0" + }, + "dependencies": { + "yn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz", + "integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg==" + } + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==" + }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha1-ys6+SSAiSX9law8PUeJoKp7S2Eg=", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha1-3SfqYTYkPHyIBoToZyuzpF/ZthQ=", + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.0.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha1-EUyUlnPiqKNenTV4hSeqN7Z52is=" + }, + "moment": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", + "integrity": "sha1-dZ6kkayX1UusWtd2mW4qWMwbwiU=" + }, + "moment-timezone": { + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", + "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "^6.0.1" + } + } + } + }, + "mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "requires": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "dependencies": { + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, + "needle": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz", + "integrity": "sha1-UZMb/4JTOxkot9HWngHxsA/9Kk4=", + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "nise": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.3.tgz", + "integrity": "sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==" + }, + "node-pre-gyp": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", + "integrity": "sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nodemon": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", + "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==", + "dev": true, + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true + }, + "npm-bundled": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", + "integrity": "sha1-PBcyt7qTazoQMlrvYWRnwMy8yXk=" + }, + "npm-packlist": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz", + "integrity": "sha1-Ir3i68EucspIKr1nr8UetJN3JDo=", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.values": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", + "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.2" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + } + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-duration": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.4.4.tgz", + "integrity": "sha512-KbAJuYGUhZkB9gotDiKLnZ7Z3VTacK3fgwmDdB6ZVDtJbMBT6MfLga0WJaYpPDu0mzqT0NgHtHDt5PY4l0nidg==" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pdfjs-dist": { + "version": "2.7.570", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.7.570.tgz", + "integrity": "sha512-/ZkA1FwkEOyDaq11JhMLazdwQAA0F9uwrP7h/1L9Akt9KWh1G5/tkzS+bPuUELq2s2GDFnaT+kooN/aSjT7DXQ==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pprof": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pprof/-/pprof-3.0.0.tgz", + "integrity": "sha512-uPWbAhoH/zvq1kM3/Fd/wshb4D7sLlGap8t6uCTER4aZRWqqyPYgXzpjWbT0Unn5U25pEy2VREUu27nQ9o9VPA==", + "requires": { + "bindings": "^1.2.1", + "delay": "^4.0.1", + "findit2": "^2.2.3", + "nan": "^2.14.0", + "node-pre-gyp": "^0.16.0", + "p-limit": "^3.0.0", + "pify": "^5.0.0", + "protobufjs": "~6.10.0", + "source-map": "^0.7.3", + "split": "^1.0.1" + }, + "dependencies": { + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "@types/node": { + "version": "13.13.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.42.tgz", + "integrity": "sha512-g+w2QgbW7k2CWLOXzQXbO37a7v5P9ObPvYahKphdBLV5aqpbVZRhTpWCT0SMRqX1i30Aig791ZmIM2fJGL2S8A==" + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.16.0.tgz", + "integrity": "sha512-4efGA+X/YXAHLi1hN8KaPrILULaUn2nWecFrn1k2I+99HpoyvcOGEbtcOxpDiUwPF2ZANMJDh32qwOUPenuR1g==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, + "protobufjs": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", + "integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + } + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "requires": { + "parse-ms": "^2.1.0" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o=" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prom-client": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.3.tgz", + "integrity": "sha512-iz22FmTbtkyL2vt0MdDFY+kWof+S9UB/NACxSn2aJcewtw+EERsen0urSkZ2WrHseNdydsvcxCTAnPcSMZZv4Q==", + "requires": { + "tdigest": "^0.1.1" + } + }, + "protobufjs": { + "version": "6.8.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "psl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" + }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "requires": { + "escape-goat": "^2.0.0" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "ramda": { + "version": "0.27.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", + "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raven": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/raven/-/raven-2.6.4.tgz", + "integrity": "sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw==", + "requires": { + "cookie": "0.3.1", + "md5": "^2.2.1", + "stack-trace": "0.0.10", + "timed-out": "4.0.1", + "uuid": "3.3.2" + }, + "dependencies": { + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0=", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-in-the-middle": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz", + "integrity": "sha512-M2rLKVupQfJ5lf9OvqFGIT+9iVLnTmjgbOmpil12hiSQNn5zJTKGPoIisETNjfK+09vP3rpm1zJajmErpr2sEQ==", + "requires": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.12.0" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", + "dev": true + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "retry-as-promised": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-3.2.0.tgz", + "integrity": "sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==", + "requires": { + "any-promise": "^1.3.0" + } + }, + "retry-request": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", + "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", + "requires": { + "debug": "^4.1.1", + "through2": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" + }, + "safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" + }, + "sandboxed-module": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz", + "integrity": "sha1-x+VFkzm7y6KMUwPusz9ug4e/upY=", + "dev": true, + "requires": { + "require-like": "0.1.2", + "stack-trace": "0.0.9" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=" + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha1-fnQlb7qknHWqfHogXMInmcrIAAQ=" + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "sequelize": { + "version": "5.21.5", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-5.21.5.tgz", + "integrity": "sha512-n9hR5K4uQGmBGK/Y/iqewCeSFmKVsd0TRnh0tfoLoAkmXbKC4tpeK96RhKs7d+TTMtrJlgt2TNLVBaAxEwC4iw==", + "requires": { + "bluebird": "^3.5.0", + "cls-bluebird": "^2.1.0", + "debug": "^4.1.1", + "dottie": "^2.0.0", + "inflection": "1.12.0", + "lodash": "^4.17.15", + "moment": "^2.24.0", + "moment-timezone": "^0.5.21", + "retry-as-promised": "^3.2.0", + "semver": "^6.3.0", + "sequelize-pool": "^2.3.0", + "toposort-class": "^1.0.1", + "uuid": "^3.3.3", + "validator": "^10.11.0", + "wkx": "^0.4.8" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "sequelize-pool": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-2.3.0.tgz", + "integrity": "sha512-Ibz08vnXvkZ8LJTiUOxRcj1Ckdn7qafNZ2t59jYHMX1VIebTAOYefWdRYFt6z6+hy52WGthAHAoLc9hvk3onqA==" + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shimmer": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", + "integrity": "sha1-+Wb3VVeJdj502IQRk2haXnhzZmU=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sinon": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.1.tgz", + "integrity": "sha512-iTTyiQo5T94jrOx7X7QLBZyucUJ2WvL9J13+96HMfm2CGoJYbIPqRfl6wgNcqmzk0DI28jeGx5bUTXizkrqBmg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/formatio": "^5.0.1", + "@sinonjs/samsam": "^5.0.3", + "diff": "^4.0.2", + "nise": "^4.0.1", + "supports-color": "^7.1.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true + } + } + }, + "snakecase-keys": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-3.2.0.tgz", + "integrity": "sha512-WTJ0NhCH/37J+PU3fuz0x5b6TvtWQChTcKPOndWoUy0pteKOe0hrHMzSRsJOWSIP48EQkzUEsgQPmrG3W8pFNQ==", + "requires": { + "map-obj": "^4.0.0", + "to-snake-case": "^1.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sqlite3": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.1.tgz", + "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.11.0", + "request": "^2.87.0" + } + }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, + "ssh2": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.8.tgz", + "integrity": "sha512-egJVQkf3sbjECTY6rCeg8rgV/fab6S/7E5kpYqHT3Fe/YpfJbLYeA1qTcB2d+LRUUAjqKi7rlbfWkaP66YdpAQ==", + "requires": { + "ssh2-streams": "~0.4.9" + } + }, + "ssh2-streams": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.9.tgz", + "integrity": "sha512-glMQKeYKuA+rLaH16fJC3nZMV1BWklbxuYCR4C5/LlBSf2yaoNRpPU7Ul702xsi5nvYjIx9XDkKBJwrBjkDynw==", + "requires": { + "asn1": "~0.2.0", + "bcrypt-pbkdf": "^1.0.2", + "streamsearch": "~0.1.2" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.1.tgz", + "integrity": "sha512-42VLtQUOLefAvKFAQIxIZDaThq6om/PrfP0CYk3/vn+y4BMNkKnbli8ON2QCiHov4KkzOSJ/xSoBJdayiiYvVQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + } + } + }, + "tar": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", + "integrity": "sha1-sZ7sP94qluZGZt+f20DFyhvDdH0=", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "tar-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", + "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", + "requires": { + "chownr": "^1.1.1", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", + "requires": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "tdigest": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz", + "integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=", + "requires": { + "bintrees": "1.0.1" + } + }, + "teeny-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", + "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.2.0", + "stream-events": "^1.0.5", + "uuid": "^7.0.0" + }, + "dependencies": { + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + } + } + }, + "term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "requires": { + "readable-stream": "2 || 3" + } + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "timekeeper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-2.2.0.tgz", + "integrity": "sha512-W3AmPTJWZkRwu+iSNxPIsLZ2ByADsOLbbLxe46UJyWj3mlYLlwucKiq+/dPm0l9wTzqoF3/2PH0AGFCebjq23A==", + "dev": true + }, + "to-no-case": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", + "integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=" + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "to-snake-case": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-snake-case/-/to-snake-case-1.0.0.tgz", + "integrity": "sha1-znRpE4l5RgGah+Yu366upMYIq4w=", + "requires": { + "to-space-case": "^1.0.0" + } + }, + "to-space-case": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz", + "integrity": "sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=", + "requires": { + "to-no-case": "^1.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "toposort-class": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", + "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tsconfig-paths": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz", + "integrity": "sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==", + "dev": true, + "requires": { + "json5": "^2.2.0", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "dependencies": { + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + } + } + }, + "undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "dev": true, + "requires": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "v8-profiler-node8": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/v8-profiler-node8/-/v8-profiler-node8-6.1.1.tgz", + "integrity": "sha512-mKS7TXRRYi70hvbv5c1tk9AbuqNrtbLc+jFLlsZ2TpaC1l5lWryBlDLZKJ1JP6hjSbMEjW1ucjWLSaKsaPnGXg==", + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.13.0" + }, + "dependencies": { + "node-pre-gyp": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", + "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + } + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha1-rgdOa9wMFKQx6ATmJFScYzsABFc=", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "wkx": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.8.tgz", + "integrity": "sha512-ikPXMM9IR/gy/LwiOSqWlSL3X/J5uk9EO2hHNRXS41eTLXaUFEVw9fn/593jW/tE5tedNg8YjT5HkCa4FqQZyQ==", + "requires": { + "@types/node": "*" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "workerpool": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "wrench": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.5.9.tgz", + "integrity": "sha1-QRaRxjqbJTGxcAJnJ5veyiOyFCo=" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha1-tLBJ4xS+VF486AIjbWzSLNkcPek=" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } +} diff --git a/services/clsi/package.json b/services/clsi/package.json new file mode 100644 index 0000000000..0b2b011d49 --- /dev/null +++ b/services/clsi/package.json @@ -0,0 +1,67 @@ +{ + "name": "node-clsi", + "description": "A Node.js implementation of the CLSI LaTeX web-API", + "version": "0.1.4", + "repository": { + "type": "git", + "url": "https://github.com/sharelatex/clsi-sharelatex.git" + }, + "scripts": { + "start": "node $NODE_APP_OPTIONS app.js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP", + "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP", + "nodemon": "nodemon --config nodemon.json", + "lint": "eslint --max-warnings 0 --format unix .", + "format": "prettier --list-different $PWD/'**/*.js'", + "format:fix": "prettier --write $PWD/'**/*.js'", + "lint:fix": "eslint --fix ." + }, + "author": "James Allen ", + "dependencies": { + "@overleaf/metrics": "^3.5.1", + "@overleaf/o-error": "^3.3.1", + "@overleaf/settings": "^2.1.1", + "async": "3.2.0", + "body-parser": "^1.19.0", + "bunyan": "^1.8.15", + "diskusage": "^1.1.3", + "dockerode": "^3.1.0", + "express": "^4.17.1", + "fs-extra": "^10.0.0", + "heapdump": "^0.3.15", + "lockfile": "^1.0.4", + "lodash": "^4.17.21", + "logger-sharelatex": "^2.2.0", + "mysql": "^2.18.1", + "p-limit": "^3.1.0", + "pdfjs-dist": "^2.7.570", + "request": "^2.88.2", + "send": "^0.17.1", + "sequelize": "^5.21.5", + "sqlite3": "^4.1.1", + "v8-profiler-node8": "^6.1.1", + "wrench": "~1.5.9" + }, + "devDependencies": { + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", + "eslint": "^7.21.0", + "eslint-config-prettier": "^8.1.0", + "eslint-config-standard": "^16.0.2", + "eslint-plugin-chai-expect": "^2.2.0", + "eslint-plugin-chai-friendly": "^0.6.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-mocha": "^8.0.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-promise": "^4.2.1", + "mocha": "^8.3.2", + "nodemon": "^2.0.7", + "prettier": "^2.2.1", + "sandboxed-module": "^2.0.3", + "sinon": "~9.0.1", + "timekeeper": "2.2.0" + } +} diff --git a/services/clsi/patch-texlive-dockerfile b/services/clsi/patch-texlive-dockerfile new file mode 100644 index 0000000000..61cb796414 --- /dev/null +++ b/services/clsi/patch-texlive-dockerfile @@ -0,0 +1,3 @@ +FROM quay.io/sharelatex/texlive-full:2017.1 + +# RUN usermod -u 1001 tex diff --git a/services/clsi/scripts/demo-pdfjs-Xref.js b/services/clsi/scripts/demo-pdfjs-Xref.js new file mode 100644 index 0000000000..86be134ef5 --- /dev/null +++ b/services/clsi/scripts/demo-pdfjs-Xref.js @@ -0,0 +1,12 @@ +const fs = require('fs') +const { parseXrefTable } = require('../app/lib/pdfjs/parseXrefTable') + +const pdfPath = process.argv[2] + +async function main() { + const size = (await fs.promises.stat(pdfPath)).size + const xRefEntries = await parseXrefTable(pdfPath, size) + console.log('Xref entries', xRefEntries) +} + +main().catch(console.error) diff --git a/services/clsi/seccomp/clsi-profile.json b/services/clsi/seccomp/clsi-profile.json new file mode 100644 index 0000000000..e7e9dd010b --- /dev/null +++ b/services/clsi/seccomp/clsi-profile.json @@ -0,0 +1,836 @@ +{ + "defaultAction": "SCMP_ACT_ERRNO", + "architectures": [ + "SCMP_ARCH_X86_64", + "SCMP_ARCH_X86", + "SCMP_ARCH_X32" + ], + "syscalls": [ + { + "name": "access", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "arch_prctl", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "brk", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "chdir", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "chmod", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "clock_getres", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "clock_gettime", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "clock_nanosleep", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "clone", + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 2080505856, + "valueTwo": 0, + "op": "SCMP_CMP_MASKED_EQ" + } + ] + }, + { + "name": "close", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "copy_file_range", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "creat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "dup", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "dup2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "dup3", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "execve", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "execveat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "exit", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "exit_group", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "faccessat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fadvise64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fadvise64_64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fallocate", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fchdir", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fchmod", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fchmodat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fcntl", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fcntl64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fdatasync", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fork", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstat64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstatat64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstatfs", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fstatfs64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fsync", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "ftruncate", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "ftruncate64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "futex", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "futimesat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getcpu", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getcwd", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getdents", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getdents64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getegid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getegid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "geteuid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "geteuid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getgid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getgid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getgroups", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getgroups32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getpgid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getpgrp", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getpid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getppid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getpriority", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getresgid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getresgid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getresuid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getresuid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getrlimit", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "get_robust_list", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getrusage", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getsid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "gettid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getuid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "getuid32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "ioctl", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "kill", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "_llseek", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "lseek", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "lstat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "lstat64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "madvise", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mkdir", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mkdirat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mmap", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mmap2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mprotect", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "mremap", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "munmap", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "newfstatat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "open", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "openat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pause", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pipe", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pipe2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "prctl", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pread64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "preadv", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "prlimit64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pwrite64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pwritev", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "read", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "readlink", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "readlinkat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "readv", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rename", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "renameat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "renameat2", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "restart_syscall", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rmdir", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigaction", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigpending", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigprocmask", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigqueueinfo", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigreturn", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigsuspend", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_sigtimedwait", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "rt_tgsigqueueinfo", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_getaffinity", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_getparam", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_get_priority_max", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_get_priority_min", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_getscheduler", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_rr_get_interval", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sched_yield", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sendfile", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sendfile64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "setgroups", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "setgroups32", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "set_robust_list", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "set_tid_address", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sigaltstack", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "stat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "stat64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "statfs", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "statfs64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sync", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sync_file_range", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "syncfs", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "sysinfo", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "tgkill", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_create", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_delete", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_getoverrun", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_gettime", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "timer_settime", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "times", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "tkill", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "truncate", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "truncate64", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "umask", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "uname", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "unlink", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "unlinkat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "utime", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "utimensat", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "utimes", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "vfork", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "vhangup", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "wait4", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "waitid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "write", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "writev", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "pread", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "setgid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "setuid", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "capget", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "capset", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "fchown", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, + { + "name": "gettimeofday", + "action": "SCMP_ACT_ALLOW", + "args": [] + }, { + "name": "epoll_pwait", + "action": "SCMP_ACT_ALLOW", + "args": [] + } + ] +} \ No newline at end of file diff --git a/services/clsi/src/synctex.c b/services/clsi/src/synctex.c new file mode 100644 index 0000000000..5267f81f9c --- /dev/null +++ b/services/clsi/src/synctex.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +#include "synctex/synctex_parser.h" + + +void print_usage() { + fprintf (stderr, "Usage: synctex code \n"); + fprintf (stderr, " synctex pdf \n"); +} + +int main(int argc, char *argv[], char *envp[]) { + synctex_scanner_t scanner; + + if (argc < 6 || (strcmp(argv[1], "code") != 0 && strcmp(argv[1], "pdf") != 0)) { + print_usage(); + return EXIT_FAILURE; + } + + const char* direction = argv[1]; + const char* synctex_file = argv[2]; + + scanner = synctex_scanner_new_with_output_file(synctex_file, NULL, 1); + + if(!(scanner = synctex_scanner_parse(scanner))) { + fprintf (stderr, "Could not parse output file\n"); + return EXIT_FAILURE; + } + + if (strcmp(direction, "code") == 0) { + const char* name = argv[3]; + int line = atoi(argv[4]); + int column = atoi(argv[5]); + + if(synctex_display_query(scanner, name, line, column) > 0) { + synctex_node_t node; + while((node = synctex_next_result(scanner))) { + int page = synctex_node_page(node); + float h = synctex_node_box_visible_h(node); + float v = synctex_node_box_visible_v(node); + float width = synctex_node_box_visible_width(node); + float height = synctex_node_box_visible_height(node); + printf ("NODE\t%d\t%.2f\t%.2f\t%.2f\t%.2f\n", page, h, v, width, height); + } + } + } else if (strcmp(direction, "pdf") == 0) { + int page = atoi(argv[3]); + float h = atof(argv[4]); + float v = atof(argv[5]); + + if(synctex_edit_query(scanner, page, h, v) > 0) { + synctex_node_t node; + while((node = synctex_next_result(scanner))) { + int tag = synctex_node_tag(node); + const char* name = synctex_scanner_get_name(scanner, tag); + int line = synctex_node_line(node); + int column = synctex_node_column(node); + printf ("NODE\t%s\t%d\t%d\n", name, line, column); + } + } + } + + return 0; +} \ No newline at end of file diff --git a/services/clsi/src/synctex/synctex_parser.c b/services/clsi/src/synctex/synctex_parser.c new file mode 100644 index 0000000000..0d3de08b48 --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser.c @@ -0,0 +1,4249 @@ +/* +Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Version 1 +Thu Jun 19 09:39:21 UTC 2008 + +*/ + +/* We assume that high level application like pdf viewers will want + * to embed this code as is. We assume that they also have locale.h and setlocale. + * For other tools such as TeXLive tools, you must define SYNCTEX_USE_LOCAL_HEADER, + * when building. You also have to create and customize synctex_parser_local.h to fit your system. + * In particular, the HAVE_LOCALE_H and HAVE_SETLOCALE macros should be properly defined. + * With this design, you should not need to edit this file. */ + +# if defined(SYNCTEX_USE_LOCAL_HEADER) +# include "synctex_parser_local.h" +# else +# define HAVE_LOCALE_H 1 +# define HAVE_SETLOCALE 1 +# if defined(_MSC_VER) +# define SYNCTEX_INLINE __inline +# else +# define SYNCTEX_INLINE inline +# endif +# endif + +#include +#include +#include +#include +#include + +#if defined(HAVE_LOCALE_H) +#include +#endif + +/* The data is organized in a graph with multiple entries. + * The root object is a scanner, it is created with the contents on a synctex file. + * Each leaf of the tree is a synctex_node_t object. + * There are 3 subtrees, two of them sharing the same leaves. + * The first tree is the list of input records, where input file names are associated with tags. + * The second tree is the box tree as given by TeX when shipping pages out. + * First level objects are sheets, containing boxes, glues, kerns... + * The third tree allows to browse leaves according to tag and line. + */ + +#include "synctex_parser.h" +#include "synctex_parser_utils.h" + +/* These are the possible extensions of the synctex file */ +const char * synctex_suffix = ".synctex"; +const char * synctex_suffix_gz = ".gz"; + +/* each synctex node has a class */ +typedef struct __synctex_class_t _synctex_class_t; +typedef _synctex_class_t * synctex_class_t; + + +/* synctex_node_t is a pointer to a node + * _synctex_node is the target of the synctex_node_t pointer + * It is a pseudo object oriented program. + * class is a pointer to the class object the node belongs to. + * implementation is meant to contain the private data of the node + * basically, there are 2 kinds of information: navigation information and + * synctex information. Both will depend on the type of the node, + * thus different nodes will have different private data. + * There is no inheritancy overhead. + */ +typedef union _synctex_info_t { + int INT; + char * PTR; +} synctex_info_t; + +struct _synctex_node { + synctex_class_t class; + synctex_info_t * implementation; +}; + +/* Each node of the tree, except the scanner itself belongs to a class. + * The class object is just a struct declaring the owning scanner + * This is a pointer to the scanner as root of the tree. + * The type is used to identify the kind of node. + * The class declares pointers to a creator and a destructor method. + * The log and display fields are used to log and display the node. + * display will also display the child, sibling and parent sibling. + * parent, child and sibling are used to navigate the tree, + * from TeX box hierarchy point of view. + * The friend field points to a method which allows to navigate from friend to friend. + * A friend is a node with very close tag and line numbers. + * Finally, the info field point to a method giving the private node info offset. + */ + +typedef synctex_node_t *(*_synctex_node_getter_t)(synctex_node_t); +typedef synctex_info_t *(*_synctex_info_getter_t)(synctex_node_t); + +struct __synctex_class_t { + synctex_scanner_t scanner; + int type; + synctex_node_t (*new)(synctex_scanner_t scanner); + void (*free)(synctex_node_t); + void (*log)(synctex_node_t); + void (*display)(synctex_node_t); + _synctex_node_getter_t parent; + _synctex_node_getter_t child; + _synctex_node_getter_t sibling; + _synctex_node_getter_t friend; + _synctex_node_getter_t next_box; + _synctex_info_getter_t info; +}; + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Abstract OBJECTS and METHODS +# endif + +/* These macros are shortcuts + * This macro checks if a message can be sent. + */ +# define SYNCTEX_CAN_PERFORM(NODE,SELECTOR)\ + (NULL!=((((NODE)->class))->SELECTOR)) + +/* This macro is some kind of objc_msg_send. + * It takes care of sending the proper message if possible. + */ +# define SYNCTEX_MSG_SEND(NODE,SELECTOR) if (NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR)) {\ + (*((((NODE)->class))->SELECTOR))(NODE);\ + } + +/* read only safe getter + */ +# define SYNCTEX_GET(NODE,SELECTOR)((NODE && SYNCTEX_CAN_PERFORM(NODE,SELECTOR))?SYNCTEX_GETTER(NODE,SELECTOR)[0]:(NULL)) + +/* read/write getter + */ +# define SYNCTEX_GETTER(NODE,SELECTOR)\ + ((synctex_node_t *)((*((((NODE)->class))->SELECTOR))(NODE))) + +# define SYNCTEX_FREE(NODE) SYNCTEX_MSG_SEND(NODE,free); + +/* Parent getter and setter + */ +# define SYNCTEX_PARENT(NODE) SYNCTEX_GET(NODE,parent) +# define SYNCTEX_SET_PARENT(NODE,NEW_PARENT) if (NODE && NEW_PARENT && SYNCTEX_CAN_PERFORM(NODE,parent)){\ + SYNCTEX_GETTER(NODE,parent)[0]=NEW_PARENT;\ + } + +/* Child getter and setter + */ +# define SYNCTEX_CHILD(NODE) SYNCTEX_GET(NODE,child) +# define SYNCTEX_SET_CHILD(NODE,NEW_CHILD) if (NODE && NEW_CHILD){\ + SYNCTEX_GETTER(NODE,child)[0]=NEW_CHILD;\ + SYNCTEX_GETTER(NEW_CHILD,parent)[0]=NODE;\ + } + +/* Sibling getter and setter + */ +# define SYNCTEX_SIBLING(NODE) SYNCTEX_GET(NODE,sibling) +# define SYNCTEX_SET_SIBLING(NODE,NEW_SIBLING) if (NODE && NEW_SIBLING) {\ + SYNCTEX_GETTER(NODE,sibling)[0]=NEW_SIBLING;\ + if (SYNCTEX_CAN_PERFORM(NEW_SIBLING,parent) && SYNCTEX_CAN_PERFORM(NODE,parent)) {\ + SYNCTEX_GETTER(NEW_SIBLING,parent)[0]=SYNCTEX_GETTER(NODE,parent)[0];\ + }\ + } +/* Friend getter and setter. A friend is a kern, math, glue or void box node which tag and line numbers are similar. + * This is a first filter on the nodes that avoids testing all of them. + * Friends are used mainly in forward synchronization aka from source to output. + */ +# define SYNCTEX_FRIEND(NODE) SYNCTEX_GET(NODE,friend) +# define SYNCTEX_SET_FRIEND(NODE,NEW_FRIEND) if (NODE && NEW_FRIEND){\ + SYNCTEX_GETTER(NODE,friend)[0]=NEW_FRIEND;\ + } + +/* Next box getter and setter. The box tree can be traversed from one horizontal box to the other. + * Navigation starts with the deeper boxes. + */ +# define SYNCTEX_NEXT_HORIZ_BOX(NODE) SYNCTEX_GET(NODE,next_box) +# define SYNCTEX_SET_NEXT_HORIZ_BOX(NODE,NEXT_BOX) if (NODE && NEXT_BOX){\ + SYNCTEX_GETTER(NODE,next_box)[0]=NEXT_BOX;\ + } + +void _synctex_free_node(synctex_node_t node); +void _synctex_free_leaf(synctex_node_t node); + +/* A node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for all nodes with children. + */ +void _synctex_free_node(synctex_node_t node) { + if (node) { + (*((node->class)->sibling))(node); + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + SYNCTEX_FREE(SYNCTEX_CHILD(node)); + free(node); + } + return; +} + +/* A node is meant to own its child and sibling. + * It is not owned by its parent, unless it is its first child. + * This destructor is for nodes with no child. + */ +void _synctex_free_leaf(synctex_node_t node) { + if (node) { + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + free(node); + } + return; +} +# ifdef __SYNCTEX_WORK__ +# include "/usr/include/zlib.h" +# else +# include +# endif + +/* The synctex scanner is the root object. + * Is is initialized with the contents of a text file or a gzipped file. + * The buffer_? are first used to parse the text. + */ +struct __synctex_scanner_t { + gzFile file; /* The (possibly compressed) file */ + char * buffer_cur; /* current location in the buffer */ + char * buffer_start; /* start of the buffer */ + char * buffer_end; /* end of the buffer */ + char * output_fmt; /* dvi or pdf, not yet used */ + char * output; /* the output name used to create the scanner */ + char * synctex; /* the .synctex or .synctex.gz name used to create the scanner */ + int version; /* 1, not yet used */ + struct { + unsigned has_parsed:1; /* Whether the scanner has parsed its underlying synctex file. */ + unsigned reserved:sizeof(unsigned)-1; /* alignment */ + } flags; + int pre_magnification; /* magnification from the synctex preamble */ + int pre_unit; /* unit from the synctex preamble */ + int pre_x_offset; /* X offste from the synctex preamble */ + int pre_y_offset; /* Y offset from the synctex preamble */ + int count; /* Number of records, from the synctex postamble */ + float unit; /* real unit, from synctex preamble or post scriptum */ + float x_offset; /* X offset, from synctex preamble or post scriptum */ + float y_offset; /* Y Offset, from synctex preamble or post scriptum */ + synctex_node_t sheet; /* The first sheet node, its siblings are the other sheet nodes */ + synctex_node_t input; /* The first input node, its siblings are the other input nodes */ + int number_of_lists; /* The number of friend lists */ + synctex_node_t * lists_of_friends;/* The friend lists */ + _synctex_class_t class[synctex_node_number_of_types]; /* The classes of the nodes of the scanner */ +}; + +/* SYNCTEX_CUR, SYNCTEX_START and SYNCTEX_END are convenient shortcuts + */ +# define SYNCTEX_CUR (scanner->buffer_cur) +# define SYNCTEX_START (scanner->buffer_start) +# define SYNCTEX_END (scanner->buffer_end) + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark OBJECTS, their creators and destructors. +# endif + +/* Here, we define the indices for the different informations. + * They are used to declare the size of the implementation. + * For example, if one object uses SYNCTEX_HORIZ_IDX is its size, + * then its info will contain a tag, line, column, horiz but no width nor height nor depth + */ + +/* The sheet is a first level node. + * It has no parent (the parent is the scanner itself) + * Its sibling points to another sheet. + * Its child points to its first child, in general a box. + * A sheet node contains only one synctex information: the page. + * This is the 1 based page index as given by TeX. + */ +/* The next macros are used to access the node info + * SYNCTEX_INFO(node) points to the first synctex integer or pointer data of node + * SYNCTEX_INFO(node)[index] is the information at index + * for example, the page of a sheet is stored in SYNCTEX_INFO(sheet)[SYNCTEX_PAGE_IDX] + */ +# define SYNCTEX_INFO(NODE) ((*((((NODE)->class))->info))(NODE)) +# define SYNCTEX_PAGE_IDX 0 +# define SYNCTEX_PAGE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_PAGE_IDX].INT + +/* This macro defines implementation offsets + * It is only used for pointer values + */ +# define SYNCTEX_MAKE_GET(SYNCTEX_GETTER,OFFSET)\ +synctex_node_t * SYNCTEX_GETTER (synctex_node_t node);\ +synctex_node_t * SYNCTEX_GETTER (synctex_node_t node) {\ + return node?(synctex_node_t *)((&((node)->implementation))+OFFSET):NULL;\ +} +SYNCTEX_MAKE_GET(_synctex_implementation_0,0) +SYNCTEX_MAKE_GET(_synctex_implementation_1,1) +SYNCTEX_MAKE_GET(_synctex_implementation_2,2) +SYNCTEX_MAKE_GET(_synctex_implementation_3,3) +SYNCTEX_MAKE_GET(_synctex_implementation_4,4) +SYNCTEX_MAKE_GET(_synctex_implementation_5,5) + +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_PAGE_IDX+1];/* child, sibling, next box, + * SYNCTEX_PAGE_IDX */ +} synctex_sheet_t; + +synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner); +void _synctex_display_sheet(synctex_node_t sheet); +void _synctex_log_sheet(synctex_node_t sheet); + +static _synctex_class_t synctex_class_sheet = { + NULL, /* No scanner yet */ + synctex_node_type_sheet, /* Node type */ + &_synctex_new_sheet, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_sheet, /* log */ + &_synctex_display_sheet, /* display */ + NULL, /* No parent */ + &_synctex_implementation_0, /* child */ + &_synctex_implementation_1, /* sibling */ + NULL, /* No friend */ + &_synctex_implementation_2, /* Next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 /* info */ +}; + +/* sheet node creator */ +synctex_node_t _synctex_new_sheet(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_sheet_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_sheet:(synctex_class_t)&synctex_class_sheet; + } + return node; +} + +/* A box node contains navigation and synctex information + * There are different kind of boxes. + * Only horizontal boxes are treated differently because of their visible size. + */ +# define SYNCTEX_TAG_IDX 0 +# define SYNCTEX_LINE_IDX (SYNCTEX_TAG_IDX+1) +# define SYNCTEX_COLUMN_IDX (SYNCTEX_LINE_IDX+1) +# define SYNCTEX_HORIZ_IDX (SYNCTEX_COLUMN_IDX+1) +# define SYNCTEX_VERT_IDX (SYNCTEX_HORIZ_IDX+1) +# define SYNCTEX_WIDTH_IDX (SYNCTEX_VERT_IDX+1) +# define SYNCTEX_HEIGHT_IDX (SYNCTEX_WIDTH_IDX+1) +# define SYNCTEX_DEPTH_IDX (SYNCTEX_HEIGHT_IDX+1) +/* the corresponding info accessors */ +# define SYNCTEX_TAG(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_TAG_IDX].INT +# define SYNCTEX_LINE(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_LINE_IDX].INT +# define SYNCTEX_COLUMN(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_COLUMN_IDX].INT +# define SYNCTEX_HORIZ(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_IDX].INT +# define SYNCTEX_VERT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_IDX].INT +# define SYNCTEX_WIDTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_IDX].INT +# define SYNCTEX_HEIGHT(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_IDX].INT +# define SYNCTEX_DEPTH(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_IDX].INT +# define SYNCTEX_ABS_WIDTH(NODE) ((SYNCTEX_WIDTH(NODE)>0?SYNCTEX_WIDTH(NODE):-SYNCTEX_WIDTH(NODE))) +# define SYNCTEX_ABS_HEIGHT(NODE) ((SYNCTEX_HEIGHT(NODE)>0?SYNCTEX_HEIGHT(NODE):-SYNCTEX_HEIGHT(NODE))) +# define SYNCTEX_ABS_DEPTH(NODE) ((SYNCTEX_DEPTH(NODE)>0?SYNCTEX_DEPTH(NODE):-SYNCTEX_DEPTH(NODE))) + +typedef struct { + synctex_class_t class; + synctex_info_t implementation[5+SYNCTEX_DEPTH_IDX+1]; /* parent,child,sibling,friend,next box, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH */ +} synctex_vert_box_node_t; + +synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner); +void _synctex_log_box(synctex_node_t sheet); +void _synctex_display_vbox(synctex_node_t node); + +/* These are static class objects, each scanner will make a copy of them and setup the scanner field. + */ +static _synctex_class_t synctex_class_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_vbox, /* Node type */ + &_synctex_new_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_box, /* log */ + &_synctex_display_vbox, /* display */ + &_synctex_implementation_0, /* parent */ + &_synctex_implementation_1, /* child */ + &_synctex_implementation_2, /* sibling */ + &_synctex_implementation_3, /* friend */ + &_synctex_implementation_4, /* next box */ + (_synctex_info_getter_t)&_synctex_implementation_5 +}; + +/* vertical box node creator */ +synctex_node_t _synctex_new_vbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_vert_box_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_vbox:(synctex_class_t)&synctex_class_vbox; + } + return node; +} + +# define SYNCTEX_HORIZ_V_IDX (SYNCTEX_DEPTH_IDX+1) +# define SYNCTEX_VERT_V_IDX (SYNCTEX_HORIZ_V_IDX+1) +# define SYNCTEX_WIDTH_V_IDX (SYNCTEX_VERT_V_IDX+1) +# define SYNCTEX_HEIGHT_V_IDX (SYNCTEX_WIDTH_V_IDX+1) +# define SYNCTEX_DEPTH_V_IDX (SYNCTEX_HEIGHT_V_IDX+1) +/* the corresponding info accessors */ +# define SYNCTEX_HORIZ_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HORIZ_V_IDX].INT +# define SYNCTEX_VERT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_VERT_V_IDX].INT +# define SYNCTEX_WIDTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_WIDTH_V_IDX].INT +# define SYNCTEX_HEIGHT_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_HEIGHT_V_IDX].INT +# define SYNCTEX_DEPTH_V(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_DEPTH_V_IDX].INT +# define SYNCTEX_ABS_WIDTH_V(NODE) ((SYNCTEX_WIDTH_V(NODE)>0?SYNCTEX_WIDTH_V(NODE):-SYNCTEX_WIDTH_V(NODE))) +# define SYNCTEX_ABS_HEIGHT_V(NODE) ((SYNCTEX_HEIGHT_V(NODE)>0?SYNCTEX_HEIGHT_V(NODE):-SYNCTEX_HEIGHT_V(NODE))) +# define SYNCTEX_ABS_DEPTH_V(NODE) ((SYNCTEX_DEPTH_V(NODE)>0?SYNCTEX_DEPTH_V(NODE):-SYNCTEX_DEPTH_V(NODE))) + +/* Horizontal boxes must contain visible size, because 0 width does not mean emptiness */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[5+SYNCTEX_DEPTH_V_IDX+1]; /*parent,child,sibling,friend,next box, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH, + * SYNCTEX_HORIZ_V,SYNCTEX_VERT_V,SYNCTEX_WIDTH_V,SYNCTEX_HEIGHT_V,SYNCTEX_DEPTH_V*/ +} synctex_horiz_box_node_t; + +synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner); +void _synctex_display_hbox(synctex_node_t node); +void _synctex_log_horiz_box(synctex_node_t sheet); + + +static _synctex_class_t synctex_class_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_hbox, /* Node type */ + &_synctex_new_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_horiz_box, /* log */ + &_synctex_display_hbox, /* display */ + &_synctex_implementation_0, /* parent */ + &_synctex_implementation_1, /* child */ + &_synctex_implementation_2, /* sibling */ + &_synctex_implementation_3, /* friend */ + &_synctex_implementation_4, /* next box */ + (_synctex_info_getter_t)&_synctex_implementation_5 +}; + +/* horizontal box node creator */ +synctex_node_t _synctex_new_hbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_horiz_box_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_hbox:(synctex_class_t)&synctex_class_hbox; + } + return node; +} + +/* This void box node implementation is either horizontal or vertical + * It does not contain a child field. + */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_DEPTH_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH,SYNCTEX_HEIGHT,SYNCTEX_DEPTH*/ +} synctex_void_box_node_t; + +synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner); +void _synctex_log_void_box(synctex_node_t sheet); +void _synctex_display_void_vbox(synctex_node_t node); + +static _synctex_class_t synctex_class_void_vbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_vbox,/* Node type */ + &_synctex_new_void_vbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_vbox,/* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +/* vertical void box node creator */ +synctex_node_t _synctex_new_void_vbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_void_vbox:(synctex_class_t)&synctex_class_void_vbox; + } + return node; +} + +synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner); +void _synctex_display_void_hbox(synctex_node_t node); + +static _synctex_class_t synctex_class_void_hbox = { + NULL, /* No scanner yet */ + synctex_node_type_void_hbox,/* Node type */ + &_synctex_new_void_hbox, /* creator */ + &_synctex_free_node, /* destructor */ + &_synctex_log_void_box, /* log */ + &_synctex_display_void_hbox,/* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +/* horizontal void box node creator */ +synctex_node_t _synctex_new_void_hbox(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_void_box_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_void_hbox:(synctex_class_t)&synctex_class_void_hbox; + } + return node; +} + +/* The medium nodes correspond to kern, glue, penalty and math nodes. */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_WIDTH_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT,SYNCTEX_WIDTH */ +} synctex_medium_node_t; + +#define SYNCTEX_IS_BOX(NODE)\ + ((NODE->class->type == synctex_node_type_vbox)\ + || (NODE->class->type == synctex_node_type_void_vbox)\ + || (NODE->class->type == synctex_node_type_hbox)\ + || (NODE->class->type == synctex_node_type_void_hbox)) + +#define SYNCTEX_HAS_CHILDREN(NODE) (NODE && SYNCTEX_CHILD(NODE)) + +void _synctex_log_medium_node(synctex_node_t node); + +/* math node creator */ +synctex_node_t _synctex_new_math(synctex_scanner_t scanner); +void _synctex_display_math(synctex_node_t node); + +static _synctex_class_t synctex_class_math = { + NULL, /* No scanner yet */ + synctex_node_type_math, /* Node type */ + &_synctex_new_math, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_math, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_math(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_math:(synctex_class_t)&synctex_class_math; + } + return node; +} + +/* kern node creator */ +synctex_node_t _synctex_new_kern(synctex_scanner_t scanner); +void _synctex_display_kern(synctex_node_t node); + +static _synctex_class_t synctex_class_kern = { + NULL, /* No scanner yet */ + synctex_node_type_kern, /* Node type */ + &_synctex_new_kern, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_kern, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_kern(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_kern:(synctex_class_t)&synctex_class_kern; + } + return node; +} + +/* The small nodes correspond to glue and boundary nodes. */ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[3+SYNCTEX_VERT_IDX+1]; /* parent,sibling,friend, + * SYNCTEX_TAG,SYNCTEX_LINE,SYNCTEX_COLUMN, + * SYNCTEX_HORIZ,SYNCTEX_VERT */ +} synctex_small_node_t; + +void _synctex_log_small_node(synctex_node_t node); +/* glue node creator */ +synctex_node_t _synctex_new_glue(synctex_scanner_t scanner); +void _synctex_display_glue(synctex_node_t node); + +static _synctex_class_t synctex_class_glue = { + NULL, /* No scanner yet */ + synctex_node_type_glue, /* Node type */ + &_synctex_new_glue, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_medium_node, /* log */ + &_synctex_display_glue, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; +synctex_node_t _synctex_new_glue(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_medium_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_glue:(synctex_class_t)&synctex_class_glue; + } + return node; +} + +/* boundary node creator */ +synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner); +void _synctex_display_boundary(synctex_node_t node); + +static _synctex_class_t synctex_class_boundary = { + NULL, /* No scanner yet */ + synctex_node_type_boundary, /* Node type */ + &_synctex_new_boundary, /* creator */ + &_synctex_free_leaf, /* destructor */ + &_synctex_log_small_node, /* log */ + &_synctex_display_boundary, /* display */ + &_synctex_implementation_0, /* parent */ + NULL, /* No child */ + &_synctex_implementation_1, /* sibling */ + &_synctex_implementation_2, /* friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_3 +}; + +synctex_node_t _synctex_new_boundary(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_small_node_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_boundary:(synctex_class_t)&synctex_class_boundary; + } + return node; +} + +# define SYNCTEX_NAME_IDX (SYNCTEX_TAG_IDX+1) +# define SYNCTEX_NAME(NODE) SYNCTEX_INFO(NODE)[SYNCTEX_NAME_IDX].PTR + +/* Input nodes only know about their sibling, which is another input node. + * The synctex information is the SYNCTEX_TAG and SYNCTEX_NAME*/ +typedef struct { + synctex_class_t class; + synctex_info_t implementation[1+SYNCTEX_NAME_IDX+1]; /* sibling, + * SYNCTEX_TAG,SYNCTEX_NAME */ +} synctex_input_t; + +synctex_node_t _synctex_new_input(synctex_scanner_t scanner); +void _synctex_free_input(synctex_node_t node); +void _synctex_display_input(synctex_node_t node); +void _synctex_log_input(synctex_node_t sheet); + +static _synctex_class_t synctex_class_input = { + NULL, /* No scanner yet */ + synctex_node_type_input, /* Node type */ + &_synctex_new_input, /* creator */ + &_synctex_free_input, /* destructor */ + &_synctex_log_input, /* log */ + &_synctex_display_input, /* display */ + NULL, /* No parent */ + NULL, /* No child */ + &_synctex_implementation_0, /* sibling */ + NULL, /* No friend */ + NULL, /* No next box */ + (_synctex_info_getter_t)&_synctex_implementation_1 +}; + +synctex_node_t _synctex_new_input(synctex_scanner_t scanner) { + synctex_node_t node = _synctex_malloc(sizeof(synctex_input_t)); + if (node) { + node->class = scanner?scanner->class+synctex_node_type_input:(synctex_class_t)&synctex_class_input; + } + return node; +} +void _synctex_free_input(synctex_node_t node){ + if (node) { + SYNCTEX_FREE(SYNCTEX_SIBLING(node)); + free(SYNCTEX_NAME(node)); + free(node); + } +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Navigation +# endif +synctex_node_t synctex_node_parent(synctex_node_t node) +{ + return SYNCTEX_PARENT(node); +} +synctex_node_t synctex_node_sheet(synctex_node_t node) +{ + while(node && node->class->type != synctex_node_type_sheet) { + node = SYNCTEX_PARENT(node); + } + /* exit the while loop either when node is NULL or node is a sheet */ + return node; +} +synctex_node_t synctex_node_child(synctex_node_t node) +{ + return SYNCTEX_CHILD(node); +} +synctex_node_t synctex_node_sibling(synctex_node_t node) +{ + return SYNCTEX_SIBLING(node); +} +synctex_node_t synctex_node_next(synctex_node_t node) { + if (SYNCTEX_CHILD(node)) { + return SYNCTEX_CHILD(node); + } +sibling: + if (SYNCTEX_SIBLING(node)) { + return SYNCTEX_SIBLING(node); + } + if ((node = SYNCTEX_PARENT(node))) { + if (node->class->type == synctex_node_type_sheet) {/* EXC_BAD_ACCESS? */ + return NULL; + } + goto sibling; + } + return NULL; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark CLASS +# endif + +/* Public node accessor: the type */ +synctex_node_type_t synctex_node_type(synctex_node_t node) { + if (node) { + return (((node)->class))->type; + } + return synctex_node_type_error; +} + +/* Public node accessor: the human readable type */ +const char * synctex_node_isa(synctex_node_t node) { +static const char * isa[synctex_node_number_of_types] = + {"Not a node","input","sheet","vbox","void vbox","hbox","void hbox","kern","glue","math","boundary"}; + return isa[synctex_node_type(node)]; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SYNCTEX_LOG +# endif + +# define SYNCTEX_LOG(NODE) SYNCTEX_MSG_SEND(NODE,log) + +/* Public node logger */ +void synctex_node_log(synctex_node_t node) { + SYNCTEX_LOG(node); +} + +# define SYNCTEX_DISPLAY(NODE) SYNCTEX_MSG_SEND(NODE,display) + +void synctex_node_display(synctex_node_t node) { + SYNCTEX_DISPLAY(node); +} + +void _synctex_display_input(synctex_node_t node) { + printf("....Input:%i:%s\n", + SYNCTEX_TAG(node), + SYNCTEX_NAME(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_log_sheet(synctex_node_t sheet) { + if (sheet) { + printf("%s:%i\n",synctex_node_isa(sheet),SYNCTEX_PAGE(sheet)); + printf("SELF:%p",(void *)sheet); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(sheet)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(sheet)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(sheet)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(sheet)); + } +} + +void _synctex_log_small_node(synctex_node_t node) { + printf("%s:%i,%i:%i,%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + printf("SELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_medium_node(synctex_node_t node) { + printf("%s:%i,%i:%i,%i:%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node)); + printf("SELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_void_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_horiz_box(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%i",SYNCTEX_LINE(node)); + printf(",%i",0); + printf(":%i",SYNCTEX_HORIZ(node)); + printf(",%i",SYNCTEX_VERT(node)); + printf(":%i",SYNCTEX_WIDTH(node)); + printf(",%i",SYNCTEX_HEIGHT(node)); + printf(",%i",SYNCTEX_DEPTH(node)); + printf("/%i",SYNCTEX_HORIZ_V(node)); + printf(",%i",SYNCTEX_VERT_V(node)); + printf(":%i",SYNCTEX_WIDTH_V(node)); + printf(",%i",SYNCTEX_HEIGHT_V(node)); + printf(",%i",SYNCTEX_DEPTH_V(node)); + printf("\nSELF:%p",(void *)node); + printf(" SYNCTEX_PARENT:%p",(void *)SYNCTEX_PARENT(node)); + printf(" SYNCTEX_CHILD:%p",(void *)SYNCTEX_CHILD(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); + printf(" SYNCTEX_FRIEND:%p\n",(void *)SYNCTEX_FRIEND(node)); +} + +void _synctex_log_input(synctex_node_t node) { + printf("%s",synctex_node_isa(node)); + printf(":%i",SYNCTEX_TAG(node)); + printf(",%s",SYNCTEX_NAME(node)); + printf(" SYNCTEX_SIBLING:%p",(void *)SYNCTEX_SIBLING(node)); +} + +void _synctex_display_sheet(synctex_node_t sheet) { + if (sheet) { + printf("....{%i\n",SYNCTEX_PAGE(sheet)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(sheet)); + printf("....}\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(sheet)); + } +} + +void _synctex_display_vbox(synctex_node_t node) { + printf("....[%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); + printf("....]\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_hbox(synctex_node_t node) { + printf("....(%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_CHILD(node)); + printf("....)\n"); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_void_vbox(synctex_node_t node) { + printf("....v%i,%i;%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_void_hbox(synctex_node_t node) { + printf("....h%i,%i:%i,%i:%i,%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node), + SYNCTEX_HEIGHT(node), + SYNCTEX_DEPTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_glue(synctex_node_t node) { + printf("....glue:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_math(synctex_node_t node) { + printf("....math:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_kern(synctex_node_t node) { + printf("....kern:%i,%i:%i,%i:%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node), + SYNCTEX_WIDTH(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +void _synctex_display_boundary(synctex_node_t node) { + printf("....boundary:%i,%i:%i,%i\n", + SYNCTEX_TAG(node), + SYNCTEX_LINE(node), + SYNCTEX_HORIZ(node), + SYNCTEX_VERT(node)); + SYNCTEX_DISPLAY(SYNCTEX_SIBLING(node)); +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark SCANNER +# endif + +/* Here are gathered all the possible status that the next scanning functions will return. + * All these functions return a status, and pass their result through pointers. + * Negative values correspond to errors. + * The management of the buffer is causing some significant overhead. + * Every function that may access the buffer returns a status related to the buffer and file state. + * status >= SYNCTEX_STATUS_OK means the function worked as expected + * status < SYNCTEX_STATUS_OK means the function did not work as expected + * status == SYNCTEX_STATUS_NOT_OK means the function did not work as expected but there is still some material to parse. + * status == SYNCTEX_STATUS_EOF means the function did not work as expected and there is no more material. + * statusfile) + +/* Actually, the minimum buffer size is driven by integer and float parsing. + * 0.123456789e123 + */ +# define SYNCTEX_BUFFER_MIN_SIZE 16 +# define SYNCTEX_BUFFER_SIZE 32768 + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Prototypes +# endif +void _synctex_log_void_box(synctex_node_t node); +void _synctex_log_box(synctex_node_t node); +void _synctex_log_horiz_box(synctex_node_t node); +void _synctex_log_input(synctex_node_t node); +synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr); +synctex_status_t _synctex_next_line(synctex_scanner_t scanner); +synctex_status_t _synctex_match_string(synctex_scanner_t scanner, const char * the_string); +synctex_status_t _synctex_decode_int(synctex_scanner_t scanner, int* value_ref); +synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref); +synctex_status_t _synctex_scan_input(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_preamble(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_float_and_dimension(synctex_scanner_t scanner, float * value_ref); +synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner); +int _synctex_scan_postamble(synctex_scanner_t scanner); +synctex_status_t _synctex_setup_visible_box(synctex_node_t box); +synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v); +synctex_status_t _synctex_scan_sheet(synctex_scanner_t scanner, synctex_node_t parent); +synctex_status_t _synctex_scan_nested_sheet(synctex_scanner_t scanner); +synctex_status_t _synctex_scan_content(synctex_scanner_t scanner); +int synctex_scanner_pre_x_offset(synctex_scanner_t scanner); +int synctex_scanner_pre_y_offset(synctex_scanner_t scanner); +const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner); +int _synctex_node_is_box(synctex_node_t node); +int _synctex_bail(void); + +/* Try to ensure that the buffer contains at least size bytes. + * Passing a huge size argument means the whole buffer length. + * Passing a null size argument means return the available buffer length, without reading the file. + * In that case, the return status is always SYNCTEX_STATUS_OK unless the given scanner is NULL, + * in which case, SYNCTEX_STATUS_BAD_ARGUMENT is returned. + * The value returned in size_ptr is the number of bytes now available in the buffer. + * This is a nonnegative integer, it may take the value 0. + * It is the responsibility of the caller to test whether this size is conforming to its needs. + * Negative values may return in case of error, actually + * when there was an error reading the synctex file. */ +synctex_status_t _synctex_buffer_get_available_size(synctex_scanner_t scanner, size_t * size_ptr) { + size_t available = 0; + if (NULL == scanner || NULL == size_ptr) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +# define size (* size_ptr) + if (size>SYNCTEX_BUFFER_SIZE){ + size = SYNCTEX_BUFFER_SIZE; + } + available = SYNCTEX_END - SYNCTEX_CUR; /* available is the number of unparsed chars in the buffer */ + if (size<=available) { + /* There are already sufficiently many characters in the buffer */ + size = available; + return SYNCTEX_STATUS_OK; + } + if (SYNCTEX_FILE) { + /* Copy the remaining part of the buffer to the beginning, + * then read the next part of the file */ + int already_read = 0; + if (available) { + memmove(SYNCTEX_START, SYNCTEX_CUR, available); + } + SYNCTEX_CUR = SYNCTEX_START + available; /* the next character after the move, will change. */ + /* Fill the buffer up to its end */ + already_read = gzread(SYNCTEX_FILE,(void *)SYNCTEX_CUR,SYNCTEX_BUFFER_SIZE - available); + if (already_read>0) { + /* We assume that 0already_read) { + /* There is a possible error in reading the file */ + int errnum = 0; + const char * error_string = gzerror(SYNCTEX_FILE, &errnum); + if (Z_ERRNO == errnum) { + /* There is an error in zlib caused by the file system */ + _synctex_error("gzread error from the file system (%i)",errno); + return SYNCTEX_STATUS_ERROR; + } else if (errnum) { + _synctex_error("gzread error (%i:%i,%s)",already_read,errnum,error_string); + return SYNCTEX_STATUS_ERROR; + } + } + /* Nothing was read, we are at the end of the file. */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_END = SYNCTEX_CUR; + SYNCTEX_CUR = SYNCTEX_START; + * SYNCTEX_END = '\0';/* Terminate the string properly.*/ + size = SYNCTEX_END - SYNCTEX_CUR; + return SYNCTEX_STATUS_EOF; /* there might be a bit of text left */ + } + /* We cannot enlarge the buffer because the end of the file was reached. */ + size = available; + return SYNCTEX_STATUS_EOF; +# undef size +} + +/* Used when parsing the synctex file. + * Advance to the next character starting a line. + * Actually, only '\n' is recognized as end of line marker. + * On normal completion, the returned value is the number of unparsed characters available in the buffer. + * In general, it is a positive value, 0 meaning that the end of file was reached. + * -1 is returned in case of error, actually because there was an error while feeding the buffer. + * When the function returns with no error, SYNCTEX_CUR points to the first character of the next line, if any. + * J. Laurens: Sat May 10 07:52:31 UTC 2008 + */ +synctex_status_t _synctex_next_line(synctex_scanner_t scanner) { + synctex_status_t status = SYNCTEX_STATUS_OK; + size_t available = 0; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +infinite_loop: + while(SYNCTEX_CUR=remaining_len) { + /* The buffer is sufficiently big to hold the expected number of characters. */ + if (strncmp((char *)SYNCTEX_CUR,the_string,remaining_len)) { + return SYNCTEX_STATUS_NOT_OK; + } +return_OK: + /* Advance SYNCTEX_CUR to the next character after the_string. */ + SYNCTEX_CUR += remaining_len; + return SYNCTEX_STATUS_OK; + } else if (strncmp((char *)SYNCTEX_CUR,the_string,available)) { + /* No need to goo further, this is not the expected string in the buffer. */ + return SYNCTEX_STATUS_NOT_OK; + } else if (SYNCTEX_FILE) { + /* The buffer was too small to contain remaining_len characters. + * We have to cut the string into pieces. */ + z_off_t offset = 0L; + /* the first part of the string is found, advance the_string to the next untested character. */ + the_string += available; + /* update the remaining length and the parsed length. */ + remaining_len -= available; + tested_len += available; + SYNCTEX_CUR += available; /* We validate the tested characters. */ + if (0 == remaining_len) { + /* Nothing left to test, we have found the given string, we return the length. */ + return tested_len; + } + /* We also have to record the current state of the file cursor because + * if the_string does not match, all this should be a totally blank operation, + * for which the file and buffer states should not be modified at all. + * In fact, the states of the buffer before and after this function are in general different + * but they are totally equivalent as long as the values of the buffer before SYNCTEX_CUR + * can be safely discarded. */ + offset = gztell(SYNCTEX_FILE); + /* offset now corresponds to the first character of the file that was not buffered. */ + available = SYNCTEX_CUR - SYNCTEX_START; /* available can be used as temporary placeholder. */ + /* available now corresponds to the number of chars that where already buffered and + * that match the head of the_string. If in fine the_string does not match, all these chars must be recovered + * because the buffer contents is completely replaced by _synctex_buffer_get_available_size. + * They were buffered from offset-len location in the file. */ + offset -= available; +more_characters: + /* There is still some work to be done, so read another bunch of file. + * This is the second call to _synctex_buffer_get_available_size, + * which means that the actual contents of the buffer will be discarded. + * We will definitely have to recover the previous state in case we do not find the expected string. */ + available = remaining_len; + status = _synctex_buffer_get_available_size(scanner,&available); + if (statusptr) { + SYNCTEX_CUR = end; + if (value_ref) { + * value_ref = result; + } + return SYNCTEX_STATUS_OK;/* Successfully scanned an int */ + } + return SYNCTEX_STATUS_NOT_OK;/* Could not scan an int */ +} + +/* The purpose of this function is to read a string. + * A string is an array of characters from the current parser location + * and before the next '\n' character. + * If a string was properly decoded, it is returned in value_ref and + * the cursor points to the new line marker. + * The returned string was alloced on the heap, the caller is the owner and + * is responsible to free it in due time. + * If no string is parsed, * value_ref is undefined. + * The maximum length of a string that a scanner can decode is platform dependent, namely UINT_MAX. + * If you just want to blindly parse the file up to the end of the current line, + * use _synctex_next_line instead. + * On return, the scanner cursor is unchanged if a string could not be scanned or + * points to the terminating '\n' character otherwise. As a consequence, + * _synctex_next_line is necessary after. + * If either scanner or value_ref is NULL, it is considered as an error and + * SYNCTEX_STATUS_BAD_ARGUMENT is returned. + */ +synctex_status_t _synctex_decode_string(synctex_scanner_t scanner, char ** value_ref) { + char * end = NULL; + size_t current_size = 0; + size_t new_size = 0; + size_t len = 0;/* The number of bytes to copy */ + size_t available = 0; + synctex_status_t status = 0; + if (NULL == scanner || NULL == value_ref) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* The buffer must at least contain one character: the '\n' end of line marker */ + if (SYNCTEX_CUR>=SYNCTEX_END) { + available = 1; + status = _synctex_buffer_get_available_size(scanner,&available); + if (status < 0) { + return status; + } + if (0 == available) { + return SYNCTEX_STATUS_EOF; + } + } + /* Now we are sure that there is at least one available character, either because + * SYNCTEX_CUR was already < SYNCTEX_END, or because the buffer has been properly filled. */ + /* end will point to the next unparsed '\n' character in the file, when mapped to the buffer. */ + end = SYNCTEX_CUR; + * value_ref = NULL;/* Initialize, it will be realloc'ed */ + /* We scan all the characters up to the next '\n' */ +next_character: + if (endUINT_MAX-len-1) { + /* But we have reached the limit: we do not have current_size+len+1>UINT_MAX. + * We return the missing amount of memory. + * This will never occur in practice. */ + return UINT_MAX-len-1 - current_size; + } + new_size = current_size+len; + /* We have current_size+len+1<=UINT_MAX + * or equivalently new_sizeUINT_MAX-len-1) { + /* We have reached the limit. */ + _synctex_error("limit reached (missing %i).",current_size-(UINT_MAX-len-1)); + return SYNCTEX_STATUS_ERROR; + } + new_size = current_size+len; + if ((* value_ref = realloc(* value_ref,new_size+1)) != NULL) { + if (memcpy((*value_ref)+current_size,SYNCTEX_CUR,len)) { + (* value_ref)[new_size]='\0'; /* Terminate the string */ + SYNCTEX_CUR = SYNCTEX_END;/* Advance the cursor to the end of the bufer */ + return SYNCTEX_STATUS_OK; + } + free(* value_ref); + * value_ref = NULL; + _synctex_error("could not copy memory (2)."); + return SYNCTEX_STATUS_ERROR; + } + /* Huge memory problem */ + _synctex_error("could not allocate memory (2)."); + return SYNCTEX_STATUS_ERROR; + } +} + +/* Used when parsing the synctex file. + * Read an Input record. + */ +synctex_status_t _synctex_scan_input(synctex_scanner_t scanner) { + synctex_status_t status = 0; + size_t available = 0; + synctex_node_t input = NULL; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + status = _synctex_match_string(scanner,"Input:"); + if (statusinput); + scanner->input = input; + return _synctex_next_line(scanner);/* read the line termination character, if any */ + /* Now, set up the path */ +} + +typedef synctex_status_t (*synctex_decoder_t)(synctex_scanner_t,void *); + +synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder); + +/* Used when parsing the synctex file. + * Read one of the settings. + * On normal completion, returns SYNCTEX_STATUS_OK. + * On error, returns SYNCTEX_STATUS_ERROR. + * Both arguments must not be NULL. + * On return, the scanner points to the next character after the decoded object whatever it is. + * It is the responsibility of the caller to prepare the scanner for the next line. + */ +synctex_status_t _synctex_scan_named(synctex_scanner_t scanner,const char * name,void * value_ref,synctex_decoder_t decoder) { + synctex_status_t status = 0; + if (NULL == scanner || NULL == name || NULL == value_ref || NULL == decoder) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } +not_found: + status = _synctex_match_string(scanner,name); + if (statusversion),(synctex_decoder_t)&_synctex_decode_int); + if (statusoutput_fmt),(synctex_decoder_t)&_synctex_decode_string); + if (statuspre_magnification),(synctex_decoder_t)&_synctex_decode_int); + if (statuspre_unit),(synctex_decoder_t)&_synctex_decode_int); + if (statuspre_x_offset),(synctex_decoder_t)&_synctex_decode_int); + if (statuspre_y_offset),(synctex_decoder_t)&_synctex_decode_int); + if (status= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536; + } else if (status= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536/2.54f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"mm")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f*65536/25.4f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"pt")) >= SYNCTEX_STATUS_OK) { + f *= 65536.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"bp")) >= SYNCTEX_STATUS_OK) { + f *= 72.27f/72*65536.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"pc")) >= SYNCTEX_STATUS_OK) { + f *= 12.0*65536.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"sp")) >= SYNCTEX_STATUS_OK) { + f *= 1.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"dd")) >= SYNCTEX_STATUS_OK) { + f *= 1238.0f/1157*65536.0f; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"cc")) >= SYNCTEX_STATUS_OK) { + f *= 14856.0f/1157*65536; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"nd")) >= SYNCTEX_STATUS_OK) { + f *= 685.0f/642*65536; + } else if (status<0) { + goto report_unit_error; + } else if ((status = _synctex_match_string(scanner,"nc")) >= SYNCTEX_STATUS_OK) { + f *= 1370.0f/107*65536; + } else if (status<0) { + goto report_unit_error; + } + *value_ref = f; + return SYNCTEX_STATUS_OK; +} + +/* parse the post scriptum + * SYNCTEX_STATUS_OK is returned on completion + * a negative error is returned otherwise */ +synctex_status_t _synctex_scan_post_scriptum(synctex_scanner_t scanner) { + synctex_status_t status = 0; + char * endptr = NULL; +#ifdef HAVE_SETLOCALE + char * loc = setlocale(LC_NUMERIC, NULL); +#endif + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* Scan the file until a post scriptum line is found */ +post_scriptum_not_found: + status = _synctex_match_string(scanner,"Post scriptum:"); + if (statusunit = strtod(SYNCTEX_CUR,&endptr); +#ifdef HAVE_SETLOCALE + setlocale(LC_NUMERIC, loc); +#endif + if (endptr == SYNCTEX_CUR) { + _synctex_error("bad magnification in the post scriptum, a float was expected."); + return SYNCTEX_STATUS_ERROR; + } + if (scanner->unit<=0) { + _synctex_error("bad magnification in the post scriptum, a positive float was expected."); + return SYNCTEX_STATUS_ERROR; + } + SYNCTEX_CUR = endptr; + goto next_line; + } + if (statusx_offset)); + if (statusy_offset)); + if (statuscount),(synctex_decoder_t)&_synctex_decode_int); + if (status < SYNCTEX_STATUS_EOF) { + return status; /* forward the error */ + } else if (status < SYNCTEX_STATUS_OK) { /* No Count record found */ + status = _synctex_next_line(scanner); /* Advance one more line */ + if (statusclass->type) { + case synctex_node_type_hbox: + if (SYNCTEX_INFO(box) != NULL) { + SYNCTEX_HORIZ_V(box) = SYNCTEX_HORIZ(box); + SYNCTEX_VERT_V(box) = SYNCTEX_VERT(box); + SYNCTEX_WIDTH_V(box) = SYNCTEX_WIDTH(box); + SYNCTEX_HEIGHT_V(box) = SYNCTEX_HEIGHT(box); + SYNCTEX_DEPTH_V(box) = SYNCTEX_DEPTH(box); + return SYNCTEX_STATUS_OK; + } + return SYNCTEX_STATUS_ERROR; + } + } + return SYNCTEX_STATUS_BAD_ARGUMENT; +} + +/* This method is sent to an horizontal box to setup the visible size + * Some box have 0 width but do contain text material. + * With this method, one can enlarge the box to contain the given point (h,v). + */ +synctex_status_t _synctex_horiz_box_setup_visible(synctex_node_t node,int h, int v) { +# ifdef __DARWIN_UNIX03 +# pragma unused(v) +# endif + int itsBtm, itsTop; + if (NULL == node || node->class->type != synctex_node_type_hbox) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + if (SYNCTEX_WIDTH_V(node)<0) { + itsBtm = SYNCTEX_HORIZ_V(node); + itsTop = SYNCTEX_HORIZ_V(node)-SYNCTEX_WIDTH_V(node); + if (hitsTop) { + SYNCTEX_WIDTH_V(node) = SYNCTEX_HORIZ_V(node) - h; + } + } else { + itsBtm = SYNCTEX_HORIZ_V(node); + itsTop = SYNCTEX_HORIZ_V(node)+SYNCTEX_WIDTH_V(node); + if (hitsTop) { + SYNCTEX_WIDTH_V(node) = h - SYNCTEX_HORIZ_V(node); + } + } + return SYNCTEX_STATUS_OK; +} + +/* Here are the control characters that strat each line of the synctex output file. + * Their values define the meaning of the line. + */ +# define SYNCTEX_CHAR_BEGIN_SHEET '{' +# define SYNCTEX_CHAR_END_SHEET '}' +# define SYNCTEX_CHAR_BEGIN_VBOX '[' +# define SYNCTEX_CHAR_END_VBOX ']' +# define SYNCTEX_CHAR_BEGIN_HBOX '(' +# define SYNCTEX_CHAR_END_HBOX ')' +# define SYNCTEX_CHAR_ANCHOR '!' +# define SYNCTEX_CHAR_VOID_VBOX 'v' +# define SYNCTEX_CHAR_VOID_HBOX 'h' +# define SYNCTEX_CHAR_KERN 'k' +# define SYNCTEX_CHAR_GLUE 'g' +# define SYNCTEX_CHAR_MATH '$' +# define SYNCTEX_CHAR_BOUNDARY 'x' + +# define SYNCTEX_RETURN(STATUS) return STATUS; + +/* Used when parsing the synctex file. A '{' character has just been parsed. + * The purpose is to gobble everything until the closing '}'. + * Actually only one nesting depth has been observed when using the clip option + * of \includegraphics option. Here we use arbitrary level of depth. + */ +synctex_status_t _synctex_scan_nested_sheet(synctex_scanner_t scanner) { + unsigned int depth = 0; +deeper: + ++depth; + if (_synctex_next_line(scanner)0) { + goto scan_next_line; + } else { + SYNCTEX_RETURN(SYNCTEX_STATUS_OK); + } + } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_BEGIN_SHEET) { + ++SYNCTEX_CUR; + goto deeper; + + } else if (_synctex_next_line(scanner)class->type != synctex_node_type_sheet + || _synctex_next_line(scanner)0){ + _synctex_error("Uncomplete sheet(0)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } else { + goto prepare_loop; + } + } + _synctex_bail(); +/* The child loop means that we go do one level, when we just created a box node, + * the next node created is a child of this box. */ +child_loop: + if (SYNCTEX_CURclass->type == synctex_node_type_vbox) { + #define SYNCTEX_UPDATE_BOX_FRIEND(NODE)\ + friend_index = ((SYNCTEX_INFO(NODE))[SYNCTEX_TAG_IDX].INT+(SYNCTEX_INFO(NODE))[SYNCTEX_LINE_IDX].INT)%(scanner->number_of_lists);\ + SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ + (scanner->lists_of_friends)[friend_index] = NODE; + if (NULL == SYNCTEX_CHILD(parent)) { + /* only void boxes are friends */ + SYNCTEX_UPDATE_BOX_FRIEND(parent); + } + child = parent; + parent = SYNCTEX_PARENT(child); + } else { + _synctex_error("Unexpected end of vbox, ignored."); + } + if (_synctex_next_line(scanner)class->type == synctex_node_type_hbox) { + if (NULL == child) { + /* Only boxes with no children are friends, + * boxes with children are indirectly friends through one of their descendants. */ + SYNCTEX_UPDATE_BOX_FRIEND(parent); + } + /* setting the next horizontal box at the end ensures that a child is recorded before any of its ancestors. */ + SYNCTEX_SET_NEXT_HORIZ_BOX(box,parent); + box = parent; + child = parent; + parent = SYNCTEX_PARENT(child); + } else { + _synctex_error("Unexpected enf of hbox, ignored."); + } + if (_synctex_next_line(scanner)number_of_lists);\ + SYNCTEX_SET_FRIEND(NODE,(scanner->lists_of_friends)[friend_index]);\ + (scanner->lists_of_friends)[friend_index] = NODE; + SYNCTEX_UPDATE_FRIEND(child); + goto sibling_loop; + } else { + _synctex_error("Can't create vbox record."); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } else if (*SYNCTEX_CUR == SYNCTEX_CHAR_VOID_HBOX) { + ++SYNCTEX_CUR; + if (NULL != (child = _synctex_new_void_hbox(scanner)) + && NULL != (info = SYNCTEX_INFO(child))) { + if (SYNCTEX_DECODE_FAILED(SYNCTEX_TAG_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_LINE_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HORIZ_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_VERT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_WIDTH_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_HEIGHT_IDX) + || SYNCTEX_DECODE_FAILED(SYNCTEX_DEPTH_IDX) + || _synctex_next_line(scanner)0){ + _synctex_error("Uncomplete sheet(0)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } else { + goto child_loop; + } + } + _synctex_bail(); +/* The vertical loop means that we are on the same level, for example when we just ended a box. + * If a node is created now, it will be a sibling of the current node, sharing the same parent. */ +sibling_loop: + if (SYNCTEX_CUR0){ + goto sibling_loop; + } else { + _synctex_error("Uncomplete sheet(2)"); + SYNCTEX_RETURN(SYNCTEX_STATUS_ERROR); + } + } +# undef SYNCTEX_DECODE_FAILED +} + +/* Used when parsing the synctex file + */ +synctex_status_t _synctex_scan_content(synctex_scanner_t scanner) { + synctex_node_t sheet = NULL; + synctex_status_t status = 0; + if (NULL == scanner) { + return SYNCTEX_STATUS_BAD_ARGUMENT; + } + /* set up the lists of friends */ + if (NULL == scanner->lists_of_friends) { + scanner->number_of_lists = 1024; + scanner->lists_of_friends = (synctex_node_t *)_synctex_malloc(scanner->number_of_lists*sizeof(synctex_node_t)); + if (NULL == scanner->lists_of_friends) { + _synctex_error("malloc:2"); + return SYNCTEX_STATUS_ERROR; + } + } + /* Find where this section starts */ +content_not_found: + status = _synctex_match_string(scanner,"Content:"); + if (statussheet); + scanner->sheet = sheet; + sheet = NULL; + /* Now read the list of Inputs between 2 sheets. */ + do { + status = _synctex_scan_input(scanner); + if (status= SYNCTEX_STATUS_OK); + goto next_sheet; +} + +int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_modeRef); + +/* Where the synctex scanner is created. */ +synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse) { + gzFile file = NULL; + char * synctex = NULL; + synctex_scanner_t scanner = NULL; + synctex_io_mode_t io_mode = 0; + /* Here we assume that int are smaller than void * */ + if (sizeof(int)>sizeof(void*)) { + _synctex_error("INTERNAL INCONSISTENCY: int's are unexpectedly bigger than pointers, bailing out."); + return NULL; + } + /* We ensure that SYNCTEX_BUFFER_SIZE < UINT_MAX, I don't know if it makes sense... */ + if (SYNCTEX_BUFFER_SIZE >= UINT_MAX) { + _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (1)"); + return NULL; + } + /* for integers: */ + if (SYNCTEX_BUFFER_SIZE < SYNCTEX_BUFFER_MIN_SIZE) { + _synctex_error("SyncTeX BUG: Internal inconsistency, bad SYNCTEX_BUFFER_SIZE (2)"); + return NULL; + } + /* now open the synctex file */ + if (_synctex_open(output,build_directory,&synctex,&file,synctex_ADD_QUOTES,&io_mode) || !file) { + if (_synctex_open(output,build_directory,&synctex,&file,synctex_DONT_ADD_QUOTES,&io_mode) || !file) { + return NULL; + } + } + scanner = (synctex_scanner_t)_synctex_malloc(sizeof(_synctex_scanner_t)); + if (NULL == scanner) { + _synctex_error("SyncTeX: malloc problem"); + free(synctex); + gzclose(file); + return NULL; + } + /* make a private copy of output for the scanner */ + if (NULL == (scanner->output = (char *)malloc(strlen(output)+1))){ + _synctex_error("! synctex_scanner_new_with_output_file: Memory problem (2), scanner's output is not reliable."); + } else if (scanner->output != strcpy(scanner->output,output)) { + _synctex_error("! synctex_scanner_new_with_output_file: Copy problem, scanner's output is not reliable."); + } + scanner->synctex = synctex;/* Now the scanner owns synctex */ + SYNCTEX_FILE = file; + return parse? synctex_scanner_parse(scanner):scanner; +} + +int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref); + +/* This functions opens the file at the "output" given location. + * It manages the problem of quoted filenames that appear with pdftex and filenames containing the space character. + * In TeXLive 2008, the synctex file created with pdftex did contain unexpected quotes. + * This function will remove them if possible. + * All the reference arguments will take a value on return. They must be non NULL. + * 0 on success, non 0 on error. */ +int __synctex_open(const char * output, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref) { + if (synctex_name_ref && file_ref && io_mode_ref) { + /* 1 local variables that uses dynamic memory */ + char * synctex_name = NULL; + gzFile the_file = NULL; + char * quoteless_synctex_name = NULL; + size_t size = 0; + synctex_io_mode_t io_mode = *io_mode_ref; + const char * mode = _synctex_get_io_mode_name(io_mode); + /* now create the synctex file name */ + size = strlen(output)+strlen(synctex_suffix)+strlen(synctex_suffix_gz)+1; + synctex_name = (char *)malloc(size); + if (NULL == synctex_name) { + _synctex_error("! __synctex_open: Memory problem (1)\n"); + return 1; + } + /* we have reserved for synctex enough memory to copy output (including its 2 eventual quotes), both suffices, + * including the terminating character. size is free now. */ + if (synctex_name != strcpy(synctex_name,output)) { + _synctex_error("! __synctex_open: Copy problem\n"); +return_on_error: + free(synctex_name); + free(quoteless_synctex_name); + return 2; + } + /* remove the last path extension if any */ + _synctex_strip_last_path_extension(synctex_name); + if (!strlen(synctex_name)) { + goto return_on_error; + } + /* now insert quotes. */ + if (add_quotes) { + char * quoted = NULL; + if (_synctex_copy_with_quoting_last_path_component(synctex_name,"ed,size) || (NULL == quoted)) { + /* There was an error or quoting does not make sense: */ + goto return_on_error; + } + quoteless_synctex_name = synctex_name; + synctex_name = quoted; + } + /* Now add to synctex_name the first path extension. */ + if (synctex_name != strcat(synctex_name,synctex_suffix)){ + _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix); + goto return_on_error; + } + /* Add to quoteless_synctex_name as well, if relevant. */ + if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix))){ + free(quoteless_synctex_name); + quoteless_synctex_name = NULL; + } + if (NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); + goto return_on_error; + } + /* Apparently, there is no uncompressed synctex file. Try the compressed version */ + if (synctex_name != strcat(synctex_name,synctex_suffix_gz)){ + _synctex_error("! __synctex_open: Concatenation problem (can't add suffix '%s')\n",synctex_suffix_gz); + goto return_on_error; + } + io_mode |= synctex_io_gz_mask; + mode = _synctex_get_io_mode_name(io_mode); /* the file is a compressed and is a binary file, this caused errors on Windows */ + /* Add the suffix to the quoteless_synctex_name as well. */ + if (quoteless_synctex_name && (quoteless_synctex_name != strcat(quoteless_synctex_name,synctex_suffix_gz))){ + free(quoteless_synctex_name); + quoteless_synctex_name = NULL; + } + if (NULL == (the_file = gzopen(synctex_name,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("SyncTeX: could not open %s, error %i\n",synctex_name,errno); + } + goto return_on_error; + } + } + /* At this point, the file is properly open. + * If we are in the add_quotes mode, we change the file name by removing the quotes. */ + if (quoteless_synctex_name) { + gzclose(the_file); + if (rename(synctex_name,quoteless_synctex_name)) { + _synctex_error("SyncTeX: could not rename %s to %s, error %i\n",synctex_name,quoteless_synctex_name,errno); + /* We could not rename, reopen the file with the quoted name. */ + if (NULL == (the_file = gzopen(synctex_name,mode))) { + /* No luck, could not re open this file, something has happened meanwhile */ + if (errno != ENOENT) { + /* The file does not exist any more, it has certainly be removed somehow + * this is a lower level error, I can't do anything. */ + _synctex_error("SyncTeX: could not open again %s, error %i\n",synctex_name,errno); + } + goto return_on_error; + } + } else { + /* The file has been successfully renamed */ + if (NULL == (the_file = gzopen(quoteless_synctex_name,mode))) { + /* Could not open this file */ + if (errno != ENOENT) { + /* The file does exist, this is a lower level error, I can't do anything. */ + _synctex_error("SyncTeX: could not open renamed %s, error %i\n",quoteless_synctex_name,errno); + } + goto return_on_error; + } + /* The quote free file name should replace the old one:*/ + free(synctex_name); + synctex_name = quoteless_synctex_name; + quoteless_synctex_name = NULL; + } + } + /* The operation is successfull, return the arguments by value. */ + * file_ref = the_file; + * io_mode_ref = io_mode; + * synctex_name_ref = synctex_name; + return 0; + } + return 3; /* Bad parameter. */ +} + +/* Opens the ouput file, taking into account the eventual build_directory. + * 0 on success, non 0 on error. */ +int _synctex_open(const char * output, const char * build_directory, char ** synctex_name_ref, gzFile * file_ref, synctex_bool_t add_quotes, synctex_io_mode_t * io_mode_ref) { +# define synctex_name (*synctex_name_ref) +# define the_file (*file_ref) + int result = __synctex_open(output,synctex_name_ref,file_ref,add_quotes,io_mode_ref); + if ((result || !*file_ref) && build_directory && strlen(build_directory)) { + char * build_output; + const char *lpc; + size_t size; + synctex_bool_t is_absolute; + build_output = NULL; + lpc = _synctex_last_path_component(output); + size = strlen(build_directory)+strlen(lpc)+2; /* One for the '/' and one for the '\0'. */ + is_absolute = _synctex_path_is_absolute(build_directory); + if (!is_absolute) { + size += strlen(output); + } + if ((build_output = (char *)malloc(size))) { + if (is_absolute) { + build_output[0] = '\0'; + } else { + if (build_output != strcpy(build_output,output)) { + return -4; + } + build_output[lpc-output]='\0'; + } + if (build_output == strcat(build_output,build_directory)) { + /* Append a path separator if necessary. */ + if (!SYNCTEX_IS_PATH_SEPARATOR(build_output[strlen(build_directory)-1])) { + if (build_output != strcat(build_output,"/")) { + return -2; + } + } + /* Append the last path component of the output. */ + if (build_output != strcat(build_output,lpc)) { + return -3; + } + return __synctex_open(build_output,synctex_name_ref,file_ref,add_quotes,io_mode_ref); + } + } + return -1; + } + return result; +# undef synctex_name +# undef the_file +} + +/* The scanner destructor + */ +void synctex_scanner_free(synctex_scanner_t scanner) { + if (NULL == scanner) { + return; + } + if (SYNCTEX_FILE) { + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + } + SYNCTEX_FREE(scanner->sheet); + SYNCTEX_FREE(scanner->input); + free(SYNCTEX_START); + free(scanner->output_fmt); + free(scanner->output); + free(scanner->synctex); + free(scanner->lists_of_friends); + free(scanner); +} + +/* Where the synctex scanner parses the contents of the file. */ +synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner) { + synctex_status_t status = 0; + if (!scanner || scanner->flags.has_parsed) { + return scanner; + } + scanner->flags.has_parsed=1; + scanner->pre_magnification = 1000; + scanner->pre_unit = 8192; + scanner->pre_x_offset = scanner->pre_y_offset = 578; + /* initialize the offset with a fake unprobable value, + * If there is a post scriptum section, this value will be overriden by the real life value */ + scanner->x_offset = scanner->y_offset = 6.027e23f; + scanner->class[synctex_node_type_sheet] = synctex_class_sheet; + scanner->class[synctex_node_type_input] = synctex_class_input; + (scanner->class[synctex_node_type_input]).scanner = scanner; + (scanner->class[synctex_node_type_sheet]).scanner = scanner; + scanner->class[synctex_node_type_vbox] = synctex_class_vbox; + (scanner->class[synctex_node_type_vbox]).scanner = scanner; + scanner->class[synctex_node_type_void_vbox] = synctex_class_void_vbox; + (scanner->class[synctex_node_type_void_vbox]).scanner = scanner; + scanner->class[synctex_node_type_hbox] = synctex_class_hbox; + (scanner->class[synctex_node_type_hbox]).scanner = scanner; + scanner->class[synctex_node_type_void_hbox] = synctex_class_void_hbox; + (scanner->class[synctex_node_type_void_hbox]).scanner = scanner; + scanner->class[synctex_node_type_kern] = synctex_class_kern; + (scanner->class[synctex_node_type_kern]).scanner = scanner; + scanner->class[synctex_node_type_glue] = synctex_class_glue; + (scanner->class[synctex_node_type_glue]).scanner = scanner; + scanner->class[synctex_node_type_math] = synctex_class_math; + (scanner->class[synctex_node_type_math]).scanner = scanner; + scanner->class[synctex_node_type_boundary] = synctex_class_boundary; + (scanner->class[synctex_node_type_boundary]).scanner = scanner; + SYNCTEX_START = (char *)malloc(SYNCTEX_BUFFER_SIZE+1); /* one more character for null termination */ + if (NULL == SYNCTEX_START) { + _synctex_error("SyncTeX: malloc error"); + synctex_scanner_free(scanner); + return NULL; + } + SYNCTEX_END = SYNCTEX_START+SYNCTEX_BUFFER_SIZE; + /* SYNCTEX_END always points to a null terminating character. + * Maybe there is another null terminating character between SYNCTEX_CUR and SYNCTEX_END-1. + * At least, we are sure that SYNCTEX_CUR points to a string covering a valid part of the memory. */ + *SYNCTEX_END = '\0'; + SYNCTEX_CUR = SYNCTEX_END; + status = _synctex_scan_preamble(scanner); + if (statuspre_unit)/65536 pt = (scanner->pre_unit)/65781.76 bp + * 1 pt = 65536 sp */ + if (scanner->pre_unit<=0) { + scanner->pre_unit = 8192; + } + if (scanner->pre_magnification<=0) { + scanner->pre_magnification = 1000; + } + if (scanner->unit <= 0) { + /* no post magnification */ + scanner->unit = scanner->pre_unit / 65781.76;/* 65781.76 or 65536.0*/ + } else { + /* post magnification */ + scanner->unit *= scanner->pre_unit / 65781.76; + } + scanner->unit *= scanner->pre_magnification / 1000.0; + if (scanner->x_offset > 6e23) { + /* no post offset */ + scanner->x_offset = scanner->pre_x_offset * (scanner->pre_unit / 65781.76); + scanner->y_offset = scanner->pre_y_offset * (scanner->pre_unit / 65781.76); + } else { + /* post offset */ + scanner->x_offset /= 65781.76f; + scanner->y_offset /= 65781.76f; + } + return scanner; + #undef SYNCTEX_FILE +} + +/* Scanner accessors. + */ +int synctex_scanner_pre_x_offset(synctex_scanner_t scanner){ + return scanner?scanner->pre_x_offset:0; +} +int synctex_scanner_pre_y_offset(synctex_scanner_t scanner){ + return scanner?scanner->pre_y_offset:0; +} +int synctex_scanner_x_offset(synctex_scanner_t scanner){ + return scanner?scanner->x_offset:0; +} +int synctex_scanner_y_offset(synctex_scanner_t scanner){ + return scanner?scanner->y_offset:0; +} +float synctex_scanner_magnification(synctex_scanner_t scanner){ + return scanner?scanner->unit:1; +} +void synctex_scanner_display(synctex_scanner_t scanner) { + if (NULL == scanner) { + return; + } + printf("The scanner:\noutput:%s\noutput_fmt:%s\nversion:%i\n",scanner->output,scanner->output_fmt,scanner->version); + printf("pre_unit:%i\nx_offset:%i\ny_offset:%i\n",scanner->pre_unit,scanner->pre_x_offset,scanner->pre_y_offset); + printf("count:%i\npost_magnification:%f\npost_x_offset:%f\npost_y_offset:%f\n", + scanner->count,scanner->unit,scanner->x_offset,scanner->y_offset); + printf("The input:\n"); + SYNCTEX_DISPLAY(scanner->input); + if (scanner->count<1000) { + printf("The sheets:\n"); + SYNCTEX_DISPLAY(scanner->sheet); + printf("The friends:\n"); + if (scanner->lists_of_friends) { + int i = scanner->number_of_lists; + synctex_node_t node; + while(i--) { + printf("Friend index:%i\n",i); + node = (scanner->lists_of_friends)[i]; + while(node) { + printf("%s:%i,%i\n", + synctex_node_isa(node), + SYNCTEX_TAG(node), + SYNCTEX_LINE(node) + ); + node = SYNCTEX_FRIEND(node); + } + } + } + } else { + printf("SyncTeX Warning: Too many objects\n"); + } +} +/* Public*/ +const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag) { + synctex_node_t input = NULL; + if (NULL == scanner) { + return NULL; + } + input = scanner->input; + do { + if (tag == SYNCTEX_TAG(input)) { + return (SYNCTEX_NAME(input)); + } + } while((input = SYNCTEX_SIBLING(input)) != NULL); + return NULL; +} + +int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); +int _synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { + synctex_node_t input = NULL; + if (NULL == scanner) { + return 0; + } + input = scanner->input; + do { + if (_synctex_is_equivalent_file_name(name,(SYNCTEX_NAME(input)))) { + return SYNCTEX_TAG(input); + } + } while((input = SYNCTEX_SIBLING(input)) != NULL); + return 0; +} + +int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name) { + size_t char_index = strlen(name); + if ((scanner = synctex_scanner_parse(scanner)) && (0 < char_index)) { + /* the name is not void */ + char_index -= 1; + if (!SYNCTEX_IS_PATH_SEPARATOR(name[char_index])) { + /* the last character of name is not a path separator */ + int result = _synctex_scanner_get_tag(scanner,name); + if (result) { + return result; + } else { + /* the given name was not the one known by TeX + * try a name relative to the enclosing directory of the scanner->output file */ + const char * relative = name; + const char * ptr = scanner->output; + while((strlen(relative) > 0) && (strlen(ptr) > 0) && (*relative == *ptr)) + { + relative += 1; + ptr += 1; + } + /* Find the last path separator before relative */ + while(relative > name) { + if (SYNCTEX_IS_PATH_SEPARATOR(*(relative-1))) { + break; + } + relative -= 1; + } + if ((relative > name) && (result = _synctex_scanner_get_tag(scanner,relative))) { + return result; + } + if (SYNCTEX_IS_PATH_SEPARATOR(name[0])) { + /* No tag found for the given absolute name, + * Try each relative path starting from the shortest one */ + while(0input:NULL; +} +const char * synctex_scanner_get_output_fmt(synctex_scanner_t scanner) { + return NULL != scanner && scanner->output_fmt?scanner->output_fmt:""; +} +const char * synctex_scanner_get_output(synctex_scanner_t scanner) { + return NULL != scanner && scanner->output?scanner->output:""; +} +const char * synctex_scanner_get_synctex(synctex_scanner_t scanner) { + return NULL != scanner && scanner->synctex?scanner->synctex:""; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node attributes +# endif +int synctex_node_h(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_HORIZ(node); +} +int synctex_node_v(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_VERT(node); +} +int synctex_node_width(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_WIDTH(node); +} +int synctex_node_box_h(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_HORIZ(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_v(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_VERT(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_width(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_WIDTH(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_height(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_HEIGHT(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +int synctex_node_box_depth(synctex_node_t node){ + if (!node) { + return 0; + } + if (SYNCTEX_IS_BOX(node)) { +result: + return SYNCTEX_DEPTH(node); + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Public node visible attributes +# endif +float synctex_node_visible_h(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; +} +float synctex_node_visible_v(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; +} +float synctex_node_visible_width(synctex_node_t node){ + if (!node) { + return 0; + } + return SYNCTEX_WIDTH(node)*node->class->scanner->unit; +} +float synctex_node_box_visible_h(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_HORIZ(node)*node->class->scanner->unit+node->class->scanner->x_offset; + case synctex_node_type_hbox: +result: + return SYNCTEX_HORIZ_V(node)*node->class->scanner->unit+node->class->scanner->x_offset; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_v(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_VERT(node)*node->class->scanner->unit+node->class->scanner->y_offset; + case synctex_node_type_hbox: +result: + return SYNCTEX_VERT_V(node)*node->class->scanner->unit+node->class->scanner->y_offset; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_width(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_WIDTH(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_WIDTH_V(node)*node->class->scanner->unit; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_height(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_HEIGHT(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_HEIGHT_V(node)*node->class->scanner->unit; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +float synctex_node_box_visible_depth(synctex_node_t node){ + if (!node) { + return 0; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + return SYNCTEX_DEPTH(node)*node->class->scanner->unit; + case synctex_node_type_hbox: +result: + return SYNCTEX_DEPTH_V(node)*node->class->scanner->unit; + } + if ((node = SYNCTEX_PARENT(node)) && (node->class->type != synctex_node_type_sheet)) { + goto result; + } + return 0; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Other public node attributes +# endif + +int synctex_node_page(synctex_node_t node){ + synctex_node_t parent = NULL; + if (!node) { + return -1; + } + parent = SYNCTEX_PARENT(node); + while(parent) { + node = parent; + parent = SYNCTEX_PARENT(node); + } + if (node->class->type == synctex_node_type_sheet) { + return SYNCTEX_PAGE(node); + } + return -1; +} +int synctex_node_tag(synctex_node_t node) { + return node?SYNCTEX_TAG(node):-1; +} +int synctex_node_line(synctex_node_t node) { + return node?SYNCTEX_LINE(node):-1; +} +int synctex_node_column(synctex_node_t node) { +# ifdef __DARWIN_UNIX03 +# pragma unused(node) +# endif + return -1; +} +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Sheet +# endif + +synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page) { + if (scanner) { + synctex_node_t sheet = scanner->sheet; + while(sheet) { + if (page == SYNCTEX_PAGE(sheet)) { + return SYNCTEX_CHILD(sheet); + } + sheet = SYNCTEX_SIBLING(sheet); + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Query +# endif + +int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column) { +# ifdef __DARWIN_UNIX03 +# pragma unused(column) +# endif + int tag = synctex_scanner_get_tag(scanner,name); + size_t size = 0; + int friend_index = 0; + int max_line = 0; + synctex_node_t node = NULL; + if (tag == 0) { + printf("SyncTeX Warning: No tag for %s\n",name); + return -1; + } + free(SYNCTEX_START); + SYNCTEX_CUR = SYNCTEX_END = SYNCTEX_START = NULL; + max_line = line < INT_MAX-scanner->number_of_lists ? line+scanner->number_of_lists:INT_MAX; + while(linenumber_of_lists); + if ((node = (scanner->lists_of_friends)[friend_index])) { + do { + if ((synctex_node_type(node)>=synctex_node_type_boundary) + && (tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if (SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + if (SYNCTEX_START == NULL) { + /* We did not find any matching boundary, retry with glue or kern */ + node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ + do { + if ((synctex_node_type(node)>=synctex_node_type_kern) + && (tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if (SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + if (SYNCTEX_START == NULL) { + /* We did not find any matching glue or kern, retry with boxes */ + node = (scanner->lists_of_friends)[friend_index];/* no need to test it again, already done */ + do { + if ((tag == SYNCTEX_TAG(node)) + && (line == SYNCTEX_LINE(node))) { + if (SYNCTEX_CUR == SYNCTEX_END) { + size += 16; + SYNCTEX_END = realloc(SYNCTEX_START,size*sizeof(synctex_node_t *)); + SYNCTEX_CUR += SYNCTEX_END - SYNCTEX_START; + SYNCTEX_START = SYNCTEX_END; + SYNCTEX_END = SYNCTEX_START + size*sizeof(synctex_node_t *); + } + *(synctex_node_t *)SYNCTEX_CUR = node; + SYNCTEX_CUR += sizeof(synctex_node_t); + } + } while((node = SYNCTEX_FRIEND(node))); + } + } + SYNCTEX_END = SYNCTEX_CUR; + /* Now reverse the order to have nodes in display order, and keep just a few nodes */ + if ((SYNCTEX_START) && (SYNCTEX_END)) + { + synctex_node_t * start_ref = (synctex_node_t *)SYNCTEX_START; + synctex_node_t * end_ref = (synctex_node_t *)SYNCTEX_END; + end_ref -= 1; + while(start_ref < end_ref) { + node = *start_ref; + *start_ref = *end_ref; + *end_ref = node; + start_ref += 1; + end_ref -= 1; + } + /* Basically, we keep the first node for each parent. + * More precisely, we keep only nodes that are not descendants of + * their predecessor's parent. */ + start_ref = (synctex_node_t *)SYNCTEX_START; + end_ref = (synctex_node_t *)SYNCTEX_START; + next_end: + end_ref += 1; /* we allways have start_ref<= end_ref*/ + if (end_ref < (synctex_node_t *)SYNCTEX_END) { + node = *end_ref; + while((node = SYNCTEX_PARENT(node))) { + if (SYNCTEX_PARENT(*start_ref) == node) { + goto next_end; + } + } + start_ref += 1; + *start_ref = *end_ref; + goto next_end; + } + start_ref += 1; + SYNCTEX_END = (char *)start_ref; + SYNCTEX_CUR = NULL;// added on behalf of Jose Alliste + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t);// added on behalf Jan Sundermeyer + } + SYNCTEX_CUR = NULL; + // return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); removed on behalf Jan Sundermeyer + } +# if defined(__SYNCTEX_STRONG_DISPLAY_QUERY__) + break; +# else + ++line; +# endif + } + return 0; +} + +synctex_node_t synctex_next_result(synctex_scanner_t scanner) { + if (NULL == SYNCTEX_CUR) { + SYNCTEX_CUR = SYNCTEX_START; + } else { + SYNCTEX_CUR+=sizeof(synctex_node_t); + } + if (SYNCTEX_CUR= scanner->unit) {/* scanner->unit must be >0 */ + return 0; + } + /* Convert the given point to scanner integer coordinates */ + hitPoint.h = (h-scanner->x_offset)/scanner->unit; + hitPoint.v = (v-scanner->y_offset)/scanner->unit; + /* We will store in the scanner's buffer the result of the query. */ + free(SYNCTEX_START); + SYNCTEX_START = SYNCTEX_END = SYNCTEX_CUR = NULL; + /* Find the proper sheet */ + sheet = scanner->sheet; + while((sheet) && SYNCTEX_PAGE(sheet) != page) { + sheet = SYNCTEX_SIBLING(sheet); + } + if (NULL == sheet) { + return -1; + } + /* Now sheet points to the sheet node with proper page number */ + /* Here is how we work: + * At first we do not consider the visible box dimensions. This will cover the most frequent cases. + * Then we try with the visible box dimensions. + * We try to find a non void box containing the hit point. + * We browse all the horizontal boxes until we find one containing the hit point. */ + if ((node = SYNCTEX_NEXT_HORIZ_BOX(sheet))) { + do { + if (_synctex_point_in_box(hitPoint,node,synctex_YES)) { + /* Maybe the hitPoint belongs to a contained vertical box. */ +end: + /* This trick is for catching overlapping boxes */ + if ((other_node = SYNCTEX_NEXT_HORIZ_BOX(node))) { + do { + if (_synctex_point_in_box(hitPoint,other_node,synctex_YES)) { + node = _synctex_smallest_container(other_node,node); + } + } while((other_node = SYNCTEX_NEXT_HORIZ_BOX(other_node))); + } + /* node is the smallest horizontal box that contains hitPoint. */ + if ((bestContainer = _synctex_eq_deepest_container(hitPoint,node,synctex_YES))) { + node = bestContainer; + } + _synctex_eq_get_closest_children_in_box(hitPoint,node,&bestNodes,&bestDistances,synctex_YES); + if (bestNodes.right && bestNodes.left) { + if ((SYNCTEX_TAG(bestNodes.right)!=SYNCTEX_TAG(bestNodes.left)) + || (SYNCTEX_LINE(bestNodes.right)!=SYNCTEX_LINE(bestNodes.left)) + || (SYNCTEX_COLUMN(bestNodes.right)!=SYNCTEX_COLUMN(bestNodes.left))) { + if ((SYNCTEX_START = malloc(2*sizeof(synctex_node_t)))) { + if (bestDistances.left>bestDistances.right) { + ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.right; + ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.left; + } else { + ((synctex_node_t *)SYNCTEX_START)[0] = bestNodes.left; + ((synctex_node_t *)SYNCTEX_START)[1] = bestNodes.right; + } + SYNCTEX_END = SYNCTEX_START + 2*sizeof(synctex_node_t); + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } + return SYNCTEX_STATUS_ERROR; + } + /* both nodes have the same input coordinates + * We choose the one closest to the hit point */ + if (bestDistances.left>bestDistances.right) { + bestNodes.left = bestNodes.right; + } + bestNodes.right = NULL; + } else if (bestNodes.right) { + bestNodes.left = bestNodes.right; + } else if (!bestNodes.left){ + bestNodes.left = node; + } + if ((SYNCTEX_START = malloc(sizeof(synctex_node_t)))) { + * (synctex_node_t *)SYNCTEX_START = bestNodes.left; + SYNCTEX_END = SYNCTEX_START + sizeof(synctex_node_t); + SYNCTEX_CUR = NULL; + return (SYNCTEX_END-SYNCTEX_START)/sizeof(synctex_node_t); + } + return SYNCTEX_STATUS_ERROR; + } + } while ((node = SYNCTEX_NEXT_HORIZ_BOX(node))); + /* All the horizontal boxes have been tested, + * None of them contains the hit point. + */ + } + /* We are not lucky */ + if ((node = SYNCTEX_CHILD(sheet))) { + goto end; + } + return 0; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Utilities +# endif + +int _synctex_bail(void) { + _synctex_error("SyncTeX ERROR\n"); + return -1; +} +/* Rougly speaking, this is: + * node's h coordinate - hitPoint's h coordinate. + * If node is to the right of the hit point, then this distance is positive, + * if node is to the left of the hit point, this distance is negative.*/ +int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible); +int _synctex_point_h_distance(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { + if (node) { + int min,med,max; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = visible?SYNCTEX_HORIZ_V(node):SYNCTEX_HORIZ(node); + max = min + (visible?SYNCTEX_ABS_WIDTH_V(node):SYNCTEX_ABS_WIDTH(node)); + /* We allways have min <= max */ + if (hitPoint.h 0 */ + } else if (hitPoint.h>max) { + return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ + } else { + return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. + * For these boxes, no visible dimension available */ + min = SYNCTEX_HORIZ(node); + max = min + SYNCTEX_ABS_WIDTH(node); + /* We allways have min <= max */ + if (hitPoint.h 0 */ + } else if (hitPoint.h>max) { + return max - hitPoint.h; /* regions 3+6+9, result is < 0 */ + } else { + return 0; /* regions 2+5+8, inside the box, except for vertical coordinates */ + } + break; + case synctex_node_type_kern: + /* IMPORTANT NOTICE: the location of the kern is recorded AFTER the move. + * The distance to the kern is very special, + * in general, there is no text material in the kern, + * this is why we compute the offset relative to the closest edge of the kern.*/ + max = SYNCTEX_WIDTH(node); + if (max<0) { + min = SYNCTEX_HORIZ(node); + max = min - max; + } else { + min = -max; + max = SYNCTEX_HORIZ(node); + min += max; + } + med = (min+max)/2; + /* positive kern: '.' means text, '>' means kern offset + * ............. + * min>>>>med>>>>max + * ............... + * negative kern: '.' means text, '<' means kern offset + * ............................ + * min<<<max) { + return max - hitPoint.h - 1; /* same kind of penalty */ + } else if (hitPoint.h>med) { + /* do things like if the node had 0 width and was placed at the max edge + 1*/ + return max - hitPoint.h + 1; /* positive, the kern is to the right of the hitPoint */ + } else { + return min - hitPoint.h - 1; /* negative, the kern is to the left of the hitPoint */ + } + case synctex_node_type_glue: + case synctex_node_type_math: + return SYNCTEX_HORIZ(node) - hitPoint.h; + } + } + return INT_MAX;/* We always assume that the node is faraway to the right*/ +} +/* Rougly speaking, this is: + * node's v coordinate - hitPoint's v coordinate. + * If node is at the top of the hit point, then this distance is positive, + * if node is at the bottom of the hit point, this distance is negative.*/ +int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible); +int _synctex_point_v_distance(synctex_point_t hitPoint, synctex_node_t node,synctex_bool_t visible) { +# ifdef __DARWIN_UNIX03 +# pragma unused(visible) +# endif + if (node) { + int min,max; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + */ + case synctex_node_type_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = SYNCTEX_VERT_V(node); + max = min + SYNCTEX_ABS_DEPTH_V(node); + min -= SYNCTEX_ABS_HEIGHT_V(node); + /* We allways have min <= max */ + if (hitPoint.v 0 */ + } else if (hitPoint.v>max) { + return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ + } else { + return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative width, height and depth. */ + min = SYNCTEX_VERT(node); + max = min + SYNCTEX_ABS_DEPTH(node); + min -= SYNCTEX_ABS_HEIGHT(node); + /* We allways have min <= max */ + if (hitPoint.v 0 */ + } else if (hitPoint.v>max) { + return max - hitPoint.v; /* regions 7+8+9, result is < 0 */ + } else { + return 0; /* regions 4.5.6, inside the box, except for horizontal coordinates */ + } + break; + case synctex_node_type_kern: + case synctex_node_type_glue: + case synctex_node_type_math: + return SYNCTEX_VERT(node) - hitPoint.v; + } + } + return INT_MAX;/* We always assume that the node is faraway to the top*/ +} + +SYNCTEX_INLINE static synctex_node_t _synctex_smallest_container(synctex_node_t node, synctex_node_t other_node) { + float height, other_height; + if (SYNCTEX_ABS_WIDTH(node)SYNCTEX_ABS_WIDTH(other_node)) { + return other_node; + } + height = SYNCTEX_ABS_DEPTH(node) + SYNCTEX_ABS_HEIGHT(node); + other_height = SYNCTEX_ABS_DEPTH(other_node) + SYNCTEX_ABS_HEIGHT(other_node); + if (heightother_height) { + return other_node; + } + return node; +} + +synctex_bool_t _synctex_point_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { + if (node) { + if (0 == _synctex_point_h_distance(hitPoint,node,visible) + && 0 == _synctex_point_v_distance(hitPoint,node,visible)) { + return synctex_YES; + } + } + return synctex_NO; +} + +int _synctex_node_distance_to_point(synctex_point_t hitPoint, synctex_node_t node, synctex_bool_t visible) { +# ifdef __DARWIN_UNIX03 +# pragma unused(visible) +# endif + int result = INT_MAX; /* when the distance is meaning less (sheet, input...) */ + if (node) { + int minH,maxH,minV,maxV; + switch(node->class->type) { + /* The distance between a point and a box is special. + * It is not the euclidian distance, nor something similar. + * We have to take into account the particular layout, + * and the box hierarchy. + * Given a box, there are 9 regions delimited by the lines of the edges of the box. + * The origin being at the top left corner of the page, + * we also give names to the vertices of the box. + * + * 1 | 2 | 3 + * ---A---B---> + * 4 | 5 | 6 + * ---C---D---> + * 7 | 8 | 9 + * v v + * In each region, there is a different formula. + * In the end we have a continuous distance which may not be a mathematical distance but who cares. */ + case synctex_node_type_vbox: + case synctex_node_type_void_vbox: + case synctex_node_type_hbox: + case synctex_node_type_void_hbox: + /* getting the box bounds, taking into account negative widths. */ + minH = SYNCTEX_HORIZ(node); + maxH = minH + SYNCTEX_ABS_WIDTH(node); + minV = SYNCTEX_VERT(node); + maxV = minV + SYNCTEX_ABS_DEPTH(node); + minV -= SYNCTEX_ABS_HEIGHT(node); + /* In what region is the point hitPoint=(H,V) ? */ + if (hitPoint.vminV) { + result = hitPoint.v - minV + minH - hitPoint.h; + } else { + result = minV - hitPoint.v + minH - hitPoint.h; + } + } else if (hitPoint.h>maxH) { + if (hitPoint.v>minV) { + result = hitPoint.v - minV + hitPoint.h - maxH; + } else { + result = minV - hitPoint.v + hitPoint.h - maxH; + } + } else if (hitPoint.v>minV) { + result = hitPoint.v - minV; + } else { + result = minV - hitPoint.v; + } + break; + case synctex_node_type_glue: + case synctex_node_type_math: + minH = SYNCTEX_HORIZ(node); + minV = SYNCTEX_VERT(node); + if (hitPoint.hminV) { + result = hitPoint.v - minV + minH - hitPoint.h; + } else { + result = minV - hitPoint.v + minH - hitPoint.h; + } + } else if (hitPoint.v>minV) { + result = hitPoint.v - minV + hitPoint.h - minH; + } else { + result = minV - hitPoint.v + hitPoint.h - minH; + } + break; + } + } + return result; +} + +static synctex_node_t _synctex_eq_deepest_container(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { + if (node) { + synctex_node_t result = NULL; + synctex_node_t child = NULL; + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + /* test the deep nodes first */ + if ((child = SYNCTEX_CHILD(node))) { + do { + if ((result = _synctex_eq_deepest_container(hitPoint,child,visible))) { + return result; + } + } while((child = SYNCTEX_SIBLING(child))); + } + /* is the hit point inside the box? */ + if (_synctex_point_in_box(hitPoint,node,visible)) { + /* for vboxes we try to use some node inside. + * Walk through the list of siblings until we find the closest one. + * Only consider siblings with children. */ + if ((node->class->type == synctex_node_type_vbox) && (child = SYNCTEX_CHILD(node))) { + int bestDistance = INT_MAX; + do { + if (SYNCTEX_CHILD(child)) { + int distance = _synctex_node_distance_to_point(hitPoint,child,visible); + if (distance < bestDistance) { + bestDistance = distance; + node = child; + } + } + } while((child = SYNCTEX_SIBLING(child))); + } + return node; + } + } + } + return NULL; +} + +/* Compares the locations of the hitPoint with the locations of the various nodes contained in the box. + * As it is an horizontal box, we only compare horizontal coordinates. */ +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible); +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_hbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef, synctex_bool_t visible) { + int result = 0; + if ((node = SYNCTEX_CHILD(node))) { + do { + int off7 = _synctex_point_h_distance(hitPoint,node,visible); + if (off7 > 0) { + /* node is to the right of the hit point. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (bestDistancesRef->right > off7) { + bestDistancesRef->right = off7; + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } else if (bestDistancesRef->right == off7 && bestNodesRef->right) { + if (SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } + } + } else if (off7 == 0) { + /* hitPoint is inside node. */ + bestDistancesRef->left = bestDistancesRef->right = 0; + bestNodesRef->left = node; + bestNodesRef->right = NULL; + result |= SYNCTEX_MASK_LEFT; + } else { /* here off7 < 0, hitPoint is to the right of node */ + off7 = -off7; + if (bestDistancesRef->left > off7) { + bestDistancesRef->left = off7; + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } else if (bestDistancesRef->left == off7 && bestNodesRef->left) { + if (SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } + } + } + } while((node = SYNCTEX_SIBLING(node))); + if (result & SYNCTEX_MASK_LEFT) { + /* the left node is new, try to narrow the result */ + if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + } + if (result & SYNCTEX_MASK_RIGHT) { + /* the right node is new, try to narrow the result */ + if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + } + } + return result; +} +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible); +SYNCTEX_INLINE static int __synctex_eq_get_closest_children_in_vbox(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { + int result = 0; + if ((node = SYNCTEX_CHILD(node))) { + do { + int off7 = _synctex_point_v_distance(hitPoint,node,visible);/* this is what makes the difference with the h version above */ + if (off7 > 0) { + /* node is to the top of the hit point (below because TeX is oriented from top to bottom. + * We compare node and the previously recorded one, through the recorded distance. + * If the nodes have the same tag, prefer the one with the smallest line number, + * if the nodes also have the same line number, prefer the one with the smallest column. */ + if (bestDistancesRef->right > off7) { + bestDistancesRef->right = off7; + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } else if (bestDistancesRef->right == off7 && bestNodesRef->right) { + if (SYNCTEX_TAG(bestNodesRef->right) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->right) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->right) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->right) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->right = node; + result |= SYNCTEX_MASK_RIGHT; + } + } + } else if (off7 == 0) { + bestDistancesRef->left = bestDistancesRef->right = 0; + bestNodesRef->left = node; + bestNodesRef->right = NULL; + result |= SYNCTEX_MASK_LEFT; + } else { /* here off7 < 0 */ + off7 = -off7; + if (bestDistancesRef->left > off7) { + bestDistancesRef->left = off7; + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } else if (bestDistancesRef->left == off7 && bestNodesRef->left) { + if (SYNCTEX_TAG(bestNodesRef->left) == SYNCTEX_TAG(node) + && (SYNCTEX_LINE(bestNodesRef->left) > SYNCTEX_LINE(node) + || (SYNCTEX_LINE(bestNodesRef->left) == SYNCTEX_LINE(node) + && SYNCTEX_COLUMN(bestNodesRef->left) > SYNCTEX_COLUMN(node)))) { + bestNodesRef->left = node; + result |= SYNCTEX_MASK_LEFT; + } + } + } + } while((node = SYNCTEX_SIBLING(node))); + if (result & SYNCTEX_MASK_LEFT) { + /* the left node is new, try to narrow the result */ + if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->left,visible))) { + bestNodesRef->left = node; + } + } + if (result & SYNCTEX_MASK_RIGHT) { + /* the right node is new, try to narrow the result */ + if ((node = _synctex_eq_deepest_container(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + if ((node = _synctex_eq_closest_child(hitPoint,bestNodesRef->right,visible))) { + bestNodesRef->right = node; + } + } + } + return result; +} +SYNCTEX_INLINE static int _synctex_eq_get_closest_children_in_box(synctex_point_t hitPoint, synctex_node_t node, synctex_node_set_t* bestNodesRef,synctex_distances_t* bestDistancesRef,synctex_bool_t visible) { + if (node) { + switch(node->class->type) { + case synctex_node_type_hbox: + return __synctex_eq_get_closest_children_in_hbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); + case synctex_node_type_vbox: + return __synctex_eq_get_closest_children_in_vbox(hitPoint, node, bestNodesRef, bestDistancesRef,visible); + } + } + return 0; +} + +SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible); +SYNCTEX_INLINE static synctex_node_t __synctex_eq_closest_child(synctex_point_t hitPoint, synctex_node_t node,int* distanceRef, synctex_bool_t visible) { + synctex_node_t best_node = NULL; + if ((node = SYNCTEX_CHILD(node))) { + do { + int distance = _synctex_node_distance_to_point(hitPoint,node,visible); + synctex_node_t candidate = NULL; + if (distance<=*distanceRef) { + *distanceRef = distance; + best_node = node; + } + switch(node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + if ((candidate = __synctex_eq_closest_child(hitPoint,node,distanceRef,visible))) { + best_node = candidate; + } + } + } while((node = SYNCTEX_SIBLING(node))); + } + return best_node; +} +SYNCTEX_INLINE static synctex_node_t _synctex_eq_closest_child(synctex_point_t hitPoint,synctex_node_t node, synctex_bool_t visible) { + if (node) { + switch(node->class->type) { + case synctex_node_type_hbox: + case synctex_node_type_vbox: + { + int best_distance = INT_MAX; + synctex_node_t best_node = __synctex_eq_closest_child(hitPoint,node,&best_distance,visible); + if ((best_node)) { + synctex_node_t child = NULL; + switch(best_node->class->type) { + case synctex_node_type_vbox: + case synctex_node_type_hbox: + if ((child = SYNCTEX_CHILD(best_node))) { + best_distance = _synctex_node_distance_to_point(hitPoint,child,visible); + while((child = SYNCTEX_SIBLING(child))) { + int distance = _synctex_node_distance_to_point(hitPoint,child,visible); + if (distance<=best_distance) { + best_distance = distance; + best_node = child; + } + } + } + } + } + return best_node; + } + } + } + return NULL; +} + +# ifdef SYNCTEX_NOTHING +# pragma mark - +# pragma mark Updater +# endif + +typedef int (*synctex_fprintf_t)(void *, const char * , ...); /* print formatted to either FILE * or gzFile */ + +# define SYNCTEX_BITS_PER_BYTE 8 + +struct __synctex_updater_t { + void *file; /* the foo.synctex or foo.synctex.gz I/O identifier */ + synctex_fprintf_t fprintf; /* either fprintf or gzprintf */ + int length; /* the number of chars appended */ + struct _flags { + unsigned int no_gz:1; /* Whether zlib is used or not */ + unsigned int reserved:SYNCTEX_BITS_PER_BYTE*sizeof(int)-1; /* Align */ + } flags; +}; +# define SYNCTEX_FILE updater->file +# define SYNCTEX_NO_GZ ((updater->flags).no_gz) +# define SYNCTEX_fprintf (*(updater->fprintf)) + +synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * build_directory) { + synctex_updater_t updater = NULL; + char * synctex = NULL; + synctex_io_mode_t io_mode = 0; + const char * mode = NULL; + /* prepare the updater, the memory is the only one dynamically allocated */ + updater = (synctex_updater_t)_synctex_malloc(sizeof(synctex_updater_t)); + if (NULL == updater) { + _synctex_error("! synctex_updater_new_with_file: malloc problem"); + return NULL; + } + if (_synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_ADD_QUOTES,&io_mode) + && _synctex_open(output,build_directory,&synctex,&SYNCTEX_FILE,synctex_DONT_ADD_QUOTES,&io_mode)) { +return_on_error: + free(updater); + updater = NULL; + return NULL; + } + /* OK, the file exists, we close it and reopen it with the correct mode. + * The receiver is now the owner of the "synctex" variable. */ + gzclose(SYNCTEX_FILE); + SYNCTEX_FILE = NULL; + SYNCTEX_NO_GZ = (io_mode&synctex_io_gz_mask)?synctex_NO:synctex_YES; + mode = _synctex_get_io_mode_name(io_mode|synctex_io_append_mask);/* either "a" or "ab", depending on the file extension */ + if (SYNCTEX_NO_GZ) { + if (NULL == (SYNCTEX_FILE = (void *)fopen(synctex,mode))) { +no_write_error: + _synctex_error("! synctex_updater_new_with_file: Can't append to %s",synctex); + free(synctex); + goto return_on_error; + } + updater->fprintf = (synctex_fprintf_t)(&fprintf); + } else { + if (NULL == (SYNCTEX_FILE = (void *)gzopen(synctex,mode))) { + goto no_write_error; + } + updater->fprintf = (synctex_fprintf_t)(&gzprintf); + } + printf("SyncTeX: updating %s...",synctex); + free(synctex); + return updater; +} + + +void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification){ + if (NULL==updater) { + return; + } + if (magnification && strlen(magnification)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Magnification:%s\n",magnification); + } +} + +void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset){ + if (NULL==updater) { + return; + } + if (x_offset && strlen(x_offset)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"X Offset:%s\n",x_offset); + } +} + +void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset){ + if (NULL==updater) { + return; + } + if (y_offset && strlen(y_offset)) { + updater->length += SYNCTEX_fprintf(SYNCTEX_FILE,"Y Offset:%s\n",y_offset); + } +} + +void synctex_updater_free(synctex_updater_t updater){ + if (NULL==updater) { + return; + } + if (updater->length>0) { + SYNCTEX_fprintf(SYNCTEX_FILE,"!%i\n",updater->length); + } + if (SYNCTEX_NO_GZ) { + fclose((FILE *)SYNCTEX_FILE); + } else { + gzclose((gzFile)SYNCTEX_FILE); + } + free(updater); + printf("... done.\n"); + return; +} diff --git a/services/clsi/src/synctex/synctex_parser.h b/services/clsi/src/synctex/synctex_parser.h new file mode 100644 index 0000000000..4aca41501e --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser.h @@ -0,0 +1,346 @@ +/* +Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Version 1 +Thu Jun 19 09:39:21 UTC 2008 + +*/ + +#ifndef __SYNCTEX_PARSER__ +# define __SYNCTEX_PARSER__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* synctex_node_t is the type for all synctex nodes. + * The synctex file is parsed into a tree of nodes, either sheet, boxes, math nodes... */ +typedef struct _synctex_node * synctex_node_t; + +/* The main synctex object is a scanner + * Its implementation is considered private. + * The basic workflow is + * - create a "synctex scanner" with the contents of a file + * - perform actions on that scanner like display or edit queries + * - free the scanner when the work is done + */ +typedef struct __synctex_scanner_t _synctex_scanner_t; +typedef _synctex_scanner_t * synctex_scanner_t; + +/* This is the designated method to create a new synctex scanner object. + * output is the pdf/dvi/xdv file associated to the synctex file. + * If necessary, it can be the tex file that originated the synctex file + * but this might cause problems if the \jobname has a custom value. + * Despite this method can accept a relative path in practice, + * you should only pass a full path name. + * The path should be encoded by the underlying file system, + * assuming that it is based on 8 bits characters, including UTF8, + * not 16 bits nor 32 bits. + * The last file extension is removed and replaced by the proper extension. + * Then the private method _synctex_scanner_new_with_contents_of_file is called. + * NULL is returned in case of an error or non existent file. + * Once you have a scanner, use the synctex_display_query and synctex_edit_query below. + * The new "build_directory" argument is available since version 1.5. + * It is the directory where all the auxiliary stuff is created. + * Sometimes, the synctex output file and the pdf, dvi or xdv files are not created in the same directory. + * This is the case in MikTeX (I will include this into TeX Live). + * This directory path can be nil, it will be ignored then. + * It can be either absolute or relative to the directory of the output pdf (dvi or xdv) file. + * If no synctex file is found in the same directory as the output file, then we try to find one in the build directory. + * Please note that this new "build_directory" is provided as a convenient argument but should not be used. + * In fact, this is implempented as a work around of a bug in MikTeX where the synctex file does not follow the pdf file. + * The new "parse" argument is available since version 1.5. In general, use 1. + * Use 0 only if you do not want to parse the content but just check the existence. + */ +synctex_scanner_t synctex_scanner_new_with_output_file(const char * output, const char * build_directory, int parse); + +/* This is the designated method to delete a synctex scanner object. + * Frees all the memory, you must call it when you are finished with the scanner. + */ +void synctex_scanner_free(synctex_scanner_t scanner); + +/* Send this message to force the scanner to parse the contents of the synctex output file. + * Nothing is performed if the file was already parsed. + * In each query below, this message is sent, but if you need to access information more directly, + * you must be sure that the parsing did occur. + * Usage: + * if((my_scanner = synctex_scanner_parse(my_scanner))) { + * continue with my_scanner... + * } else { + * there was a problem + * } + */ +synctex_scanner_t synctex_scanner_parse(synctex_scanner_t scanner); + +/* The main entry points. + * Given the file name, a line and a column number, synctex_display_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_display_query(scanner,name,line,column)>0) { + * synctex_node_t node; + * while((node = synctex_next_result(scanner))) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting node in the output, using synctex_node_h and synctex_node_v + * - highlight all the rectangles enclosing those nodes, using synctex_box_... functions + * - highlight just the character using that information + * + * Given the page and the position in the page, synctex_edit_query returns the number of nodes + * satisfying the contrain. Use code like + * + * if(synctex_edit_query(scanner,page,h,v)>0) { + * synctex_node_t node; + * while(node = synctex_next_result(scanner)) { + * // do something with node + * ... + * } + * } + * + * For example, one can + * - highlight each resulting line in the input, + * - highlight just the character using that information + * + * page is 1 based + * h and v are coordinates in 72 dpi unit, relative to the top left corner of the page. + * If you make a new query, the result of the previous one is discarded. + * If one of this function returns a non positive integer, + * it means that an error occurred. + * + * Both methods are conservative, in the sense that matching is weak. + * If the exact column number is not found, there will be an answer with the whole line. + * + * Sumatra-PDF, Skim, iTeXMac2 and Texworks are examples of open source software that use this library. + * You can browse their code for a concrete implementation. + */ +int synctex_display_query(synctex_scanner_t scanner,const char * name,int line,int column); +int synctex_edit_query(synctex_scanner_t scanner,int page,float h,float v); +synctex_node_t synctex_next_result(synctex_scanner_t scanner); + +/* Display all the information contained in the scanner object. + * If the records are too numerous, only the first ones are displayed. + * This is mainly for informatinal purpose to help developers. + */ +void synctex_scanner_display(synctex_scanner_t scanner); + +/* The x and y offset of the origin in TeX coordinates. The magnification + These are used by pdf viewers that want to display the real box size. + For example, getting the horizontal coordinates of a node would require + synctex_node_box_h(node)*synctex_scanner_magnification(scanner)+synctex_scanner_x_offset(scanner) + Getting its TeX width would simply require + synctex_node_box_width(node)*synctex_scanner_magnification(scanner) + but direct methods are available for that below. + */ +int synctex_scanner_x_offset(synctex_scanner_t scanner); +int synctex_scanner_y_offset(synctex_scanner_t scanner); +float synctex_scanner_magnification(synctex_scanner_t scanner); + +/* Managing the input file names. + * Given a tag, synctex_scanner_get_name will return the corresponding file name. + * Conversely, given a file name, synctex_scanner_get_tag will retur, the corresponding tag. + * The file name must be the very same as understood by TeX. + * For example, if you \input myDir/foo.tex, the file name is myDir/foo.tex. + * No automatic path expansion is performed. + * Finally, synctex_scanner_input is the first input node of the scanner. + * To browse all the input node, use a loop like + * + * if((input_node = synctex_scanner_input(scanner))){ + * do { + * blah + * } while((input_node=synctex_node_sibling(input_node))); + * } + * + * The output is the name that was used to create the scanner. + * The synctex is the real name of the synctex file, + * it was obtained from output by setting the proper file extension. + */ +const char * synctex_scanner_get_name(synctex_scanner_t scanner,int tag); +int synctex_scanner_get_tag(synctex_scanner_t scanner,const char * name); +synctex_node_t synctex_scanner_input(synctex_scanner_t scanner); +const char * synctex_scanner_get_output(synctex_scanner_t scanner); +const char * synctex_scanner_get_synctex(synctex_scanner_t scanner); + +/* Browsing the nodes + * parent, child and sibling are standard names for tree nodes. + * The parent is one level higher, the child is one level deeper, + * and the sibling is at the same level. + * The sheet of a node is the first ancestor, it is of type sheet. + * A node and its sibling have the same parent. + * A node is the parent of its child. + * A node is either the child of its parent, + * or belongs to the sibling chain of its parent's child. + * The next node is either the child, the sibling or the parent's sibling, + * unless the parent is a sheet. + * This allows to navigate through all the nodes of a given sheet node: + * + * synctex_node_t node = sheet; + * while((node = synctex_node_next(node))) { + * // do something with node + * } + * + * With synctex_sheet_content, you can retrieve the sheet node given the page. + * The page is 1 based, according to TeX standards. + * Conversely synctex_node_sheet allows to retrieve the sheet containing a given node. + */ +synctex_node_t synctex_node_parent(synctex_node_t node); +synctex_node_t synctex_node_sheet(synctex_node_t node); +synctex_node_t synctex_node_child(synctex_node_t node); +synctex_node_t synctex_node_sibling(synctex_node_t node); +synctex_node_t synctex_node_next(synctex_node_t node); +synctex_node_t synctex_sheet_content(synctex_scanner_t scanner,int page); + +/* These are the types of the synctex nodes */ +typedef enum { + synctex_node_type_error = 0, + synctex_node_type_input, + synctex_node_type_sheet, + synctex_node_type_vbox, + synctex_node_type_void_vbox, + synctex_node_type_hbox, + synctex_node_type_void_hbox, + synctex_node_type_kern, + synctex_node_type_glue, + synctex_node_type_math, + synctex_node_type_boundary, + synctex_node_number_of_types +} synctex_node_type_t; + +/* synctex_node_type gives the type of a given node, + * synctex_node_isa gives the same information as a human readable text. */ +synctex_node_type_t synctex_node_type(synctex_node_t node); +const char * synctex_node_isa(synctex_node_t node); + +/* This is primarily used for debugging purpose. + * The second one logs information for the node and recursively displays information for its next node */ +void synctex_node_log(synctex_node_t node); +void synctex_node_display(synctex_node_t node); + +/* Given a node, access to its tag, line and column. + * The line and column numbers are 1 based. + * The latter is not yet fully supported in TeX, the default implementation returns 0 which means the whole line. + * When the tag is known, the scanner of the node will give the corresponding file name. + * When the tag is known, the scanner of the node will give the name. + */ +int synctex_node_tag(synctex_node_t node); +int synctex_node_line(synctex_node_t node); +int synctex_node_column(synctex_node_t node); + +/* This is the page where the node appears. + * This is a 1 based index as given by TeX. + */ +int synctex_node_page(synctex_node_t node); + +/* For quite all nodes, horizontal, vertical coordinates, and width. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + */ +int synctex_node_h(synctex_node_t node); +int synctex_node_v(synctex_node_t node); +int synctex_node_width(synctex_node_t node); + +/* For all nodes, dimensions of the enclosing box. + * These are expressed in TeX small points coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ +int synctex_node_box_h(synctex_node_t node); +int synctex_node_box_v(synctex_node_t node); +int synctex_node_box_width(synctex_node_t node); +int synctex_node_box_height(synctex_node_t node); +int synctex_node_box_depth(synctex_node_t node); + +/* For quite all nodes, horizontal, vertical coordinates, and width. + * The visible dimensions are bigger than real ones to compensate 0 width boxes + * that do contain nodes. + * These are expressed in page coordinates, with origin at the top left corner. + * A box is enclosing itself. + */ +float synctex_node_visible_h(synctex_node_t node); +float synctex_node_visible_v(synctex_node_t node); +float synctex_node_visible_width(synctex_node_t node); +/* For all nodes, visible dimensions of the enclosing box. + * A box is enclosing itself. + * The visible dimensions are bigger than real ones to compensate 0 width boxes + * that do contain nodes. + */ +float synctex_node_box_visible_h(synctex_node_t node); +float synctex_node_box_visible_v(synctex_node_t node); +float synctex_node_box_visible_width(synctex_node_t node); +float synctex_node_box_visible_height(synctex_node_t node); +float synctex_node_box_visible_depth(synctex_node_t node); + +/* The main synctex updater object. + * This object is used to append information to the synctex file. + * Its implementation is considered private. + * It is used by the synctex command line tool to take into account modifications + * that could occur while postprocessing files by dvipdf like filters. + */ +typedef struct __synctex_updater_t _synctex_updater_t; +typedef _synctex_updater_t * synctex_updater_t; + +/* Designated initializer. + * Once you are done with your whole job, + * free the updater */ +synctex_updater_t synctex_updater_new_with_output_file(const char * output, const char * directory); + +/* Use the next functions to append records to the synctex file, + * no consistency tests made on the arguments */ +void synctex_updater_append_magnification(synctex_updater_t updater, char * magnification); +void synctex_updater_append_x_offset(synctex_updater_t updater, char * x_offset); +void synctex_updater_append_y_offset(synctex_updater_t updater, char * y_offset); + +/* You MUST free the updater, once everything is properly appended */ +void synctex_updater_free(synctex_updater_t updater); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/services/clsi/src/synctex/synctex_parser_local.h b/services/clsi/src/synctex/synctex_parser_local.h new file mode 100644 index 0000000000..6573b2638a --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_local.h @@ -0,0 +1,45 @@ +/* +Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* This local header file is for TEXLIVE, use your own header to fit your system */ +# include /* for inline && HAVE_xxx */ +/* No inlining for synctex tool in texlive. */ +# define SYNCTEX_INLINE diff --git a/services/clsi/src/synctex/synctex_parser_readme.txt b/services/clsi/src/synctex/synctex_parser_readme.txt new file mode 100644 index 0000000000..ebc06bb7bd --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_readme.txt @@ -0,0 +1,141 @@ +This file is part of the SyncTeX package. + +The Synchronization TeXnology named SyncTeX is a new feature +of recent TeX engines designed by Jerome Laurens. +It allows to synchronize between input and output, which means to +navigate from the source document to the typeset material and vice versa. +More informations on http://itexmac2.sourceforge.net/SyncTeX.html + +This package is mainly for developers, it mainly contains the following files: + +synctex_parser_readme.txt +synctex_parser_version.txt +synctex_parser_utils.c +synctex_parser_utils.h +synctex_parser_local.h +synctex_parser.h +synctex_parser.c + +The file you are reading contains more informations about the SyncTeX parser history. + +In order to support SyncTeX in a viewer, it is sufficient to include +in the source the files synctex_parser.h and synctex_parser.c. +The synctex parser usage is described in synctex_parser.h header file. + +The other files are used by tex engines or by the synctex command line utility: + +ChangeLog +README.txt +am +man1 +man5 +synctex-common.h +synctex-convert.sh +synctex-e-mem.ch0 +synctex-e-mem.ch1 +synctex-e-rec.ch0 +synctex-e-rec.ch1 +synctex-etex.h +synctex-mem.ch0 +synctex-mem.ch1 +synctex-mem.ch2 +synctex-pdf-rec.ch2 +synctex-pdftex.h +synctex-rec.ch0 +synctex-rec.ch1 +synctex-rec.ch2 +synctex-tex.h +synctex-xe-mem.ch2 +synctex-xe-rec.ch2 +synctex-xe-rec.ch3 +synctex-xetex.h +synctex.c +synctex.defines +synctex.h +synctex_main.c +tests + + +Version: +-------- +This is version 1, which refers to the synctex output file format. +The files are identified by a build number. +In order to help developers to automatically manage the version and build numbers +and download the parser only when necessary, the synctex_parser.version +is an ASCII text file just containing the current version and build numbers. + +History: +-------- +1.1: Thu Jul 17 09:28:13 UTC 2008 +- First official version available in TeXLive 2008 DVD. + Unfortunately, the backwards synchronization is not working properly mainly for ConTeXt users, see below. +1.2: Tue Sep 2 10:28:32 UTC 2008 +- Correction for ConTeXt support in the edit query. + The previous method was assuming that TeX boxes do not overlap, + which is reasonable for LaTeX but not for ConTeXt. + This assumption is no longer considered. +1.3: Fri Sep 5 09:39:57 UTC 2008 +- Local variable "read" renamed to "already_read" to avoid conflicts. +- "inline" compiler directive renamed to "SYNCTEX_INLINE" for code support and maintenance +- _synctex_error cannot be inlined due to variable arguments (thanks Christiaan Hofman) +- Correction in the display query, extra boundary nodes are used for a more precise forwards synchronization +1.4: Fri Sep 12 08:12:34 UTC 2008 +- For an unknown reason, the previous version was not the real 1.3 (as used in iTeXMac2 build 747). + As a consequence, a crash was observed. +- Some typos are fixed. +1.6: Mon Nov 3 20:20:02 UTC 2008 +- The bug that prevented synchronization with compressed files on windows has been fixed. +- New interface to allow system specific customization. +- Note that some APIs have changed. +1.8: Mer 8 jul 2009 11:32:38 UTC +Note that version 1.7 was delivered privately. +- bug fix: synctex was causing a memory leak in pdftex and xetex, thus some processing speed degradation +- bug fix: the synctex command line tool was broken when updating a .synctex file +- enhancement: better accuracy of the synchronization process +- enhancement: the pdf output file and the associated .synctex file no longer need to live in the same directory. + The new -d option of the synctex command line tool manages this situation. + This is handy when using something like tex -output-directory=DIR ... +1.9: Wed Nov 4 11:52:35 UTC 2009 +- Various typo fixed +- OutputDebugString replaced by OutputDebugStringA to deliberately disable unicode preprocessing +- New conditional created because OutputDebugStringA is only available since Windows 2K professional +1.10: Sun Jan 10 10:12:32 UTC 2010 +- Bug fix in synctex_parser.c to solve a synchronization problem with amsmath's gather environment. + Concerns the synctex tool. +1.11: Sun Jan 17 09:12:31 UTC 2010 +- Bug fix in synctex_parser.c, function synctex_node_box_visible_v: 'x' replaced by 'y'. + Only 3rd party tools are concerned. +1.12: Mon Jul 19 21:52:10 UTC 2010 +- Bug fix in synctex_parser.c, function __synctex_open: the io_mode was modified even in case of a non zero return, +causing a void .synctex.gz file to be created even if it was not expected. Reported by Marek Kasik concerning a bug on evince. +1.13: Fri Mar 11 07:39:12 UTC 2011 +- Bug fix in synctex_parser.c, better synchronization as suggested by Jan Sundermeyer (near line 3388). +- Stronger code design in synctex_parser_utils.c, function _synctex_get_name (really neutral behavior). + Only 3rd party tools are concerned. +1.14: Fri Apr 15 19:10:57 UTC 2011 +- taking output_directory into account +- Replaced FOPEN_WBIN_MODE by FOPEN_W_MODE when opening the text version of the .synctex file. +- Merging with LuaTeX's version of synctex.c +1.15: Fri Jun 10 14:10:17 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. +- Bug fixed in _synctex_get_io_mode_name, sometimes the wrong mode was returned +- Support for LuaTeX convention of './' file prefixing +1.16: Tue Jun 14 08:23:30 UTC 2011 +This concerns the synctex command line tool and 3rd party developers. +TeX and friends are not concerned by these changes. +- Better forward search (thanks Jose Alliste) +- Support for LuaTeX convention of './' file prefixing now for everyone, not only for Windows + +Acknowledgments: +---------------- +The author received useful remarks from the pdfTeX developers, especially Hahn The Thanh, +and significant help from XeTeX developer Jonathan Kew + +Nota Bene: +---------- +If you include or use a significant part of the synctex package into a software, +I would appreciate to be listed as contributor and see "SyncTeX" highlighted. + +Copyright (c) 2008-2011 jerome DOT laurens AT u-bourgogne DOT fr + diff --git a/services/clsi/src/synctex/synctex_parser_utils.c b/services/clsi/src/synctex/synctex_parser_utils.c new file mode 100644 index 0000000000..569f7e96ca --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_utils.c @@ -0,0 +1,479 @@ +/* +Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* In this file, we find all the functions that may depend on the operating system. */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) +#define SYNCTEX_WINDOWS 1 +#endif + +#ifdef _WIN32_WINNT_WINXP +#define SYNCTEX_RECENT_WINDOWS 1 +#endif + +#ifdef SYNCTEX_WINDOWS +#include +#endif + +void *_synctex_malloc(size_t size) { + void * ptr = malloc(size); + if(ptr) { +/* There used to be a switch to use bzero because it is more secure. JL */ + memset(ptr,0, size); + } + return (void *)ptr; +} + +int _synctex_error(const char * reason,...) { + va_list arg; + int result; + va_start (arg, reason); +# ifdef SYNCTEX_RECENT_WINDOWS + {/* This code is contributed by William Blum. + As it does not work on some older computers, + the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. + According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx + Minimum supported client Windows 2000 Professional + Minimum supported server Windows 2000 Server + People running Windows 2K standard edition will not have OutputDebugStringA. + JL.*/ + char *buff; + size_t len; + OutputDebugStringA("SyncTeX ERROR: "); + len = _vscprintf(reason, arg) + 1; + buff = (char*)malloc( len * sizeof(char) ); + result = vsprintf(buff, reason, arg) +strlen("SyncTeX ERROR: "); + OutputDebugStringA(buff); + OutputDebugStringA("\n"); + free(buff); + } +# else + result = fprintf(stderr,"SyncTeX ERROR: "); + result += vfprintf(stderr, reason, arg); + result += fprintf(stderr,"\n"); +# endif + va_end (arg); + return result; +} + +/* strip the last extension of the given string, this string is modified! */ +void _synctex_strip_last_path_extension(char * string) { + if(NULL != string){ + char * last_component = NULL; + char * last_extension = NULL; + char * next = NULL; + /* first we find the last path component */ + if(NULL == (last_component = strstr(string,"/"))){ + last_component = string; + } else { + ++last_component; + while((next = strstr(last_component,"/"))){ + last_component = next+1; + } + } +# ifdef SYNCTEX_WINDOWS + /* On Windows, the '\' is also a path separator. */ + while((next = strstr(last_component,"\\"))){ + last_component = next+1; + } +# endif + /* then we find the last path extension */ + if((last_extension = strstr(last_component,"."))){ + ++last_extension; + while((next = strstr(last_extension,"."))){ + last_extension = next+1; + } + --last_extension;/* back to the "." */ + if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/ + last_extension[0] = '\0'; + } + } + } +} + +const char * synctex_ignore_leading_dot_slash(const char * name) +{ + while(SYNCTEX_IS_DOT(*name) && SYNCTEX_IS_PATH_SEPARATOR(name[1])) { + name += 2; + while (SYNCTEX_IS_PATH_SEPARATOR(*name)) { + ++name; + } + } + return name; +} + +/* Compare two file names, windows is sometimes case insensitive... */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) { + /* Remove the leading regex '(\./+)*' in both rhs and lhs */ + lhs = synctex_ignore_leading_dot_slash(lhs); + rhs = synctex_ignore_leading_dot_slash(rhs); +# if SYNCTEX_WINDOWS + /* On Windows, filename should be compared case insensitive. + * The characters '/' and '\' are both valid path separators. + * There will be a very serious problem concerning UTF8 because + * not all the characters must be toupper... + * I would like to have URL's instead of filenames. */ +next_character: + if(SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */ + if(!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */ + return synctex_NO; + } + } else if(SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */ + return synctex_NO; + } else if(toupper(*lhs) != toupper(*rhs)){/* uppercase do not match */ + return synctex_NO; + } else if (!*lhs) {/* lhs is at the end of the string */ + return *rhs ? synctex_NO : synctex_YES; + } else if(!*rhs) {/* rhs is at the end of the string but not lhs */ + return synctex_NO; + } + ++lhs; + ++rhs; + goto next_character; +# else + return 0 == strcmp(lhs,rhs)?synctex_YES:synctex_NO; +# endif +} + +synctex_bool_t _synctex_path_is_absolute(const char * name) { + if(!strlen(name)) { + return synctex_NO; + } +# if SYNCTEX_WINDOWS + if(strlen(name)>2) { + return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO; + } + return synctex_NO; +# else + return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO; +# endif +} + +/* We do not take care of UTF-8 */ +const char * _synctex_last_path_component(const char * name) { + const char * c = name+strlen(name); + if(c>name) { + if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) { + do { + --c; + if(SYNCTEX_IS_PATH_SEPARATOR(*c)) { + return c+1; + } + } while(c>name); + } + return c;/* the last path component is the void string*/ + } + return c; +} + +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) { + const char * lpc; + if(src && dest_ref) { +# define dest (*dest_ref) + dest = NULL; /* Default behavior: no change and sucess. */ + lpc = _synctex_last_path_component(src); + if(strlen(lpc)) { + if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') { + /* We are in the situation where adding the quotes is allowed. */ + /* Time to add the quotes. */ + /* Consistency test: we must have dest+size>dest+strlen(dest)+2 + * or equivalently: strlen(dest)+20) { + char * result = NULL; + ++size; + /* Create the memory storage */ + if(NULL!=(result = (char *)malloc(size))) { + char * dest = result; + va_start (arg, first); + temp = first; + do { + if((size = strlen(temp))>0) { + /* There is something to merge */ + if(dest != strncpy(dest,temp,size)) { + _synctex_error("! _synctex_merge_strings: Copy problem"); + free(result); + result = NULL; + return NULL; + } + dest += size; + } + } while( (temp = va_arg(arg, const char *)) != NULL); + va_end(arg); + dest[0]='\0';/* Terminate the merged string */ + return result; + } + _synctex_error("! _synctex_merge_strings: Memory problem"); + return NULL; + } + return NULL; +} + +/* The purpose of _synctex_get_name is to find the name of the synctex file. + * There is a list of possible filenames from which we return the most recent one and try to remove all the others. + * With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate. + */ +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref) +{ + if(output && synctex_name_ref && io_mode_ref) { + /* If output is already absolute, we just have to manage the quotes and the compress mode */ + size_t size = 0; + char * synctex_name = NULL; + synctex_io_mode_t io_mode = *io_mode_ref; + const char * base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/ + /* Do we have a real base name ? */ + if(strlen(base_name)>0) { + /* Yes, we do. */ + const char * temp = NULL; + char * core_name = NULL; /* base name of output without path extension. */ + char * dir_name = NULL; /* dir name of output */ + char * quoted_core_name = NULL; + char * basic_name = NULL; + char * gz_name = NULL; + char * quoted_name = NULL; + char * quoted_gz_name = NULL; + char * build_name = NULL; + char * build_gz_name = NULL; + char * build_quoted_name = NULL; + char * build_quoted_gz_name = NULL; + struct stat buf; + time_t the_time = 0; + /* Create core_name: let temp point to the dot before the path extension of base_name; + * We start form the \0 terminating character and scan the string upward until we find a dot. + * The leading dot is not accepted. */ + if((temp = strrchr(base_name,'.')) && (size = temp - base_name)>0) { + /* There is a dot and it is not at the leading position */ + if(NULL == (core_name = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem 1"); + return -1; + } + if(core_name != strncpy(core_name,base_name,size)) { + _synctex_error("! _synctex_get_name: Copy problem 1"); + free(core_name); + dir_name = NULL; + return -2; + } + core_name[size] = '\0'; + } else { + /* There is no path extension, + * Just make a copy of base_name */ + core_name = _synctex_merge_strings(base_name); + } + /* core_name is properly set up, owned by "self". */ + /* creating dir_name. */ + size = strlen(output)-strlen(base_name); + if(size>0) { + /* output contains more than one path component */ + if(NULL == (dir_name = (char *)malloc(size+1))) { + _synctex_error("! _synctex_get_name: Memory problem"); + free(core_name); + dir_name = NULL; + return -1; + } + if(dir_name != strncpy(dir_name,output,size)) { + _synctex_error("! _synctex_get_name: Copy problem"); + free(dir_name); + dir_name = NULL; + free(core_name); + dir_name = NULL; + return -2; + } + dir_name[size] = '\0'; + } + /* dir_name is properly set up. It ends with a path separator, if non void. */ + /* creating quoted_core_name. */ + if(strchr(core_name,' ')) { + quoted_core_name = _synctex_merge_strings("\"",core_name,"\""); + } + /* quoted_core_name is properly set up. */ + if(dir_name &&strlen(dir_name)>0) { + basic_name = _synctex_merge_strings(dir_name,core_name,synctex_suffix,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + quoted_name = _synctex_merge_strings(dir_name,quoted_core_name,synctex_suffix,NULL); + } + } else { + basic_name = _synctex_merge_strings(core_name,synctex_suffix,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + quoted_name = _synctex_merge_strings(quoted_core_name,synctex_suffix,NULL); + } + } + if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { + temp = build_directory + size - 1; + if(_synctex_path_is_absolute(temp)) { + build_name = _synctex_merge_strings(build_directory,basic_name,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + build_quoted_name = _synctex_merge_strings(build_directory,quoted_name,NULL); + } + } else { + build_name = _synctex_merge_strings(build_directory,"/",basic_name,NULL); + if(quoted_core_name && strlen(quoted_core_name)>0) { + build_quoted_name = _synctex_merge_strings(build_directory,"/",quoted_name,NULL); + } + } + } + if(basic_name) { + gz_name = _synctex_merge_strings(basic_name,synctex_suffix_gz,NULL); + } + if(quoted_name) { + quoted_gz_name = _synctex_merge_strings(quoted_name,synctex_suffix_gz,NULL); + } + if(build_name) { + build_gz_name = _synctex_merge_strings(build_name,synctex_suffix_gz,NULL); + } + if(build_quoted_name) { + build_quoted_gz_name = _synctex_merge_strings(build_quoted_name,synctex_suffix_gz,NULL); + } + /* All the others names are properly set up... */ + /* retain the most recently modified file */ +# define TEST(FILENAME,COMPRESS_MODE) \ + if(FILENAME) {\ + if (stat(FILENAME, &buf)) { \ + free(FILENAME);\ + FILENAME = NULL;\ + } else if (buf.st_mtime>the_time) { \ + the_time=buf.st_mtime; \ + synctex_name = FILENAME; \ + if (COMPRESS_MODE) { \ + io_mode |= synctex_io_gz_mask; \ + } else { \ + io_mode &= ~synctex_io_gz_mask; \ + } \ + } \ + } + TEST(basic_name,synctex_DONT_COMPRESS); + TEST(gz_name,synctex_COMPRESS); + TEST(quoted_name,synctex_DONT_COMPRESS); + TEST(quoted_gz_name,synctex_COMPRESS); + TEST(build_name,synctex_DONT_COMPRESS); + TEST(build_gz_name,synctex_COMPRESS); + TEST(build_quoted_name,synctex_DONT_COMPRESS); + TEST(build_quoted_gz_name,synctex_COMPRESS); +# undef TEST + /* Free all the intermediate filenames, except the one that will be used as returned value. */ +# define CLEAN_AND_REMOVE(FILENAME) \ + if(FILENAME && (FILENAME!=synctex_name)) {\ + remove(FILENAME);\ + printf("synctex tool info: %s removed\n",FILENAME);\ + free(FILENAME);\ + FILENAME = NULL;\ + } + CLEAN_AND_REMOVE(basic_name); + CLEAN_AND_REMOVE(gz_name); + CLEAN_AND_REMOVE(quoted_name); + CLEAN_AND_REMOVE(quoted_gz_name); + CLEAN_AND_REMOVE(build_name); + CLEAN_AND_REMOVE(build_gz_name); + CLEAN_AND_REMOVE(build_quoted_name); + CLEAN_AND_REMOVE(build_quoted_gz_name); +# undef CLEAN_AND_REMOVE + /* set up the returned values */ + * synctex_name_ref = synctex_name; + * io_mode_ref = io_mode; + return 0; + } + return -1;/* bad argument */ + } + return -2; +} + +const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) { + static const char * synctex_io_modes[4] = {"r","rb","a","ab"}; + unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);// bug pointed out by Jose Alliste + return synctex_io_modes[index]; +} diff --git a/services/clsi/src/synctex/synctex_parser_utils.h b/services/clsi/src/synctex/synctex_parser_utils.h new file mode 100644 index 0000000000..e67f8f56ed --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_utils.h @@ -0,0 +1,141 @@ +/* +Copyright (c) 2008, 2009, 2010, 2011 jerome DOT laurens AT u-bourgogne DOT fr + +This file is part of the SyncTeX package. + +Latest Revision: Tue Jun 14 08:23:30 UTC 2011 + +Version: 1.16 + +See synctex_parser_readme.txt for more details + +License: +-------- +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE + +Except as contained in this notice, the name of the copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in this Software without prior written +authorization from the copyright holder. + +*/ + +/* The utilities declared here are subject to conditional implementation. + * All the operating system special stuff goes here. + * The problem mainly comes from file name management: path separator, encoding... + */ + +# define synctex_bool_t int +# define synctex_YES -1 +# define synctex_ADD_QUOTES -1 +# define synctex_COMPRESS -1 +# define synctex_NO 0 +# define synctex_DONT_ADD_QUOTES 0 +# define synctex_DONT_COMPRESS 0 + +#ifndef __SYNCTEX_PARSER_UTILS__ +# define __SYNCTEX_PARSER_UTILS__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +# if _WIN32 +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) +# else +# define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) +# endif + +# if _WIN32 +# define SYNCTEX_IS_DOT(c) ('.' == c) +# else +# define SYNCTEX_IS_DOT(c) ('.' == c) +# endif + +/* This custom malloc functions initializes to 0 the newly allocated memory. + * There is no bzero function on windows. */ +void *_synctex_malloc(size_t size); + +/* This is used to log some informational message to the standard error stream. + * On Windows, the stderr stream is not exposed and another method is used. + * The return value is the number of characters printed. */ +int _synctex_error(const char * reason,...); + +/* strip the last extension of the given string, this string is modified! + * This function depends on the OS because the path separator may differ. + * This should be discussed more precisely. */ +void _synctex_strip_last_path_extension(char * string); + +/* Compare two file names, windows is sometimes case insensitive... + * The given strings may differ stricto sensu, but represent the same file name. + * It might not be the real way of doing things. + * The return value is an undefined non 0 value when the two file names are equivalent. + * It is 0 otherwise. */ +synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs); + +/* Description forthcoming.*/ +synctex_bool_t _synctex_path_is_absolute(const char * name); + +/* Description forthcoming...*/ +const char * _synctex_last_path_component(const char * name); + +/* If the core of the last path component of src is not already enclosed with double quotes ('"') + * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added. + * In all other cases, no destination buffer is created and the src is not copied. + * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter. + * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces + * were not managed in a standard way. + * On success, the caller owns the buffer pointed to by dest_ref (is any) and + * is responsible of freeing the memory when done. + * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/ +int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size); + +/* These are the possible extensions of the synctex file */ +extern const char * synctex_suffix; +extern const char * synctex_suffix_gz; + +typedef unsigned int synctex_io_mode_t; + +typedef enum { + synctex_io_append_mask = 1, + synctex_io_gz_mask = synctex_io_append_mask<<1 +} synctex_io_mode_masks_t; + +typedef enum { + synctex_compress_mode_none = 0, + synctex_compress_mode_gz = 1 +} synctex_compress_mode_t; + +int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref); + +/* returns the correct mode required by fopen and gzopen from the given io_mode */ +const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode); + +const char * synctex_ignore_leading_dot_slash(const char * name); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/services/clsi/src/synctex/synctex_parser_version.txt b/services/clsi/src/synctex/synctex_parser_version.txt new file mode 100644 index 0000000000..03ff897167 --- /dev/null +++ b/services/clsi/src/synctex/synctex_parser_version.txt @@ -0,0 +1 @@ +1.16 \ No newline at end of file diff --git a/services/clsi/synctex.profile b/services/clsi/synctex.profile new file mode 100644 index 0000000000..577a901921 --- /dev/null +++ b/services/clsi/synctex.profile @@ -0,0 +1,34 @@ +include /etc/firejail/disable-common.inc +include /etc/firejail/disable-devel.inc +# include /etc/firejail/disable-mgmt.inc ## removed in 0.9.40 +# include /etc/firejail/disable-secret.inc ## removed in 0.9.40 + +read-only /bin +blacklist /boot +blacklist /dev +read-only /etc +blacklist /home # blacklisted for synctex +read-only /lib +read-only /lib64 +blacklist /media +blacklist /mnt +blacklist /opt +blacklist /root +read-only /run +blacklist /sbin +blacklist /selinux +blacklist /src +blacklist /sys +read-only /usr + +caps.drop all +noroot +nogroups +net none +private-tmp +private-dev +shell none +seccomp +nonewprivs + + diff --git a/services/clsi/test/acceptance/fixtures/examples/asymptote/main.tex b/services/clsi/test/acceptance/fixtures/examples/asymptote/main.tex new file mode 100644 index 0000000000..910cef5bf1 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/asymptote/main.tex @@ -0,0 +1,117 @@ +\documentclass[12pt]{article} + +% Use this form to include EPS (latex) or PDF (pdflatex) files: +\usepackage{asymptote} + +% Use this form with latex or pdflatex to include inline LaTeX code by default: +%\usepackage[inline]{asymptote} + +% Use this form with latex or pdflatex to create PDF attachments by default: +%\usepackage[attach]{asymptote} + +% Enable this line to support the attach option: +%\usepackage[dvips]{attachfile2} + +\begin{document} + +% Optional subdirectory for asy files (no spaces): +\def\asydir{} + +\begin{asydef} +// Global Asymptote definitions can be put here. +import three; +usepackage("bm"); +texpreamble("\def\V#1{\bm{#1}}"); +// One can globally override the default toolbar settings here: +// settings.toolbar=true; +\end{asydef} + +Here is a venn diagram produced with Asymptote, drawn to width 4cm: + +\def\A{A} +\def\B{\V{B}} + +%\begin{figure} +\begin{center} +\begin{asy} +size(4cm,0); +pen colour1=red; +pen colour2=green; + +pair z0=(0,0); +pair z1=(-1,0); +pair z2=(1,0); +real r=1.5; +path c1=circle(z1,r); +path c2=circle(z2,r); +fill(c1,colour1); +fill(c2,colour2); + +picture intersection=new picture; +fill(intersection,c1,colour1+colour2); +clip(intersection,c2); + +add(intersection); + +draw(c1); +draw(c2); + +//draw("$\A$",box,z1); // Requires [inline] package option. +//draw(Label("$\B$","$B$"),box,z2); // Requires [inline] package option. +draw("$A$",box,z1); +draw("$\V{B}$",box,z2); + +pair z=(0,-2); +real m=3; +margin BigMargin=Margin(0,m*dot(unit(z1-z),unit(z0-z))); + +draw(Label("$A\cap B$",0),conj(z)--z0,Arrow,BigMargin); +draw(Label("$A\cup B$",0),z--z0,Arrow,BigMargin); +draw(z--z1,Arrow,Margin(0,m)); +draw(z--z2,Arrow,Margin(0,m)); + +shipout(bbox(0.25cm)); +\end{asy} +%\caption{Venn diagram}\label{venn} +\end{center} +%\end{figure} + +Each graph is drawn in its own environment. One can specify the width +and height to \LaTeX\ explicitly. This 3D example can be viewed +interactively either with Adobe Reader or Asymptote's fast OpenGL-based +renderer. To support {\tt latexmk}, 3D figures should specify +\verb+inline=true+. It is sometimes desirable to embed 3D files as annotated +attachments; this requires the \verb+attach=true+ option as well as the +\verb+attachfile2+ \LaTeX\ package. +\begin{center} +\begin{asy}[height=4cm,inline=true,attach=false,viewportwidth=\linewidth] +currentprojection=orthographic(5,4,2); +draw(unitcube,blue); +label("$V-E+F=2$",(0,1,0.5),3Y,blue+fontsize(17pt)); +\end{asy} +\end{center} + +One can also scale the figure to the full line width: +\begin{center} +\begin{asy}[width=\the\linewidth,inline=true] +pair z0=(0,0); +pair z1=(2,0); +pair z2=(5,0); +pair zf=z1+0.75*(z2-z1); + +draw(z1--z2); +dot(z1,red+0.15cm); +dot(z2,darkgreen+0.3cm); +label("$m$",z1,1.2N,red); +label("$M$",z2,1.5N,darkgreen); +label("$\hat{\ }$",zf,0.2*S,fontsize(24pt)+blue); + +pair s=-0.2*I; +draw("$x$",z0+s--z1+s,N,red,Arrows,Bars,PenMargins); +s=-0.5*I; +draw("$\bar{x}$",z0+s--zf+s,blue,Arrows,Bars,PenMargins); +s=-0.95*I; +draw("$X$",z0+s--z2+s,darkgreen,Arrows,Bars,PenMargins); +\end{asy} +\end{center} +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf b/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf new file mode 100644 index 0000000000..e8fdde8465 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/asymptote/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/bibliography.bib b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/bibliography.bib new file mode 100644 index 0000000000..5e796e057f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/bibliography.bib @@ -0,0 +1,9 @@ +@book{DouglasAdams, + title={The Hitchhiker's Guide to the Galaxy}, + author={Adams, Douglas}, + isbn={9781417642595}, + url={http://books.google.com/books?id=W-xMPgAACAAJ}, + year={1995}, + publisher={San Val} +} + diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/main.tex b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/main.tex new file mode 100644 index 0000000000..2f032d653f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/main.tex @@ -0,0 +1,12 @@ +\documentclass{article} + +\usepackage[backend=biber]{biblatex} +\addbibresource{bibliography.bib} + +\begin{document} + +The meaning of life, the universe and everything is 42 \cite{DouglasAdams} + +\printbibliography + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.bbl b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.bbl new file mode 100644 index 0000000000..48e803b7fb --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.bbl @@ -0,0 +1,48 @@ +% $ biblatex auxiliary file $ +% $ biblatex version 1.5 $ +% $ biber version 0.9.3 $ +% Do not modify the above lines! +% +% This is an auxiliary file used by the 'biblatex' package. +% This file may safely be deleted. It will be recreated by +% biber or bibtex as required. +% +\begingroup +\makeatletter +\@ifundefined{ver@biblatex.sty} + {\@latex@error + {Missing 'biblatex' package} + {The bibliography requires the 'biblatex' package.} + \aftergroup\endinput} + {} +\endgroup + + +\refsection{0} + \entry{DouglasAdams}{book}{} + \name{labelname}{1}{}{% + {{}{Adams}{A\bibinitperiod}{Douglas}{D\bibinitperiod}{}{}{}{}}% + } + \name{author}{1}{}{% + {{}{Adams}{A\bibinitperiod}{Douglas}{D\bibinitperiod}{}{}{}{}}% + } + \list{publisher}{1}{% + {San Val}% + } + \strng{namehash}{AD1} + \strng{fullhash}{AD1} + \field{sortinit}{A} + \field{isbn}{9781417642595} + \field{title}{The Hitchhiker's Guide to the Galaxy} + \field{year}{1995} + \verb{url} + \verb http://books.google.com/books?id=W-xMPgAACAAJ + \endverb + \endentry + + \lossort + \endlossort + +\endrefsection +\endinput + diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.pdf b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.pdf new file mode 100644 index 0000000000..cf7a1c2400 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.run.xml b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.run.xml new file mode 100644 index 0000000000..4d8dc9463b --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/biber_bibliography/output.run.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + latex + + output.bcf + + + output.bbl + + + blx-compat.def + biblatex.def + numeric.bbx + standard.bbx + numeric.cbx + biblatex.cfg + english.lbx + + + + biber + + biber + output + + + output.bcf + + + output.bbl + + + output.bbl + + + output.bcf + + + bibliography.bib + + + diff --git a/services/clsi/test/acceptance/fixtures/examples/epstopdf/image-eps-converted-to.pdf b/services/clsi/test/acceptance/fixtures/examples/epstopdf/image-eps-converted-to.pdf new file mode 100644 index 0000000000..7b92690ce1 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/epstopdf/image-eps-converted-to.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/epstopdf/image.eps b/services/clsi/test/acceptance/fixtures/examples/epstopdf/image.eps new file mode 100644 index 0000000000..fb131b9c36 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/epstopdf/image.eps @@ -0,0 +1,6673 @@ +%!PS-Adobe-3.0 EPSF-1.2 +%%BoundingBox: 0 0 432 268 +%%HiResBoundingBox: 0 0 432 268 +%%Creator: (Wolfram Mathematica 8.0 for Linux x86 (64-bit) (February 23, 2011)) +%%CreationDate: (Monday, October 8, 2012)(15:03:46) +%%Title: Clipboard +%%DocumentNeededResources: font Times-Roman +%%DocumentSuppliedResources: font Times-Roman-MISO +%%+ font Mathematica2 +%%+ font Mathematica1 +%%DocumentNeededFonts: Times-Roman +%%DocumentSuppliedFonts: Times-Roman-MISO +%%+ Mathematica2 +%%+ Mathematica1 +%%DocumentFonts: Times-Roman +%%+ Times-Roman-MISO +%%+ Mathematica2 +%%+ Mathematica1 +%%EndComments +/p{gsave}bind def +/P{grestore}bind def +/g{setgray}bind def +/r{setrgbcolor}bind def +/k{setcmykcolor}bind def +/w{setlinewidth}bind def +/np{newpath}bind def +/m{moveto}bind def +/Mr{rmoveto}bind def +/Mx{currentpoint exch pop moveto}bind def +/My{currentpoint pop exch moveto}bind def +/X{0 rmoveto}bind def +/Y{0 exch rmoveto}bind def +/N{currentpoint 3 -1 roll show moveto}bind def +/L{lineto}bind def +/rL{rlineto}bind def +/C{curveto}bind def +/cp{closepath}bind def +/F{eofill}bind def +/f{fill}bind def +/s{stroke}bind def +/S{show}bind def +/tri{p 9 6 roll r 6 4 roll m 4 2 roll L L cp F P}bind def +/Msf{findfont exch scalefont[1 0 0 -1 0 0]makefont setfont}bind def +1 -1 scale 0 -267.698 translate +-35.28 -2.88 translate +[1 0 0 1 0 0 ] concat +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit + +%%MathematicaCell +%Cell[BoxData[ +% GraphicsBox[{{}, {}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113dcTf8fB/B7697q7tW0KSoJWRnp/ba3rCR7kxmSzKy+KFlFvqSMjB++ +%ESqKrlVEKJoy2nvv3S/O+5x/7uPce8/n83m/nu+zeq7cOmeNBofDqeVyOH8+ +%2a0l/9EWydgK9V7d4EmZe47Cm8LwOycM2X1fGDzdYkfqz3L1024exbOfBoC3 +%r26ui2c5/R4EpUfSFFnDytWX/v2z/Q9yO+VvM84uo/8Hg9/5EpPBp8vUJbP/ +%fBMCXy8fN5GPKqPjn4DGhdnXo/JK1cKUG86jheEwxHn2wDHnSmm8Z7DcMeyV +%7+hStfni069rzZ7D73FreM8KSmj8KKiZGhB1z6dEHdRxdMqNl+C31zh+u20J +%zfcK1nC8d2oVFqsH/93egLor7NzkU0zzvwWPY/98uDG6WP2m1qxjhmhwtBjk +%dye/iNbzDkRHH6e5ny1Sz/k74XvY7J513WxUEa0vFhJ+vlgTkFOoztzzZ8AP +%YDckMqHAu5DWGwf9rDutaBhWqN72Z3nOn2DLwfSeGb8LaP2fobqXp8v54wVq +%RiMedJ1rPhpaFVA98XBvmqp6Y1q+evLfCRIgKtrukO+hfKovAa4/0ex0wDxf +%/We2xae/wuWhSVqYkEf1foM7E+x2fnPLU/8tJ+Ub9MHHQYN75FH9iZCe2ui9 +%4F2uuvuf5XRLghFBj47Yb8mlPJLAyiiZ11kvV71u7Z8tGcxC4sIeROZQPikQ +%nNGyS29ljvrPaLrBKXB/zgzOGJ0cyisVnGs38MYEZ6vr/gxXmwpxOW67tedl +%U35pMMcjofO/jVlq278DfoegKVuPNQVkUZ7f4RpHXT5wfJb67/I80mF2ZJu1 +%cWEm5fsDHKa6JZd5Z6o/xf3ZfoDBO+2sPYMyKe+fYGHW3z0pOUOt93eBv+DB +%/IdHm/ZkUP6/4Mi8AfcqumWo/w63+DfofvIPWuzxmzwy4HDdvBtr4n5RPRng +%XyQ/26D8RT4ZMPzRQcuhjj+pvgxocL11Ky/gB3llQtVuy7W2OelUbyYkKzUb +%upink18mhO8U4sXN36n+LKgLm1j3X0gaeWaB1zML89G1qZRHFmim+1ksHZ5K +%vtlgedzAUmNvCuWTDQ3HXh8c/iKZvLPBRX+wU01bEuWVA6+K352eiUnknwMT +%axKOmR9KpPxywHbtC6ejr75RP+RCoPeYCnfuN8ozF3oOWGKVjV+pP3LBe7L5 +%piz3BMo3Dxx73Vu6Niqe+iUPxjkazH/X8IXyzoM+e25rp+74TP2TD+LzNpol +%pXGUfz4kRp4W8dZ9pH7KhytWwuBOv2PJowD+O3xdbDv/PXkUQOigqX62cTHk +%UQAmpw6MmzEmmjwKID7AsmxD6BvyKAQn433ymWavyaMQ9Dj2C8ZeekkehfD2 +%tIXdFv0o8iiCLxaLbU3qItRn/noUgeSth6vXt3DyKAKT2d+7fgx+Qh7FUOYz +%5qzF8RDyKAazMPW0oKj75FEMtptcBlWtu0UeJTCxx4K8oZKr5FEChh625dcX +%+ZBHCdxdculc2bQ95FEK/jDse0aTGzAepXDat36zh6EPMB6lEOMyLXttSyAw +%HmXw0vl9VIrLLWA8yiDsQt/zKyvuA+NRBjv2/d43+GkIMB7lUHT/5ubmpifA +%eJTD2mtGW4vgKTAe5fB4Z5evQccigfGoAOVTaPVLiAKmngqY/6hX1yGyV8D4 +%VEBui2yWT9hrYOqrAGPprh6ey94C41UBT9Mc+z4XxABTbwVYR3y/ZB36Dhi/ +%Crh4w3aAYmUsMPVXgmNgbMYq+UdgPCvBzrn8foY6jvKohF1ulbIi58/A+FbC +%qfuxDgGKeMqnEmpM34d7FscD410J2DMwyigmgfKqgsnmb18fvPYVGP8qyP+i +%eXn3/m+UXxVk1g8+OnVhIjDnZxWU1nmvz7FOojyroIeBPsdSPxmY/qiCWTF9 +%JVibTPlWwzDfRG2DpBRg+qUaTjQei30Zmkp5V8O1LS5aQ/zSgOmfanAvvXnN +%fvd3yr8a7G59+7JjcTow/VQNMr7DyVn4gzxq4Oo/D/X5vX+SRw30CbVN8hT+ +%Io8aWFESNqam4hd51MCExLtuY1J/k0cN3LLSbRmxPIM8asBzeCet0MQM8qiB +%xasjHkZMySSPWoiyEMg7qTPJoxby2zPkrUOyyKMWLhklHVp0L4s8aiFlYNjQ +%vr2yyaMWTljpGO/5N5s8asG5x7V9qMghjzp4963x2KkTOeRRB5EuJi12Grnk +%UQeF/fc1B+3NJY+O/4+32L6lNpc86mCRzYR5UVvzyKMO9DZbrbtYlEce9WA1 +%w8COvzafPOoh3SfIODcznzzqwe12cr91SwvIox5SRvB2uKYXkEc98DhqF6lj +%IXnUw9beZyJtUgrJowFsrQ6d0ZpfRB4NYHrH/+TapCLyaIAuOL1mtn0xeTTA +%GuPw70VJxeTRABNkzUUqhxLyaACvueemlaeWkEcDOFnuMly9qJQ8GsFlgFfM +%gV+l5NEI+U+NetquLCOPRngeGBIYmFdGHo2wcvfdxR82lpNHI5zRh4MnK8vJ +%oxG4C3e46NhUkEcTNMd1Tw5wryCPJpi5wunA1TcV5NEEPv2vtpRoVwJzvWyC +%hNniq7emV5JHE+j1eBv69GwleTTBucifASNTKsmjGQ6uu2yt37WKPJph027d +%liWrqsijGZZWO1vp3K0ij2ZIWrG+uVtlFXk0w5uBsu6Bw6vJoxnEvr7uFw5W +%k0cLZAa39qx4X00eLdCw/9iod4oa8miBzg+mjuIvqiGPFni9sK/u26Aa8miB +%Al/97Y1lNeTRAv945ufdGFFLHi3Qpbvry89Ha8mjFbJ6eHq7xdeSRyu4t5vZ +%X+9SRx6t8HFCVbC9Ux15tMJJ19DlHmF15NEKBUP6rQJePXm0wrlbvnoec+rJ +%ow2GN6beXXKtnjzaIEQ7Qvmhop482mD5z+vvorGBPNrApmT4yTlnG8ijDfQq +%YhoOZjWQRxsIRxUOnz2kkTzaoelrk27YP43k0Q6orNB8m9ZIHu2w1HNA0X7L +%JvJoB0Mb7tisQ03k0Q4nnwiKeSlN5NEOrolmkc/6NZMHB0+MWM0dc6SZ6uFg +%7LMxG3Z/byYfDm6x7mLnOqiF6uOg79Tpd829WsiLgzNttkwMz2mBv+Wu5eC3 +%2QNna0Mr+XFw67ivTf0vtdL1gYPJni0OpnWt5MlBz/itIby5bZQHB1sbMyRR +%D9vIl4Mng7Y/Wydrp3w4+GOPlMvf2k7eHDwaGWJx6ks75cXF5DI/5+OGHGT8 +%ueh/JmX++ykcZPLj4nHJw1On9nKQ6QcujnPtZ5PxHweZPLmYMH6oV0QGB5n+ +%4OL7L1PajHW5yOTLRRvp2tM9J3OR6RcuvjN3mfxkHxeZvLnIyX8mKwzhItM/ +%XLR7Glv1NJ+LTP5c3Ou/b/LgbhrI9BMXh4UZzZ9nr4EMhwbKe4SeMfXWQMZD +%A9/FH4OwaA1kPDRwxL86ri1tGsh4aGBk9AfkjtBExkMDd7rFhH/boYlM/2ng +%4ATDJ1seaCLj0THvBndRdrEmMh4a2Du8LXiYOQ8ZDw18dd4yfPs6HjIeGmia +%1DnC7xYPGQ8NtN/c//GDPB4yHho4LPTEwxhTPjIeGhj4wmluhRMfGQ9NLJ7n +%mGv8H588NHHZ7mzu/ko+eWjij+WDrPjWWuShiUM9n54O3a9FHproO9Y5/0m0 +%FnloomCt/IGmTJs8NHG00ZFz9x21yUMTM0cbX4i9qU0empi9YeGrzdXa5KGJ +%Xvo9TS+P1SEPTaxVVV3c76NDHprYMNZ2uixPhzx4+NEnJXbXSAF58DBb97z7 +%xzMC8uBh2zLR5oGFAvLg4QHhp+f3xgnJg4cRws6zVl8VkgcPraICnnu0C8mD +%h14jQjcPXyEiDx7GpEws8owWkQcPV1zemBzdT0wePFzjsF5Hx09MHjzUuXtS +%5ceTkAcPJzs6TQ/YKSEPHgZO/xLhXCQhDz4mdptgumiVlDz4uPCrVpRPhpQ8 +%+HjQZoT/uZUy8uBjr415hx8UysiDj6tc5vYeOUpOHny89DlzY8RROXnw8YP4 +%2ODrCXLy6Dh+bFe1XS8FefCx3n/ZZ4tdCvLg45ul2VYv4xXkwccGvW3hdwco +%yYOPe54UTHjkqyQPLbzbdNf7AkdFHlrIUWPfEBcVeXT0kUty5/FVKvLQQr7R +%zaMz9uqSR0dfRW7eoKfUIw8tdO/WLXFihB55aGGd1ZKK9W765KGF41+08r7P +%NSAPLTzbeGeVaIEheWihmb5sW85FI/LQwtHDQspjbDuThxbqTQ8JHBXclTy0 +%8NGVKzfH2fUkD208suB8pPknQ/LQxrkbyiR29/TIQxtTp7aeKF+rSx7aOOrC +%4WEXL6nIQxsrrU5OsNVSkYc2Opd6f15yTkke2rhlkNmQe4OV5NFxXpgZWaZm +%sx7a+CT3WZoikPXQxvCAL9Y/VrIe2vhc5jRvuaWCPDr2HbF5eaucPHQwNt7k +%dUGHN+Ohg8szu38bf1dOHjroOoNTnO0hJw8dNLh9xX/Wajl56ODH7Zn+AyfI +%yUMHHS8WbhlqJicPHayrWcPvLpEjc//UwWNdgnq45cvIQwc1LfudCQmXkYcO +%9p/le+K+p4w8dNBUEuTfbbmMPHTQwS6g5JO1jDx00LKqzOSEQkYeAlyvv2CP +%WSl7fgjwXtXzlkMf2PNDgJ2Gvm1w/p+UPAQIid39v5+QkocAz37gLb2+UUoe +%AnQ03/Xp3kwpeQhwEH93Yf4gKXkIULVkY9dRhlLyEODI1Z+X+LdJyEOAe0VR +%B6pyJeQhwJeuv+v6f5aQhwBX5R4QTA2XkIcQ+00ct2nwNQl5CHFSXO2vn14S +%8hCi/y/dk2N3SchDiI0ub9bbr5KQhxCD7zSaSmdJyEOIptmFtStGS8hDiP8z +%vvlmmoWEPIRosnSx6K2RhDyEuDVm966XOhLyEGL/5o2FoxvY65UQy276/BhW +%ICYPIVb0vOMXlComDyH+1+p95UismDxE+LzfgvNxEWLyEKEgPUCx976YPEQY +%O2uA8mSAmDxEGCbuv6X5jJg8RLguUKV8e0RMHiKcK3jUNcdVTB4i9JiQmDp/ +%g5g8Ova33QgwXComDxFW/HvZ1XyOmDxE+H2cfOGRiWLyEOHPuV0/m4wSk4cI +%Rw4yGS4cKCYPMb7rbJ08qLeYPMQoWBQ86XInMXmI0eHZiU0oF5OHGL3HDbja +%U0tMHmI06GPUbNMiIg8xrkz1MThdJSIPMYYeLQs1KBSRhxiN6nPOJv5m7x9i +%PHTWI/p1sog8xHhsAudK1icReYjx48pae6uO+w3jIcbS10cy7j8XkYcY7beN +%z5j/REQeErzdq+Rh3/si8pDgogMWm0yDROQhQcPN9tEz/EXkIcFrpVMXXfYV +%kYcE3Ve7L5Z6i8hDgvsWHku//cfhr4cET9WPX7n6gIg8JPhTc/GC8btE5CFB +%jwdPJkzscGQ8JDgqLkTo5CQiDwka3NnW9f5KEXlIMLXx8TtpRx8wHlI0Gfrg +%/ml7EXlIMfRmulV/OxF5SHH1ovT9RZNF5CHFSdue178eKyIPKb5clnwkzIb1 +%6LhObBxhGT2M9ZCiw+ry96UDWQ8pfnXPnzDQgvWQ4qgVQi/P3qyHFH3vnX/R +%2p31kOJQ3dTo451YDykG8swCzfVYDynumvhQniVjPWR4e2LdqYdC1kOGd/wm +%p/ryWQ8Z1h+OifHmsB4yDGyy5V5uFpKHDAsXqVIj64TkIcOf+d2tKyuF5CHD +%AylXDEeUCslDhpv2nfjtWyAkDxmeOjjzCDdHSB4yPGuVevvgbyF5yHDv1AVN +%inQhecjwtYk793GykDzk2NX8Ysuar0KqR46iVP5Q889C8pFj3kuDSS2xQqpP +%joYDWuf/ihaSlxxf+PUoi38lpHrleOOkdvjXF0Lyk6Pa521i1jMh1S/Hx+1G +%CZww9vlMjm2Lp6RZPmLzkGNBj/bY9cFCZN5nOu476/YWP7zL5iNHq7DgG1q3 +%2ec3OVaevW3qdIPNS47lccqA1EAhPT/IMZ0/ycTen81PjsZDV+/4eVFI/SDH +%6c5vmredZ/OU48iN7p7Kc0LqDzl6XXU2Up9i85Xjhv5Nuq5eQuoXOQb5BPUb +%cZzNW47TJA8rtDyE1D9yjPfwP/T7EJu/HOf17rv69QEh9ZMcz7Xoj324l/VQ +%YJ5Gb7M7bqyHAuHBsCF3d7IeCkx4fu126HbWQ4G2ZxsHfdzKeijQftr1psJN +%rIcCM8y/LFFuYD0UaB0SrTt+HeuhwHqfrF0HVrMeCtTvMz3z1QrWQ4FCboNS +%uox9flZgnx4tUSsXsx4KfHuv2kvtyHoocMWXDVEmDqyHAo2CTZeem8d6KNBj +%qnO99hzWQ4EH7B9P9LBjPRToGHFHXziD9VBgb4d9tRemsh4KvHz1XTeLyayH +%Ant8CV8XM4H1UOCMkVYjncaxHgpcN6abg2oM69FR75Tvs97ash4KxM3JbXtt +%WA8Fpr25fWXkSNZDidf1lpa3W7MeSuzbuVfEx6GshxI3Ogi0AwazHko85/Xo +%k6sV66FE25/LdtoPYD2U6PZsQdJIS9ZDiQOv6Bv0sWA9lCi4yNtoYM56KHFl +%U6yt1JT1UOJUt4hRot6shxI3KOSPJMashxLN93vl6PVkPZTY/L6+xLg766HE +%IapVUuuurEfHfHe+JNh1Zj2UOCWiJGyzEeuhxBXVRulnDFgPJf7PetaiZ3qs +%R8f+4bSwPBXroURV1to4QyXrocQj/V2WzJKzHkq8O2DNvpNS1kOJfcIUNz+J +%WQ8lFojPfFWKWA8lBqgn9VoiYD1UKHTg6t/XZj1UOPfEleh2Puuhwp+xr7Y7 +%8FgPFcZ3VWs+0WA9On4/cmO2Lpf1UGFfYd85u9vZ90cV3ihKm5/VKiAPFdrc +%mxNm1yIgDxXqn71l/KpJQB4qPFUxy2FYo4A8VDhoZoDmw3oBeagwwSZpgGWd +%gDxU6HqWj8E1AvJQ4dV1Rw8PrhaQhwp/VxQufV4pII+O96i4M7mTKwTkoUK/ +%DTv6p5UJyEOFlY+G/LOpVEAeKtxxcYAbr0RAHh3vJZHL1gQWCchDhbc1upWP +%7nhfZjxUqO57Lfp3voA8VLjxznOlR56APFR474X0i2WugDxUuDpzSmJatoA8 +%dPHMBqeU41kC/D+QSguY +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113dczfsfB/BzTvPMzoispGGEzJtrv98qZFzRtSJZyYpCXDKvkK3749rE +%jcxKSEY5lAZpUlFJpb3XaY9fnPfn+0+P+n7P5/N5v57v8/30MV7lZr+Gx+Fw +%VFwO5+dPdg3+Y5N4+KVq5W79wOm5nofA0/+3/IGu7PdzEN594CYdqFY+73u4 +%bN7z6/Cs6XVygozdvwX6g/qU18VWKS9f+nndg0fWhkbCzVX0fCCk7Bv4qFZe +%pSyf9/MvwdBjiWr0ndBK+vxT6BD6xJg4VioF6X7ukwShMKbG1HA1p5LGewHZ +%++0H7btVoTR3PBOhGhQGO2rTps+3raDxX4P+DdstzWXlyltdn073ewOhltbC +%ZWfKab63kL88dvbVUeXK0b+uSEiV5ReeTy2j+d/B1ee6G5bvLFNGqgZ1zRAF +%O5xbi+t7ldF6YuBqovuk2eGlSvtfE8ZCg8fgHNflpbS+9xAbPnuZE7dUmev5 +%c8APwLkqCRD6ldB6P0LyuaUuJ2xKlFt+Ls89Hqwnx71NKCim9SdA2Lp9JzKP +%FCvVGkmg52Y18uHAYqonCYKa3nNmxBYpbX9NkAzXJAc2PlhXRPUlwy3jzsI4 +%3SLlz9kcz6RA5tkn7hF3C6neT2CYE3l5l22h8lc56Z8gZmfeAlVRAdX/GdxP +%PHIY712gNPq5nL6pUGIc6jhlQAHlkQq/dwwM1YvKV651+XmlQdEq8YQbq/Mp +%n3TQ+K3kdQM3X/lzNP3AdEjs7GfW7cYPyusLRINQ0jTph7Lh53CqL3Aq4suQ +%S5l5lN9XUOVmFPJ25Skn/xowA/zPXTQf2j2P8syA50NHfen+JFf5a3mHM6Fk +%pgKi7HIp3yxQZedNtizPUcZ//HllQYjzNff1R3Mo728wKLf4nINZjrLbrwVm +%w8fBR5b3P/Cd8s+GaTsvhotCs5W/hnP8Dk5X27ttrPhGHjkw0WnO10mm36ie +%HFgd86Jn4OIs8smBsOJpIT6nMqm+HDjyg38t+20GeeXCbW2vRc9UX6neXPC2 +%2u/FM/9KfrnA3eSv/W7pF6o/Dza+Oe7deiqdPPOg7MDX9TeUaZRHHvydM2hj +%YnUq+f6AKzGlzm7GqZTPD5iXYM7bN+8zef+AJ2KTqsoDnyivfPhkG+SQEpRC +%/vlwPim0b7/sZMovH2xsj1/OFyZTPxRAwve5vbnjkijPAugR4NA8LC6B+qMA +%rlVsSZ9lGk/5FsK9U3H3eZ5x1C+FIGlW/uuZ+J7yLuwab8GecNNY6p8iGO9+ +%4EzhjmjKvwgcbTSuPIt9R/1UBDGWv3lb9owkj2Lw06yb4r3uLXkUQ7NjBkdv +%k5I8iqFu3o8HI7XDyKMYcFl/j9arz8mjBB79+8a1bUQIeZTAyqiAxNa3weRR +%Am8MF036cuMheZRCt1PtYX7Pbyt9fnmUwoDjh5J/O3eNPEohzF0ybo3DGfIo +%A6ltcsLzU0vJowwKl/+5aPPxo6D2KINhTwt+t553GdQe5ZAoVsaeHn0L1B7l +%0G3btXHHpj4AtUc51C31i9g/LBjUHhVQNfNA46vwp6D2qADz/unbP9o9B7VH +%BdhscL75Kf8VqD0q4UyNM7zYqwS1RyW0FS7vcffkW1B7VEJvQWa3MY6RoPao +%gjEF5+18LaJA7VEFpx/vbjvOiQG1RxV8Xvjmo0NqLKg9qkF6LNBP+eAD1VMN +%a68EO1sd+ghqn2qwG7Dkej+nBKqvGvz17Zf0MkwCtVc1bPg2vL1EmEz1VsPY +%1QLfqNZkUPtVQ+VX5Qf/8hSqvwYMBla+cMn+BGrPGuAG7p1skfyZ8qiByg+T +%fpS9SwW1bw2sjO5Xe/tFGuVTA6/GTHA7F5QOau8aMJlqZ+3l/4XyqoXIurm3 +%V1//Cmr/Whi4fyLf5UIG5VcL1teK3Lz/yQT197MWjF2PZoeczKI8a+HJvVnv +%dY99A3V/1MLmiKySE0eyKd866FXzunrm4e+g7pc6GBXTuf+SVQ7lXQfc2kWB +%+pE5oO6fOrjbvqZ4ik0u5d/1/O4B3lrRudRPdRDwMbl9v20eedRDbIXy7am4 +%PPKoh8yLgZZWdj/Iox7e7L5w6+anH+RRD2aen4fdXJxPHvVQ5j55vEd2PnnU +%g2dzg26+cwF51EPtjUdT+OUF5KECYWhP5+JtheShAvel//O93lZIHl33VU4h +%zYeLyEMFE6Lt+hpJi8lDBa17/+gluVJMHioY1So/8W5ACXk0QIjDswKbJyXk +%0QBr37y56Iul5NEAW+L8xsQllJJHA7xvaX4Wv6yMPBogvnlQw7OKMvJoAGXd +%UdHefeXk0QivfDJmWEoryKMRhnlJL3z+r4I8GiHgjPNchzGV5NEI/w4LPZH+ +%oZI8GmGUym6X3Yoq8mgEh2aVLKKhijyaYE5q1jKT6dXk0QQlw8w+LztbTR5N +%IE57eXFUTjV5NMEGvVozT4sa8miCfO302fN315BHE3zof8Ip4n0NeTTB5rTT +%VyJ61JJHM3B2Xk+yX1dLHs2QluP7ZF9XX6o9mqGh3qvZll9HHs1gpJ138fGS +%OvJohhGpbSvCH9aRRzMknvxTd3NnHXm0AHSGXky0ryePFhgzufVOhn89ebTA +%FIXlf0da60H9vmwBB72ipsa5KvJogVjf7lLjOyryaIFkc9tGfoeKPFrBcOti +%8aMFDeTRCrevX3GxCGwgj1boF5dvtFmnkTxaISfwndOBlY3k0QomRgY/Noc1 +%kkcrHHlwr8aiRxN5tEF+5KrEDI8m8mgDX/25591TmsijDXQ0C3PLRzSTRxtE +%X4j0W+nTTB5t0DvJ1zClupk82mBZvHTxNPsW8mgD/rT3CyKftpBHOzxtfHd/ +%aY9W8miHovSG97y9reTRDkGizVMS81rJox3mbDuT/nZGG3m0w4qNx4wKgtvI +%ox0875Q9ntm7nTw6IPjI56aWw+3k0QFXt3OOltW0k0cHuKnyAu2Xd5BHBwQd +%uh5mkNBBHh0wanODxazJneTRAaNzd5xvDeokj04oWeJZcEnGQbVHJ7wr2nRE +%PJuDao9O+G+tcdRcbw6qPTqhbrGv65ZIDqo9OkF/4PUH2zhcVHt0wgl/XbcN +%k7lIxw8UJI05u2gvF9X1cBCl9scwnItqHw6aTkgy+62Di+r6OFh4PmCbFfJQ +%7cXBD/d77v3Li4e/ynXhYNbMBSPjY3io9uOg2bkZWavEGqh+P3DQ4KzK64/5 +%Gqj25ODMx3O27L+qgeo8OCjteBpiXqiBal8O8rYlr5w+UhPV+XAQ7jcpyvZq +%otqbg8YbRm0e+1ET1XlxUf/FX74j+2ih2p+Lnat6DO3cpEX5cfFl8FiLgLda +%qO4HLh4VGQ9ebqBNeXLxRVzfxQPctFHdH1z8ZJQ01uK9NuXLRV9pypgL/XVQ +%3S9dOXlM2Hf0kA7lzcVlwvVLpIU6qO4fLjYt9xi3c6Yu5c9FLc+P2U3Buqju +%Jy7+I9g47mofPnnwcNhL311hx/nkwcPoDudTfm188uBhvJ5BwPGtAvLg4d8O +%UWbPywXkwUOx8/LprzYKUd1/PBxfHFflXyUkDx6+CVq3a+guEXnw8O7Cjffu +%6orJg4cbfWIL4q6LyYOHt5Pfz5AF9SYPHnb7LBoSy+1DHjwMM3QOaV3chzx4 +%GGR6rLdhSB/y0MC/0/Iza7obkocGZk8JsRXuNyQPDVz1oXTUkkpD8tBANIve +%2telL3lo4Pht33pvKOpLHhp4v8+2qhEeRuShgTcW/jN/rE4/8tDAThPrP244 +%9iMPDZQs3DHHLqwfeWjg14FBwQJzY/LQQM9v376Y3jEmDw08qLI/vWKSCXlo +%otuIJIlzowl5aOLIwRn+O8pNyaPr/iGRbdX8/uShiQZZ3+dO6DGIPDTxYWd8 +%jCKjJ3loYknACkHIoO7koYmPex61bXymTx6aOH5MmMAxWUEemijWd/3DaKiC +%PDTR8L1NYa+HcvLQxChweatvIycPTVyy0ju6tlxGHpr4+ftTU/xPRh5aGP17 +%4m7jlTLy0MIBPoOzjg2UkYcWLo25u1G7XkoeWniJJ+x5LUpKHlo4u1D/wror +%UvLQwo+9dwYleEjJQwtv3g84NXWelDy0MP+CXbTbCCl5aGFa6qvF9TIpeWhh +%k7zVtVeZHnlooXsfk+jqED3y0EJN/81nZYf0yEMba819Xcct0CMPbXwdHjWF +%a65HHtoY9Ubv3ymdEvLQRp6Z7+176RLy0MaRL+6M7PtYQh7aaOx83Hn9aQl5 +%aGNV+czgTa4S8tDGluKpjzVnS8hDGz1qb+jqW0jIQxtNjZdpe+pJyKNrfQOu +%pPSvE5OHNmrsSQpsSxeThzb2EY/dXRYuJg8dLHGqds+7JSYPHdTP0f6ccFJM +%Hjro8yfY+G8Xk4cOLg/33Dx/uZg8dNBb9iQ2aYaYPHTQSDJimNRSTB466G/S +%mqpjLCYPHTTfGaJ5VywmDx107N1rZ2WLiDx0cGSywcfEYhF56GD47/XdbNNF +%5KGDzQ+99s+OFpGHLmbrTHzyKUREHrpYzdsd8e22iDx0cfSGqMmrzovIQxeH +%9c7hO3iLyEMXX05J7/F6p4g8dLG5V8m8UxtE5KGLi55effDOUYTq/VMXvxqp +%+jrYichDF2M8suNmW4nIQxc5OS79L1qKyEMX2wyyD04wF5GHLh75Ni1wmKGI +%PHQxooeBdJtMRB58/LtgtCNPW0QefMy//1dxVouQPPgYv2b31tau96nag4+v +%XgSecCoQkgcfl5ekGWhkCsmDj896xRgXJgnJg4+Dd52M1IoRkgcfT1gIuCvC +%heTBx3spQcl1T4TkwcdvB9Ymvr4vJA8+2rqeLgq7KSQPPl6/sci++qKQPARo +%5yX0/NNHSB4CXLpte1WRt5A8BHhO5BR2b7+QPAT4tv2R34W/hOQhwDcnF395 +%7Mb2DwFqu64c27iW7R8CDDgy+tnqFULyEODU0szHzYuF5CHA0nqN5KfzhOQh +%wPXNrYfPzxSShwCXcS1DL1sLyUOAbgdbPkVMFJKHAEuMv6tEY4TkIURJdIVo +%13AheQgx2DsmX9OceQix0Op+aJAJ8+iqqxjSPfswDyFO/2qdv6o78xCi6Vfj +%UBcp8xDioI8Hg70FzEOIs2dGit9pMg8hjjBJutirU0AeQuzu1/j2RLOAPLpy +%0fJw6V4vIA8h5o/au+ZFpYA8RJhitX7dthIBeYhwT8PVy9PyBeQhQutX872G +%f2f7uQgnjHs/aEQG289FuNEsbfTUVAF5iNDC/4iPe5KAPET4wcZD+1GcgDxE +%6DVP8kQjRkAeInx18bnTxggBeYhQ3zH0ZmG4gDxEqMed+JvHCwF5iNCy4mu/ +%biEC8hChysMuOPqRgDzEaFw9euvxhwLyEOPShA6bFXcF5CHGPtutA6fdEpCH +%GJ10Ok5MvCEgj6733mj9xdZXBeQhRpdHdpccLgrIQ4yTps4u3X9OQB5iHLiE +%0/+pj4A8xJguWu/SdJJ5iDFlgK/FzGPMo+s9G9Gt495h5iFGf4HLgx4HmYcE +%b1yO/ufcPuYhweDq0j2Gu5mHBL+9rjj55C/mIcFdaXXOCz2YhwR3xjsO0trC +%PCRYse+k6s0m5iFBt14Bhkc3MA8JDrsksF26lnlIcHPGWfPxzsxDgsbufZ+a +%rWQeEuSb/q+ppxPzkGBBt57+PZcyDwmKeesHmy1mHnq4qseQK+MWMA893JOn +%cXixPfPQw5PWAScP2jEPPbxzQFUQMpt56OH7IXOSamcwDz18azD56LjpzEMP +%df13NR+1YR56qCzjD86bwjz08EuMz1MbYB56aJt57a+gicxDD+tdtf82Hc88 +%9DDZ82xf39+ZhxT3h5z+x9SS1SPFpcX3W4JGMR8pxqc8gqkjWH1STH3s0TvP +%gnlJ0fSDzcIjQ1i9UozrnDxplDnzk6JzxSyrwgGsfik6ZZ22v2nGPKXoenXo +%59UmLA8pZo9qX2jRT0DnGSnaD18wqMOQ5SPFT52Gw1J7M28pHtedavekJ8tL +%irEjxuZeNBDQ/w9SfL3uqMehbiw/Kc77WuC4Q8H6QYobrrQI3GQsTynOsLm4 +%c5Me6w8pHtq1ymarmOUrxYhYTYO9QtYvUsw38Lx9is/yluKQXe9e+umw/pHi +%rPMh+W+0WP5SNJ46uyNPg/WTFDO+WFQJeMxDhqtj/XljOcxDhhkV3kUbOth5 +%RIaH95Q5/td1HlF7yPBskEonp4WdT2RoLbr+p0kznzxk6PV41sv1jXzykGFm +%wZX1z1R88pBh9/DWYzr1fPKQYa0lRC6r5ZOHDDfYTk57Xs2n75sMeZYPAntU +%8clDht3qvYP3VPDJQ4Z7jYOHFnbti2oPGbbt8Hr5ZymfPGRoar9hX1Qxnzxk +%2KNuz/cJRXzykKEiup/LswI+eciwMbNshmU+nzy66onIcnyexycPGRY6KEWQ +%yycPGTqvtDr64TufPGRd++W6IodsPnl0Pe+4Pqk8i08eMrx+uWz6oUw+echQ +%33SWX98Mdh6U4z1RfHb4Fz55yFFof7f7inTmIceTcycaaacxDznGrYvTfPSZ +%echxzb31lss+MQ85BuS2TpekMA85Jtne3hSRxDzkODzlwlLPROYhx/xdlncs +%E5hH13p8EuPqPjIPOe6LmjE+JI55yHGuR3CO5wfmIcd0T9v/Wb1nHnLsNcRv +%oSSWechxXuTV4qxo5iHHPSr90YFRzKPr+VkNd7zeMQ854qL+q5dEMg85rm41 +%0bWMYB5y5G7b1iF/yzzkeG7B/rJaJfPoWp972j9pr5mHHHdecVgWFs485Cg7 +%fbrzVhjzkGPaC9l2n1fMQ4HXE9eH73vJPBRoNUU0x+0F81Dg6CVyyernzEOB +%X6sbZQ6hzEOBKxx5bfOeMQ8F7iqT1c4OYR4KfPhxnfGsp8yj69z3tDhx5hPm +%ocC+kdut/njMPBTIvdB9rX0w81DgmS9xdQ6PmIcCqw6dPLc6iHko0GtC761u +%gcxDgTNqHhzeG8A8FLjK23n86YfMQ4ELx0zk3HzAPLrGFy6c8ew+81DgvLuu +%ofH3mIcC1xTuVRXeZR4KNLvnNIB3l3kocOe3yQeN7jCPrvWIXGPBn3ko8Nje +%KcNX3mYeCjRasrvl0C3mocDl/LOj7vsxD31U8aOPp/zHx/8Dq98lgg== +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw92XVYVNvXwPGhmWKKDhEMbEUM1Ktrid3YCihiKxa2qIAoBnaDioqg2Ipd +%iIFSiiAlId3dHe/8nLXf+ec+4zDnnL0++3tm5rlmyzfPWaXM4XCKVTic//2X +%PbQjVt3cYFYdulf78aRs1+3g/sXs10Yue34Ivlu9ilpRXRX6potX6ew3p6D3 +%mIPB81Kq6PULcCLT8uSYz1WhV3z/PeDzLT+b7vfY31+HuqG1aZyzVaFls//3 +%L/6w03WFS8Ju9v5AMHs28NatZVWhvOSALaN5QbCxuKlu5WR2vHtw9IJdTI9B +%VaG9HU5/qe/1ENLfKIen67HjP4Zpa0yFxp2VoYHydycHPIX7g7qt3xhTSecL +%hulPXSKe+1WGWv17PIfsgLEJRRsq6fwv4Ph7nrbhf5WhX+t7yc/wEj41GBkO +%4lfS9byGjdHN0aNTK0LnKE4Ih78b3xp5r4Ku7y04mTSt67e7IjTb9X8HfAfn +%qnfacSdV0PV+gKqCRz9zdCpCXf53eVtCYAVf9jMor5yu/yPsteqMdHheHqrQ +%+ASeE0MylDzLaT2f4Exd3RV/2/LQyf9O8Bly7xvs72taTuv7DEFfs/Y9LC8L +%/d/Z5KcEbtZSf+MPZbTeryAJG3d0t3dZ6L/lJH+F/FuvdvxYVEbrD4PBwxY3 +%yyzKQk3/dzldvoGp46os2/pSmsc3cJDdtNj5tTR0zer/Pb5D65m2Bu+zpTSf +%cICEdKubjqWh/zua9uNwcNjeZdT1/qU0rwiYe0IWeaq1JLThf4erj4CUA15n +%N0SW0PwiIWxa4f1xl0tCx/w7YBSYHjlkpLyqhOYZBQ4TT4z4OLgk9N/leUXD +%U6zf6sgpofn+gKRLQzM5McWhP3/8ewDf763D8avFNO+f0PbXeKXGuuJQnX8X +%GAPqNjONXYYV0/xjIM7JqiFWpTj03+EcfkGBXmhit7gi8oiFoea/nddeL6L1 +%xEJiqvYdX+ci8omFlFNZbh+ti2h9seD+ZKJ7sloRecXBDY/pq9N+F9J64+CY +%hQ8/+UYh+cWBe9SQ/z5vKKT1/4bvzt+KL44oJM/fwD9oc9tZvZDm8RsuRK8d +%1iu+gHzjgXMoKiPxRgHNJx6W+IU/2r2hgLzjYYX/8yz+iAKaVwKExV/tf0Kt +%gPwTYM3Er5eUf+fT/BLA5dZI8fLr+bQfEsFpc9cL79bn0zwTwbjlV1nHsHza +%H4mQvN5NNkoln+abBEe2XJ239Fce7ZckaIjUXbXvah7NOwlS2/5YHFqTR/sn +%GVotM57vs8qj+SfDHu1xHWs7c2k/JYNd9439JkXnkscfaEvj6Usu55LHH2j0 +%MkqOXp5LHn/A13995p4BueTxB56knjxq0JJDHilwf6nG5wffcsgjBRYdsftt +%cTaHPFLgs9vTyEsOOeSRCvYf9h4rtMgJPfPPIxU0jvNVR9dmk0cqSIzUc90+ +%ZpNHGvwYtS324bFs8kgD2xubT36fl00eaVBQ9X1/smk2eaSDU6uKb0JJFnmk +%Q0+uWuOHl1nkkQ53annfrnpkkcdfUDq+bZXTtCzy+AtN+3+46OtmkcdfaNlj +%cufG50zyyICzi7UHpCzIJI8M8I/aYZRcmkEeGZAuWWr52yODPDJB9PSkNFAn +%gzwywW30om5T7/8lj0wYZlh0+euYv+SRBWbFaR0Yn07ryYLgbNNLm9ekk08W +%TEm6vdK9NY3WlwXlPw3/XDydRl5ZcG7bss0e3dJovVlw1K/oVP/XqeSXBd+G +%PJ4cPTWV1p8NxVMKbC0zUsgzG1JOO0494pJC88iGiPx7NrdUU8g3Gy6Kxzje +%vvyH5pMN6a1DL13q84e8s0HT0UAyISSZ5pUDw/bsX58/K5n8cyBsQ6bvvJwk +%ml8OOK5v331mexL1mQOcHxDwQz2J5pkDHaOFWaE+ibQ/cmDtfjO/q30Sab65 +%cMBlgWzShwTaL7mwdUzLk6QZCTTvXNihrZ3aLzOe9k8ubN7/Jsp5SzzNPxf0 +%cnydzynF037KheHdIq9uOPebPPJgdYKpBXT7TR55cClx4tqE53HkkQf+eht/ +%TB0fRx55oPJSctAnIZY88uDswgHWL1bGkkceCPaa2B8u/kUeeTBw29fBNZq/ +%yCMfbv1NO2vTK4Y88iEvob/n6kk/ySMfVreUCKtW/SCPfNCf/eQU/1A0eeTD +%3uhlBma3osgjH8oadgmuh0aSRwHIek/vofQ3gjwKAB1GDMpqDiePArDy3cs7 +%oxtOHgWgE/9+/73B38mjAAbqlZU+nvmNPAogWtnSqff6MPIohNJC7Y/WXl/J +%oxCiFoS/HHTzC3kUgsEj9ef+7z6TRyGI+L0CTiZ8Io9CODOieOGZ9FDyKISc +%Xe5/F2t9JI8i6Dzq0aMJP5BHEfxYfyqqzuUdeRTB+T32vLRbb8ijCIKsH327 +%HfeKPIrAvjN+XDfOS/IogvXBJqKSAc/Jowhix63hdtgHk0cxOP83dMgXyRPy +%KAbPoaf7DVv7gDyK4b8dU5/afAwij2JY/evarHeSQPIohlvzB00dsOomeRSD +%o/vxzqz1vuRRArsTZaVzNM6RRwl8nvJshfYUL/IogafF2fnCn+voflkCHwev +%0zgVtQUUHiWwMtHiid64I6DwKIG6D0fX+krPg8KjFP7Embw9e+wKKDxKYdPQ +%8eMfKvmDwqMU5t6o5Ol+DgSFRykkcDpDAw7cBYVHKchOtK6WTHgICo9SeDKA +%Z/pK9SkoPMpAd+noUVGFwaDwKINIx7KNrtHPQeFRBpaW81ISnr4EhUcZpGWU +%ds649BoUHmWQ9KjT5sL+t6DwKIO+r3q1c1a9B4VHGTzLyf4weWYIKDzK4Rtu +%MRpoHQoKj3IYZZf3adnZT6DwKIe/qycen3LkMyg8yqFL1igfbbcvoPAoBxU/ +%G+G77V9B4SF//ip+KW4IA4VHBbgOM88UrvwGCo8KmOXzNPazw3dQeFTAgfqw +%9PL54aDwqICffYpCvGdFkEcFfM/ZZL17SiR5VMDxSV8Tt46PIo9KaEjs8mgG +%RJNHJej8mvV8+qgf5FEJu0KMcvyG/ySPSpjvn198dkgMeVRC+fvY86qDf5FH +%JVzYaFAxQD+WPKqgclvtm02XY2k9VTAtN6mbnl4c+VRB7dlrZXqX42h9VTB0 +%4fusgXq/yasKmhcmX313+Tf8W+7qKjgU+2D8Lv148quCHqoLQ375xoPi/iA/ +%vn1/tzFGCeRZBUPumZ9W9UugeVTBjA+LDfRME8m3ClaHR5697p9I86mCbDuz +%Oyu7J5F3FaTd/V1+PiiJ5lUNE23umRX0SSb/ajhuONbmiPx7jGJ+1eCghIa+ +%g//QfqgG3xNPHgte/6F5VkP4l+3hBaNSaH9Uw9NUx3Fln1JovtXwsNqzfMfE +%VNov8ufvT2k6/EileVfD/KcvLh6ck0b7pxoM9Mr366ek0fyr4aeT3ZZWx3Ta +%T9VQHBI1aW5hOnnUgFr40sdKm/+SRw28nhxwR6nxL3nUwKrOX3FH3DPIowaK +%DI/lzdLMJI8aWBIWl3jgbCbtvxq4Mi8x6I4wizxqYNyJn2qX52SRRw3EHxUM +%iricRR7y4wVd0xz5N4s8aiBn9Phbf82zyaMGVKpGiB+szSaPGkh92f3Yg8fZ +%5FEDvMXqFcV12eRRC5xxO1rmjMohj1r4e33qT65nDnnUwtnYX5dqInLIoxbm +%LbpiqinOJY9aGKqnKVi8MJc8aqFQ8mtp4fVc8qgFu6v+V98V5JJHLaRr3Twe +%PSCPPGrB4m3IQY1deeRRCy3LNqqvD80jj1pQ43HaLDXzyUN+PXfjK6xn55NH +%HTgdWbLK7ko+edSBpvY9i4jcfPKog3737p850L+APOrgS5vw0rZdBeRRB/7O +%2c+DPheQRx0E1AgdtQSF5FEHPl39e6csKCSPOkj/o4Qp/oXkUQfz/Fc/E5UX +%kkcd3LbS+3DRuog86kB0eELEskNF5FEHPd1X9F0SW0QedRAvOvr9rXExedRD +%2LvqyCXrismjHjbN21jR81UxedTDt4DYpctUSsijHsww7VyYbQl51EPkWusd +%ftdLyKMeYkK6C26WlZBHPdiEZvatGVlKHvWwIXj7voPHSsmjHob0DN22608p +%edRD/rzaO18tysijHjr87p5evKuMPOoh/rvznOnhZeTRABt87ilt0isnjwb4 +%41I+qHZNOXk0gJnJjx0Rb8rJowEe1RzrVcStII8GaN88qmKRfQV5NED+144/ +%Ux9VkEcDTOAZzdzWWUEeDfC8JHWj6pxK8miA99WDs7MDK8mjAQ7ZbpCNaKok +%jwYI/FRlGNejijwaADr2rI2ZVUUeDXBqit7Quj1V5NEIz5qrtNYEVpFHI1zx +%+vAzPKaKPBrhq75j8IHmKvJohIvaSeUruleTRyNYmxuZuc6qJo9GKFvNmRDq +%Wk0ejTAxqb3PpDvV5NEI+guqVIrjqsmjEZRTalzT26vJoxHMqkdpN/euIY9G +%2P22Z8qwBTXk0QidX3LLP3nWkEcTcLtnmxx+UkMeTbDQ4eqU1ek15NEEM/c5 +%V5zm1pJHE3iPW8ipH1ZLHk1g+ZRvfnJlLXk0gavLI32Hc7Xk0QQ8aC3a8KmW +%Pj+bIKET1j+vqCWPJlCa9PLGCJM68miCQwYTZqpOryOPJtBqfSZrknem8GiC +%GXYux0bcryOPJkgZ3e/E55Q68mgG7V+/9m3g1pNHM9TxdtusH1FPHs0Q5587 +%JmhdPXk0w6TdoweNvlJPHs2w+/Sjbs1R9eTRDKbJzq86WuvJoxlcZLOU5/Rv +%II9mUNpbujhnaQN5NMPZiMaAkDMN5NEMWZMHL6j40kAezZAU8mPIKvk+Ung0 +%Q2FHjptJr0byaIGVPXtv7WnfSB4tMMVr5FuXU43k0QL9HhWNNZY7KzxaYETl +%S2N+QyN5tMDYqEvR0/o0kUcLvBo/7savpU3k0QIbe8R+DDvfRB4t0PfdzB6N +%EU3k0QI7Fm5o9OpoIo8WCLFYOF8/Q4oKjxaIfXJO716pFBUeLaD5JF40q1mK +%Co8WkG36XifUkKHCoxVarG0PpWvLUOHRChmaHZN/mstQ4dEKCw+Z7Pw5SIYK +%j1b45N1rS9EYGSo8WsFhsINJlxkyVHi0wpqVnGtb7WWo8GiFQ5udshPWyVDh +%0QptGxdtddgtQ4VHK1SEZ5nUHpahwqMVat2dBPcuylDh0QqOlrsa9gTKUOEh +%//vpD6rXPZehwqMN7IOXzHX9IkOFRxvYuukdvhYnQ4VHG2SmvHEpzJKhwqMN +%rH4ccJxYJUOFRxv0O9/o+aNThgqPNjgwrO7ZZpE2KjzaYGHiEu3hptqo8GiD +%8+laa7oO1EaFRxu8v1jxujtoo8KjDUz6en6ZOUubPNogfH/ieD9HbfJoAyja +%0SLdok0ebVAx8uqpJx7a5NEO626UqK09q00e7VBuUpc46ZY2ebTDD2fJ7kXP +%tcmjHQb7LjA4FqZNHu1wYULt4cJEbfJoh9l/zu7dXqhNHu2g17Ww26BmbfJo +%h2U3eF15fB3yaAdlw/fnzU10yEP+3MZnxcqBOuTRDk3LqnwSx+qQRzvMeZw4 +%fc88HfLogEQ3t6Xz1uiQRwcsueMhXemqQx4dMNxNsvjxSR3y6ICSEaeDevvr +%kEcHWOxy6Z33Qoc8OqBVvfJBSoQOeXTA/uH3tsn+6pBHB+ztlr/0WLUOeXRA +%26WGmxPUdcmjA5Tm9soZY6RLHh3gZDq2+PAgXfLogFmrHs/Qm6hLHh1gddNA +%udFelzw64YptnorJVl3y6IQ+1UOWXTuqSx6dsPH4VgOXG7rk0QkhLuU9/F7p +%kkcnpFefi+kVo0senaAzwfqdYYEueXSC2q4wvmOHLnl0wpi/Wp56enrk0Qmn +%BY5lYwbpkYf8+ZGlvIQpeuTRCcsObXuXukKPPDpBd9vgCkc3PfLg4L4bhfdd +%fPVoPRz8kHxCvf2FHvlwMPY294xxnB6tj4NuEw8e+16uR14cNEy2tdTh69N6 +%Odir4Ty3vZc++XEw7WTKxzuT9Gn9HDR+YN2larU+eXIwtCxGWH1Yn+bBwQil +%P88ig/RR8XuGg9OcvVLXRurTfDiof23AqOJSffLmYPLNgPEzRQY0Lw4K04Xd +%Aq0MUPH9gYOcZuONnYsMaH4cDBKHa69wN6D9wMFxyz28iu8Y0Dw5uGLtg+sn +%fhnQ/uDgn54R8TubDWi+HDzQ2NF6s7sh7RcOWnIe2JrONqR5c1DdL+Zoi5sh +%7R8Obgy4u6zbY0OaPwffzEk79iPDkPYTB20M1B8ki43IQwknfM4Zaz3eiDyU +%8HAT72L8biPyUEL19xdFux8bkYcSbsxtHPktz4g8lLDzmajFxMiYPJQwymRD +%6WtbY/JQwqS5QeXcQ8bkoYRio/jMzmfG5KGED8x9z0xKNSYPJZzqeMSgvs6Y +%elPCaxNmDBe2GZOHEo7fYdTVqMCYPJTQPnZyq/NdY/JQwkpf9Z+bRxqTh/z9 +%vMC0l1eMyEMJhzyueFUXb0geSsiPFJ01b2YeSmh0vXfJTWPmoYQyk6FqxtP1 +%yUMJ7XY9Vg47qUceSng0+kLj8Fxd8lDC0dkvSmymsZ7lr7tVeZwMY/cnJWyo +%35//aLoOeShh1/aYyxOztclDGTt8Bl1K+//7rzKqOz/MaeqtTR7K2Ffz0Zf6 +%VPb5qIy/3+/9r/Ac+3xRxm6hya2/bdnnpTKGe7wWWGizzxtltKn3GV6eJiUP +%ZdQN6xfWFiQlD2V0KDJKu7RLSh7KOMki3mX6VCl5KOOOlgwDblcpeSjj9skD +%T65pkpCHMp5eobPVNF5CHsp4Yu2CublPJOQhX1+QqdGDUxLyUMZXt4/b9N0s +%IQ9l1MozzbKdLSEPZSzh+quVDpGQhzLe7TjSu8hAQh7y+Ri+knTjSMhDvl7t +%tEzfQjF5KKOSUso6vVgxeShjd+eFPrffislDGfd/abUyDhSThzL2i6vftOW0 +%mDxUcIy7hZ/PXjF5qGBj4rTxXmvF5KGCz6fbv7VcICYPFZxpO7DywHgxeahg +%c8VNHXcrMXmo4M//bjiKuonJQwVbu8WsHCkTk4cKtvCvDstWEZOHChbVP1Dq +%XSIiDxW0sbkzY9dPEXmooJnOw1sfgkXkoYJ8lWnd4i+JyEMF7bc0xETuE5GH +%Cm6/8PTjiRUi8lDB9NERyWrTROShgufn3JwxzEpEHipo+XLsXB1jEXmo4M0Y +%8UM/NRF5qOCms35XvlVqkYcKBjtZHz2YqkUeKqjT8Wzat29a5KGCNeu7jrwd +%rEUeKmg4++bkTj8t8lDBBKt7L7K8tchDBeFQ7dzRu7XIQxWnDgmMEa/WIg9V +%jPT2rbadp0UeqvjNs2O/yjgt8lDFDM/T3C6DtchDFc8Ihx/yMdMiD1XUv3Gj +%p6tEizxU0a7s/In3SlrkoYondYLHz6oRkocq2jxetnxsrpA8VNGZa831ShCS +%h/x1/dm3TL8LyUMVh/f7O0PtjZA8VNEqqN9ly/tC8lDFiVHCF8+uCclDFR3a +%ElN3nhaShyre6Ts02sNTSB6qaB2f6xmzQ0geqhhq+Mhr8ToheaiiZnjnDosl +%QvJQxcMfDacMnC0kD1W8cWe01dYJQvJQxc04e2PFCCF5yOc5v9zjzgAheaji +%f86LOk53E5KHKo4Xhbk80xeShxq+//7KkaclJA813DvtcffjKkLyUMPO1LGb +%xzQLyEMNh8ycsbd7pYA81FBzafC4ofkC8lDDZTqLTm9NE5CHGpZ83dUtM05A +%Hmo4OfI+uEQIyEMNt3vUDx0UKiAPNfRatq639JWAPNRw4+s6E9NHAvJQw1yl +%J5NnBwrIQw1/Lr3RHnhVQB5quDXe84b5eQF5qOH333bdQrwF5KGGK/8ILT08 +%BeShhhseZoU4ugrIQw1jq9+kLdsqIA81NNo91v3QegF5qOGLVhXJ9+UC8pDP +%79jYwB72AvJQw69P6sJuzRWQhxo+5R+fNma6gDzUcMfb/a4N4wXkoYZ/Ays6 +%I0cLyEMdDVTjo94ME5CHOoYtGZjyaaCAPNSxxwZeeVYv5qGOylayRV3MmYc6 +%Kk3bULDNiHmo49PDe3pkaTMPdczUNLBbo8U81LFzrOC8iibzUMcgt4jVL5WY +%hzqeqNRxcW/lk4c6JmXMVXes55OHOu6rThsxr5JPHur4M2BM45JiPnmo4xKx +%uoVrLp881DEw6q7347988lDHN00bDjQm88lDHR1XPmmx/c0nD3UM2TGr5f0P +%Pnmo41uLfPPR4XzyUMf3XScm/vjMJw91HL0xw3jzBz55qOOaWbUt3V/zyUMd +%v0wzby8P5pOHOvZ0con48pBPHvJ5Tc4/8zCITx4aOL0gyuz2LT55aKBHbdzG +%h3588tBAFeWOO999+OShgYPuDl1Xdp5PHhq4T999TI/TfPLQwAdrLSY7e/PJ +%QwMbi5ZqhnjxyUMDV6dzLc0P8MlDAys7V/c6u49PHhqobpPqJN7NPDSwn8Y5 +%U79tzEMDNV+knB+6mXloYEfTy81/1zMPDYztkht/ZjXz0MDD4Sufzl7OPDRQ +%X9u0xWQp89DANYMMZzcuZh4amOxU/Tp9PvPQwL4R9f4/ZzMPDez9dtLh6BnM +%QwOPTg6I+z2FeWigmv3EcQUTmIcGcvu7RKjaMA8NLDFvshw4hnlo4NZjQZdW +%jmQemphpvsY7YBjz0MQV6v0elg9mHppoNeveoLEDmYcmzk0/GHijL/PQxEXh +%uhyNXsxDfrzpAR57ujMPTUzqca9fXVfmoYmhb2o/7DZhHppoPO+JqaYh89DE +%6w45//nrMg9NvBf6MBNkzEMTF98Z3a9UxDw08THf8JafgHloom5tSuICLp9+ +%f8rPd6StzEideWhi3TqxZrEy89BED8f5Az508shDEwNd327wbeORhybqhF2f +%tq+ZRx7y9RmE9Hdu4JGHJg7Jq3uyrJZHHpo4zcZCd0kVjzw0ce/X+Sucynnk +%IZ+v8fwZziU88tDESouOcrdCHnlw0aBvbqRvHo88uHi83XTju2weeXBxwfTq +%1twMHnlwcbLm9q/SdB55cDHt5rlBU1N45MFFvp2T9+EkHnlw8dQmA6/oeB55 +%cDH8nN4unTgeeXAx0/Ne56oYHnlw8cql5SEfo3nkwcVjmya3GkfyyIOLXX0m +%9T3wnUceXGzWbU8s/cojDy6WdB8w3+Ezjzy4+GDw+te/P/LIg4vWx9OCZ33g +%kQcX3wgmmce/ZR5c1N8cKXR4zTy4GCBy8C5+wTy4uK1gw4V9z5gHF98GCLrq +%PWUeXOzhHVf48hHz4OKQ08eP2D1gHlzc3vKsWfUe8+Bi28bAvy/uMA8epsVX +%aq8NZB487Mrv9sbsFvPg4f2tKh8zbzAPHkbFb7UN9GMePMzQnvZjw1XmwUO9 +%BVuPjfJlHjx0nucUL7zMPHj4ouBeVP4F5sFD4cQTsz+dYx48PLX4euutM8yD +%hzu52hVHTjEP+XUc0lbfdoJ58NDnQr27kzfz4GGu/lWzeUeZBw9DHk57PP0w +%8+BhdPnpBZMPMQ8eFo7JmjDFk3nwcNecFS0zPJgHD7+nxzfNd2Me8jnv1lJe +%vo958HDgJf3D21yZBw9t1+isOrabefBQypEW+O9kHjycXTN04aftzIOHomsj +%qrK2Mg8+ui0xDldzYR7y+9Cd8V0HbmYefAy4UHnJfiPzkN8nNrhPOuHMPOT3 +%sbbi+Z/WMQ8+frC/kdO0hnnw8b86s19Wq5mH/L543/zPtpXMg4/L591seLmc +%efAx92fuxbZlzIOPYpHD3YmOzIOPEVM6Us4vYR58FF1WM8y3Zx58XNX+NNPa +%jnnw8XifAMGZRcyDjxMnqF8pWcA8+HhS2WP2lPnMQ35fvJJ69f5c5sHH4RyL +%3lpzmAcf0xd9qdlhyzz4eFWUbJY9k3nwceDjZp+ZM5gHH826aPf+NI158NGg +%aM9Iq6nMQ/658tYp+v5k5iFAs0dzw7tNYh4C1Lh3a5P/BOYhQLcOpzVm45mH +%ALd0fNALsGEeApxun7Cl91jmIcC7pTJRMDAPATrH2W3+bwzzEGBS6C7zqP+Y +%hwA3+ElLFo9iHgL8o5TBqRjBPOTfe0uyfQ9ZMw8BxvYP5pkMZx4C7GNjFfZ6 +%KPMQoP9Tw/L5Q5iH/Hvfw8fRDYOZhwBfF6X4+FoyDwEO2NeUPGYQ8xDgs9zZ +%WDCAeQhwtNeGfWf7Mw8BRs8O7j6mH/MQoM+kdQ3lfZiHAPe47hx4ozfzEKD9 +%1GPFc3sxD/n36h7h/XgWzEOAPz73bP3cg3kIMSanydWtO/MQ4sUTGVUjuzEP +%IX7RXb6t1Yx5CLFS1yL3Q1fmIUT3wDfOB0yZh/x30/4818ldmIcQh5760CYy +%YR5CPPSpMyPNiHkI0XLryJV3DZmHEBOnJq3bZcA8hPj2L+f1ZH3mIcSI+Dgv +%Iz3mIcQFrqHSah3mIUTrETFvw7WZhxB7Buzz8JcxDyHOzR9lsF/KPITYlqGe +%YCdhHkKc/t3+ywgx85DPozM92FDEPIR4xpQf2S5kHvLX7wXeyhEwDyEunryU +%E8VnHkI8V9hd/TmPeQgxuM/PD35c5iH/3edxcI23JvPQwhGnFlvs0WAe8t/5 +%9SZX1qkzDy2M9HB+YqfGPOS/s9+VBc1UZR5aaF615MJ4Feahhd57M9tHKTMP +%Law+btIyRIl5aKGL2oIRgzjMQwvPLVxr2a+TSx5aqFbb9rpPB5c8tDCxxapv +%33YueWhh6YjlZv3auOShhYVHuv4a2MolDy3c7jkwf0gLlzy08EzQMruRzVzy +%0EK35W6fbJq45KGFuW0DNKc3cslDC78Vx6QtbOCShxYKVk1atFL+uavw0MJ3 +%JywOba3jkocWFl+J6eJZyyUP+Xq9hxy8UMMlDy0cr+kfcqeaSx5a+OitrOhd +%FZc8tLDPz+9r4iq55CFC3cSka0UVXPIQYR/D1c5K8ucKDxHOv6xhZ1TOJQ8R +%ThumNmF4GZc8RPh+bMvcefLvDQoPEc749DhnawmXPESo4pq8/1wxlzxE2COm +%0fFFEZc8RLindO/VpELmIcIt3OdzWwuYhwgLB9w50bWAeYjQr8T29cR85iG/ +%3guD52zOYx4i/Har+bNPLvMQ4X2Tl9Zfc5iHCO0GaAytzGYeIqySFv0yzmYe +%Imzt+Nx3Wpb8e9M/DxF6fGtw35vJPER40v7gvocZzEOEO9y2xGX8ZR4ilIQc +%PSL9yzxEuOqS7bpJ6cxDhJrfbdfuT2MeIowcYnTlRSrzEOPie38sylNoPWpi +%HDql++yeKcxHjJMD9qx3+sNV/L4SiTFj9PrVfsnMS4zCLocbUpNovQZiHJQb +%9Uk/ifmJsf/GH6sXJdL6u4lxn3FXb58E5ilG28lvrFPjaR4DxGjS093TJJ75 +%yq/vcOVJp9+0X63FCHc44XfimLcYI3vN61keS/OyEaPHU9fbVrHMX4x/0qe4 +%7/tF85suRpedvZ98i2H7QYwOa5MniWNongvFeHez5lr7n2x/iFG6qSwj6AfN +%10mMu33E3vXRXPr/M2LcmfLOcXw0zdtZjKFFtyaej2L7R4xTQxKm5kXS/HeI +%Uav+cvbQSLafxDjs774DRyPIw0OMF6dKPNPD2f4So9L7oNGDwsnDW4xL736x +%PvydPE6LUfy08EP6N/K4IMa9nQNdhnxj+0+Mzlofu58II4/rYoz1v7Qr/yvb +%j2Lsod3zGH4lj7tiNL/qtfXqF7Y/xVgbURTR+Jk8notxTop9+bzP7P4hn2fR +%nsPPPpFHiBi9v8ycLfnE9q8Yx7ROt3YJJY9wMdpMKNz7+yPbz2J8bRmYN/gj +%ecSJ8Zn3tbGXQtj+FqOeS6R58wfySJfP67j7tSUf2H4Xo07WyxVf3pNHgRjD +%v3Gn9XrP9r8Y59343n7qHXlUiTH54CnlhresBzHyup8/svQtebSIceIky6jw +%N6wPCfImunEGv2F9SHB5w+kF116zPiT4yajIU/M160OCI3RTi7a/Yn1I8P6B +%rvNyXrI+JHhmXvDbWS9ZHxLcmnd7QOgL1ocEN2+z2j/gBetDgpUOlrIbz1kf +%EhRJTs8TP2d9SHD6l/SOA89YHxLM1vXIqwtmfUjQRFY+Ym0w60OCQ66V6aY/ +%ZX3Ir0fXPmX2U9aHBK8Orov+/oT1IcGVfNmm0U9YHxJU7Xzj+OIx60OC5Q+l +%yX0fsz4k6DRo95/AR+x+KsFeTU+bTR6xPiRombYs+vJD1ocEfU+UBkgfsj4k +%aBqy/NnJB6wPCe538+XzHrA+JDiH093/8H3Wh/z6ZitPUr3P+pA/r3dw8rzH +%+pBghVvIcqV7rA8Jrg3r2ONxl/UhwdATlQ2cu6wPCRZPNz1+IIj1IcHMEVwP +%5SDWhwTx5gMrrzusDwmm3+NlaNxhfUiwR9rXTO/brA8J7ozO6SW6zfqQ4MJ+ +%M36dD2R9SHCFZuZ4g0DWhwSl16q8bgSwPiTodUWrqEcA60OC9kX3vz68xfqQ +%yH9PRlUNucX6kOAMfdd+If6sDwk6SAP6TfQnjwIJTvjoZhZ7k/UhwbFGz/Xt +%brI+JBhXku6fd4P1Id9PE/c1b77B+pBgxrQ67bbrrA8pug2fa3f0OutDik/s +%dq7Quc76kOLMhcEzA/xYH1I8YCrbaenH+pCiclBYbug11ocUc0x9fs26xvqQ +%4rqI/g8yr7I+pNhRa6W+5SrrQ37+RWeclK6yPqTo02XbwPNXWB9SvGp+jtvj +%CutDig8Ww7nXvqwPKR4/kxs71Zf1IcXYniYLM3xYH1IcvXhb3VYf1of89cu9 +%MjR8WB9SBM/lln6XWR9StN07c7bVZdaHFIc2+a2JvMT6kGJJxeHJyy6xPqS4 +%I8o2o/Ei60OKl/PcY09fZH1IsV4y06zXRdaH/HifNJs/XWB9SDFyx9M3dhdY +%H/J5j7C2rTvP+pDiO9kjvdPnWR9SXPGr5mDv86wPKf52F+K3c6wPKQq2meg6 +%nWN9SHHMqoCX7WdZH1LMU3Oec+Us60OKTpsLXYafZX1IcXF08qLEM6wPKe50 +%SF+77QzrQ4oG5zdNlp5hfcjnYf94TvBp1ocU9z27mmZ7mvUhxe4xfl2rT7E+ +%pNimOePz2VOsD7m/Wr+8wadYH3JfTumwhJOsDykeGzR0z86TrA8pJrmHb9I/ +%yfqQ4vh5zRbvT7A+pFho88pu6QnWhxS//ne9WukE60OKfbgD228fZ31IMfPS +%lZCpx1kfUvQYc+ZXpTfrQ4YXu3wruODN+pDhbmn/8yO9WR8y/HZhQlvWMdaH +%DMdm7j5/5BjrQ4ZvRjvYDTjG+pDhzqcLkhOPsj5kqPLMInX/UdaHDCP9Hkzs +%eZT1IcOS1idhMUdYHzJU35aXt+sI60OGsn6qaHaE9SHDISk7rKIPsz5k2K1o +%ydYdh1kfMpxnOrzG9DDrQ4ZDBeP+RHmxPmRoXmG/facX60O+fjX1ueZerA8Z +%+k7Xt4s5xPqQYbD5r7+uh1gfMrwQvJHb6xDrQ4b97cVWiQdZHzIcPmvBE8+D +%rA8Z3r/829jyIOtDhjru+ksyPVkfMsxcN8zllCfrQz7Pvu8jRnuyPmR4t8fg +%JeUHWB/yeW1sWuB3gPUhw6kfg/rNOMD6kKGueVpnuwfrQ4bTnxkrPfFgfchw +%WlwNd5kH60OG1qXyu4wH60OGASPqm766sz5kuMP53eqd7qwPGbo+K2ro5c76 +%kGGb106zdDfWh3z+41f7nHZjfchQeewUu3FurA8Zbnru4dq4n/Uhwz/n3oc9 +%2M/6kOHxVe+PLNvP+pCh1bCJ3jr7WR8yTHjbMjl6H+tDhhmZCYke+1gfMvy1 +%wSV/2D7Wh3y/nMsYV76X9SHDo9OOBwfsZX3I3/986F27vawP+d/r6CyU7mV9 +%aGNE04ukSFcu/h+i+QLA +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113lczNsbB/BpmmavZokkW7cI19ZybZfOI5VoVymKJCJbiVC3ouxLJJWI +%uOLey00JP1uYuAiFlLQo2hftm/b6DXOe7z+9pu/M+Z7n8z5zzjM6a3yXrmMy +%GIxOJQbjx1+8Ul26/o4JaJf9oZG8qCxoP7EvejblzHx8HU3Kp3Tv3KfSLrs/ +%5kC9w/0EYvLPcO7Gt230/hUSCL43zWPaZOfO/riukUvnfW4OX9lG359MLp2e +%4fRVr03W4PDjP6nkOLep5s+GVvr5O8R/ZtyTlXdaZfz8RL/5/HvkN9fQC5rB +%rXS8B0R9W1JLxsJW2ST3k886Jz4i3df91u0UtNLxn5CuwPe3dXNbZFfkn85P +%TCdX6xcp551roc97SsaeXSA7sKZFZvTz+o+cu2fBNpjcQp//nGw94u32sqJZ +%9l/nRPkTXpBj6y1fGm5vpvPJIAe1JiYnMZtlS38+8BVJ3xWvPzOqic7vNXmX +%EvnwjU6TrCzox4BviOGo0dt9UhvpfLPIRH1zkfaCRtm2H9Pze0vYlebXKrIb +%6PzfkfS0E4bpqxtkCo1sovf0TMejlnpaTzYxStlZk763Xmb58wEfiEWWzrh2 +%UT2t7wMZ+76vYOaf32Q/nuZ+Moes+8e++ILBN1pvLulmDSpNe1Yn+1lOfi7J +%2q+kW7C0jtb/kahbvitKrqiVjf0xnTF55G7pwekXd9TSPPLIxOEub9JUamXr +%vX9cn8iI7SY3hWdqaD75ZJnBKub+iTWyH6NpJOeTzZuAO/FhNc2rgESvbe6u +%saqWff8xXGcBufAx51FuSRXNr5DYvXROH+ZXJTP5OWARmRzfp7qDWUXzLCLW +%He4uddGVsp/TO/CZBCzmBH3Sr6T5FpOh/7UEZD2skL3N+nEVE6dLHZ6DNhU0 +%7xIyI377/ZCyctmwnxP8QtiJLrGTAspp/l/I4/P321R45bKfw7l/JfY7Pe5r +%XyijHqVkzICdT4JBGa2nlFyws3TUfllKfUpJsdLhEqZbKa2vlPS/D9zlWPqV +%epURrdyQ2z6Cr7TeMtKt22sTP/ML9Ssjq13q/FevKaH1l5M7ET3VshPF1LOc +%kDuSwZK0zzSPcjLX0C00qK6I+lYQ3U3SeDfNIppPBdGPdB78xaKQeleQw8KO +%x3sCCmhelWTqNqdxm6/mU/9K4mhePz4z7xPNr5K4f+v9PMj+RNdDFel6wdRM +%nJ1H86wi1zh3H52OZoNifVSR624L1xgks0GRbzXZ8i79iyyDDYr1Uk36NijX +%TStjgyLvamL6yND/VC8bFOunhlxcJ/izUMoBRf41JInt/og3lQOK9VRDLryI +%mDDVggMKj1rSvuLwQkMPDig8asn1lPDrWrs5oPCoJbuW/6vaFskBhUct0Q4X +%cFKvcUDhUUfMohOuOT3jgMKjjqiP6tSsLeKAwqOOpAdUqy1r54DC4xvRyOmX +%3RZwIfKnxzfya5ZdbKcuFxQe38jbm0kfpfO4oPCoJ5n9jp1jnbig8Kgn+5M4 +%9fzNXFB41JNAPe7Bin1cUHg0kKIzyeHR8VxQeDSQB8Xx18ff5oLCo4Esn9v6 +%9NIbLig8GoljSnrGYBkXFB6NJOiwh/eMHi71aCRXhwseWol41KOJbMn0Zi3S +%51GPJpImWOM8zIRHPZqIzW1OZoYTj3o0k4TR0+4s38SjHs3kaKnV8PQwHvVo +%JiwDHw+lOB71aCFWMIM1NplH62khUS9reRrPedSnhVgbGJ//XMij9bUQVePI +%wh3NPOrVQiKs1OrqWHxabwt54B+UPmUkn/q1kFcjWx1Np/Np/a3EyPjRhwlm +%fOrZSlK5r5vyXfk0j1bCuFbW5rCFT31bSalhsvf5MD7Np5W0rPhNIymGT71b +%SXH1dM2Qa3yaVxvxbw3J037Mp/5thPVCemR3Np/m10ZeXnTNiKngg+L72Ubu +%elZxt37n0zzbSO7iPZUqPAFdH21EZ6bqTXttAc23nZjbs7udpgroemknc097 +%zFcnApp3O3kxm1+xw15A1087+T1jUVCkp4Dm305m/hK/aqm/gK6ndhJ0+qHa +%w3AB9eggy2sdZ72NElCPDtJQlKsTdllAPToIz1qq/y5VQD06CPu37G1P0gXU +%o4McKw2fZ/FeQD06SEZtZLRXiYB6dJCjG7duEdULqEcnCf3rz9k23QLq0UkK +%HErbR6gIqUcn+Tt+dukmsZB6dJKsC8b3zEYLqUcn6XltrHt5opB6dJI6rvX6 +%UCMh9fhOVmUM+uTNF1KP7yQyqOvBP4uE1OM7ebvdM6zNXkg9vhPd3P2bUpcL +%qcd38m6q/5QyTyH1+E469l2Yvd9HSD26SB37zalYPyH16CLrJ++ql+4SUo8u +%EqezIHgwWEg9ukiQC+uEY7iQenSRoqilT6SHhNSji+zZ4Fv7+zEh9egmH9Sm +%2r0/IaQe3US3503em1NC6tFNJp2McjOIFlKPbjLayrKOGSukHt3k5DdG3IIz +%QurRTbQttkqb5K8VHt0E3L5bCeKE1KOH7LnrnHNOfl/h0UP+47jMuxSLHj1E +%dbxgkXYMevSQvcabWBqn0aOHzDDfueB4JHr0kD7fuqDDEejRS3Z1nq/jH0WP +%XmJw+Z3WiIPo0UtsjmnF3AsT0v2yl7wemPBLQzB69JJTUu20tF3o0UsKTQaL +%J/mjRx/JC1N78Ptm9Ogjz6Z5CLrXoUcfMbw4RcvLAz36yGpXJdtwV/ToI9yC +%m87uDujRR9JWbwvrWIwe/eTS+3y2gyl69JMvo6bl75yLHv3Ex+PTk42G6NFP +%vubv/NNwMnr0E795re45OujRT9gr8sfba6FHPzENeTGYJEKPAfIhKHKoiYMe +%A2TNeda/2kMC6jFAjpjxcmZ+F1CPATL54f63CxoF1GOA/JUOdxdUCqjHAPFe +%/+z4/M8C6jFIApqjOUY5AuoxSMIuGSye8FpAPQZJV9XacaPk32+FxyDpfMxa +%NeyegHoMkqf3v/2nkYz71SCJPrabMe4q7ldDJDUmjGt4HverIeIoOKu+7DTu +%V0Pk1OQRJYeO4n41RPr3Maa+DsP9aojcC7YyHBWI+9UQmfXilvp+P9yvGDBB +%i1U7uB7rYUD7pkTdUx64fzHAMMi9ZI4L1seASZ4uen22uJ8x4KXppLAPFgL4 +%Wa43A/ROTD3z0gT3NwbUloqO58wU0P2BAQEaVqo903C/Y0BC2JK7s/QxDwbY +%JwiXxY7F/U8+fuHBa6ojMB8G9J4rq7oswv2QATc812gv5WFeSlAyh9czion7 +%oxLMspycxOrj0/yUABqjVTkdeH4pQaL5yJLxjXyapxI0vp/DW12N55kSJI3R +%s7n7lU/zVYIp1e6dEwvxfFMCW/u7pXdy+DRvJQh+ZfjOPQvPOyXYslY8oPOS +%T/NXgm0HC58y0vH8U4KeUCLpesCnHky4t/d4GP8OnodMmLUuzW9mMp96MCGx +%57ZO8D94PjLBOfn60+LLfOrBhJXR622WXsDzkgm/ivcwq8/wqQcTtG1SNkdE +%4fnJhNa7anE2EXzqwYSooazB8YfxPGXCwemZK4ftw/6ACQsjR87QCuVTDyY8 +%VdeuNw7EfoEJSapm1T47+NRDGeIfx7Ym+2L/oAy/eV2yUd2EHspwYkdeQag3 +%eiiDkW6OWGUNeihDjdGXlxdXoocyjH2X3WazHD2U4Y1txr9qzuihDJ8fP3lW +%YY8eyuA/+GHtG2v0UAb192eVn1uihzIE2M43yjZDD2X41M1PagD0YIF63PhC +%7fnowYJXGr7PV8xBDxa8WP5r/7+/oQcL7G61HxUaogcLtoYdbv9jGnqwYPtS +%k4yByejBgojzR8Mi9NGDBcPZA+ZT9NCDBQZeB1IKx6EHCz5/U74YOxo9WLCs +%rt3McyR6sKDu2j3vOZrowYKKPz6+HquBHirQo9PDlIrRQwUCnM9HaaihhwoE +%dwY80BGghwq8rPaYO4+LHiqwt1LNxksFPVQg+6lgZCwTPVRgR88vbz8O8aiH +%ChBPVcexA9i/qkBJlaZbQC+PesjvP9FOyuviUQ8V2FXtc9q0k0c9VODwHKWZ +%D9qw32XDgb73z+e2YH/NBqXogIEXjdj/sqE0EhLc63nUgw2XN6Sf7K/FfpgN +%1rKBwqvV2I+zIS2WMcG1Evtj+Xhz42KGl/OoBxt87w0zK/6K/TIbTB24XddL +%sH9nQ9jhb9Xhn3nUgw3m1o4Fa+T9tsKDDR990q4syedRDzZ0ufZdn5eH/T4H +%1mjV+M3M5VEPDri0n1SZ/YFHPTjwNeBxKrznUQ8OJIZYDjq8xd8HHAi0LXyz +%MZNHPTiw+9n220df86gHB1zvORWnZqAHByygmXx9gR4cyDArDBz2HD04kKSf +%luvwDD04cPNzqUtMOnrI75cHZ355gh5cuKW82Xv6Y/Tgwh33tcUH0tCDC5wl +%3ZnlD9CDC8Yfnn02v48eXJCcDhtMvoseXHC9IRga/T/04ELyQG9M1G0ePT+5 +%ULv5oonqLfTgQlTK/7wjbqIHF/bYjd8iTUEPLixq+scj4QZ6cCEwM9FrWhJ6 +%yOfj98Du+XX04EHXaaWkVdfQgwftgWNeD/6NHjzY57t7deJf6MGDzQWMYuur +%6MEDr4sZ4r5E9OCBVbzm8OTL6MGDML249+v+RA8ehLfpOOlcQg8eHKpLaitL +%QA8e6OU97//rAnrwYHLMQnO/8+jBAzuN2Ffz49GDD51Z043Uz6EHH4pWm9hV +%xqEHH2xcSooen0EPPviOq4qMj0UPPvz++Oyp4Bj04EOL+Pkjz2j04ANv7Mnz +%S06jBx/e39qvNysKPfhwNXFzoP4p9ODDwI2cbdqR6MGH7sTqPZKT6MGHhfXb +%U4Qn0IMPnBmH7vIj0EMAq41/1RMeRw95H+HsrSk6hh7yvm3oYq/mUfQQwOxr +%Goa6R9BDAAvVHhobHEYPAUStdHY0PYQeAvC3t/BadhA95H3Rp7YNWw6ghwDq +%0z1WHNqPHvK+4tIo3Sv70EMAWrmRr/8LRw/550e/elgVhh5CMPUKuCAIQw8h +%KJtHjTHaix5CmB5XHr5yD3oIoWMo6tvRUPSQ/66asD7vQQh6CKH1jmpCQzB6 +%yPve/KHfdYLRQwgjuLVrXf9ADyE070v5cCoIPYTAE1otfxuIHvLXHZJPgkD0 +%EELgUNYU693oIYSWRP+CE7vQQxWKwtR4H3eihyo8d24pH7kTPVTB0WNayNoA +%9FCFsoHWTyk70EMV3EadujOwHT1UITmj09lmO3qowqHbK9IT/NFDFe6daE5s +%24YeqpB9vcHccht6qAIj2/fART/0UAWf6kSVHl/0UIUd43LWO/mihxp4KU27 +%mroVPdSgpj/EUbQVPdRgha5+yLYt6KEGUxP4IR83o4caXPnlatjszeihBluP +%hYYnbEIPNfBxel7F3oQearDD8V2o30b0UAP/htBzn33QQw02xD6KsPRBD/l8 +%0pd8u7sBPdRgVohV44QN6KEGUZ1/x8StRw91UJ+496lgPXqoQ16/m/9eb/RQ +%h1yp6Oz3deihDtlHvlpuXYce6mDIH7GxZi16qIPrScsBz7XooQ6/apV1lHih +%hzqUelUGunmhhzpIh91eWLQGPdRBP6NUe8Ua9JC/P+1V2WdP9FCH56OFD1d5 +%oocIGkbYnilfjfWIwFaTKd6wGn1E4P6x41GTB9YngtEtYck7PdBLBKmGUZVD +%q7BeETzYdsLh6Cr0E4Gnw+bKYauwfvn9xBdNl1eipwgkqcojDVZiHiIw0ro1 +%IJPv44rfMyKwV7pB7N0xHxFsX5yQXOaG3iLomLbXdIcb5iWCvz6Gf+W4Yf8g +%Am1vm8rzKzA/EUQZ5hwwXIHrQQRZDDXH18sxTxEEly177rkc14cI9qyWru11 +%xXxFkGtmuyDaFdeLCFaGul+f7op5i2B+t8+iTBdcPyJghbWKNrhg/iLYrR+s +%z3bB9SSCfQdYrleXoYcY1vXPyzFbhh5iOJq+p6bKGT3EUN68J+uQM3qIQbfY +%Y/RkZ/QQg1bqf5ffOqGHGD7pbyzb5oQeYuAtyPDVdEIPMawqGHPrsSN6iGFj +%+pHCtY7oIYZXupeyhI74fZO/n6/E/99S9BCD2vclh1ctRQ8xGD0ouMVdih5i +%MEzJHrjtgB5iEPeWtXs4oIe83mEuSUIH9BBDQo/6uIf26CGff2ZjyAZ79BDD +%jTDueE179BDD6+E1rS/t0EMMA8t6L++yQw8xtB62ZE6yQw/5eAHLm4ps0UMM +%Q580QyJs0UMMe6b9dgxs0UMCV3b01HXYoIcEbK80ZF+zQQ8JtAUVqnjYoIcE +%fHXemAyzQQ8JnHKZfSvTGj3k9x9ywvZZo4cE1v7x7tbv1ughgUxVu/Z2K/SQ +%wNQPX1JuWKGHBIxkxhnrrdBDAmXujdN/sUIPCSwxWGBesgQ9JHDd5eTss0vQ +%QwLvh5+86rwEPSTAMTh/TLIEPSTwarePT/Zi9JDAv4bxlScWo4cEsk5PLbBZ +%jB4SsOHMSRIuRg8JpD4dpvfWEj0k8LHZ+EiEJXpIYNSdDeNsLdFDAj47Hqap +%W6KHBLT0nmrnLEIPCVSY6dZHL0IPKVwNFX5zWYQeUri7eIqR9iL0kAJX/1dG +%qQV6SOHI5CneVyzQQwqhST1nfSzQQz7eVqn1dAv0kAJ71ZOTneboIYXHN71M +%HpmjhxQcnGM37DNHDymEWNx3tDJHDynMGLHAV2qOHlK4b7x8eLEZekjB7eMx +%vatm6CEFE+ulwVvN0EMKKddI0Gwz9JCC8dncCqYZekjB1W+4yduF6CGFywfW +%jDq7ED2kwGl69mjtQvSQwuDe2rIZC9FDCtWnvEIHTNFD/vwt7/a+MUUPKVjH +%6XLiTNFDCn9kjvmwzhQ9pFD4yiXfyBQ9NGCbumUmU/76/4f9glI= +% "]]}}, +% AspectRatio->NCache[GoldenRatio^(-1), 0.6180339887498948], +% Axes->None, +% AxesOrigin->{0, 0}, +% Epilog->{ +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.02\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.02"]], TraditionalForm]], {0.8, 4}, BaseStyle \ +%-> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.05\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.05"]], TraditionalForm]], {1.67, 6.4}, \ +%BaseStyle -> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.07\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.07"]], TraditionalForm]], {2.85, 12}, \ +%BaseStyle -> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.1\"", TraditionalForm]], "Text", "TR"], \ +% +% Text["|q| = 0.1"]], TraditionalForm]], {3.2, 7}, BaseStyle \ +%-> 14], Null}, +% Frame->True, +% FrameLabel->{ +% FormBox["\"Impact parameter, b\"", TraditionalForm], +% FormBox["\"Spatial rotation, \[Chi]\"", TraditionalForm]}, +% FrameStyle->{{14, +% GrayLevel[1]}, {14, +% GrayLevel[1]}}, +% FrameTicks->{Automatic, {{0, +% FormBox["0", TraditionalForm]}, { +% NCache[Pi, 3.141592653589793], +% FormBox["\[Pi]", TraditionalForm]}, { +% NCache[2 Pi, 6.283185307179586], +% FormBox[ +% RowBox[{"2", " ", "\[Pi]"}], TraditionalForm]}, { +% NCache[3 Pi, 9.42477796076938], +% FormBox[ +% RowBox[{"3", " ", "\[Pi]"}], TraditionalForm]}, { +% NCache[4 Pi, 12.566370614359172`], +% FormBox[ +% RowBox[{"4", " ", "\[Pi]"}], TraditionalForm]}}}, +% FrameTicksStyle->{16, 16}, +% ImageSize->600, +% PlotRange->{All, All}, +% PlotRangeClipping->True, +% PlotRangePadding->{Automatic, Automatic}, +% TicksStyle->16]], "Output", +% CellChangeTimes->{3.556953242036603*^9, {3.556953596625984*^9, \ +%3.556953702375863*^9}, {3.556953796337514*^9, 3.556953956593231*^9}, \ +%{3.556954020687426*^9, 3.556954030706046*^9}, { +% 3.558693364370013*^9, 3.5586933829491863`*^9}, \ +%{3.55869345851305*^9, 3.5586934742140837`*^9}}] +%%EndMathematicaCell +p +np 33 1 m +33 273 L +469 273 L +469 1 L +cp +clip np +p +np 35 3 m +35 271 L +467 271 L +467 3 L +cp +clip np +3.239 setmiterlimit +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +0 g +0.36 w +[ ] 0 setdash +3.25 setmiterlimit +78.19 226.892 m +78.919 226.724 L +79.648 226.555 L +80.376 226.386 L +81.105 226.217 L +81.834 226.048 L +82.562 225.878 L +83.291 225.707 L +84.02 225.537 L +84.749 225.366 L +85.477 225.194 L +86.206 225.022 L +86.935 224.85 L +87.663 224.678 L +88.392 224.505 L +89.121 224.331 L +89.849 224.158 L +90.578 223.984 L +91.307 223.809 L +92.035 223.635 L +92.764 223.459 L +93.493 223.284 L +94.221 223.108 L +94.95 222.932 L +95.679 222.755 L +96.407 222.578 L +97.136 222.4 L +97.865 222.222 L +98.593 222.044 L +99.322 221.865 L +100.051 221.686 L +100.779 221.507 L +101.508 221.327 L +102.237 221.147 L +102.965 220.966 L +103.694 220.785 L +104.423 220.604 L +105.152 220.422 L +105.88 220.24 L +106.609 220.057 L +107.338 219.874 L +108.066 219.691 L +108.795 219.507 L +109.524 219.323 L +110.252 219.138 L +110.981 218.953 L +111.71 218.767 L +112.438 218.581 L +113.167 218.395 L +113.896 218.208 L +114.624 218.021 L +115.353 217.833 L +116.082 217.645 L +116.81 217.457 L +117.539 217.268 L +118.268 217.078 L +118.996 216.889 L +119.725 216.698 L +120.454 216.508 L +121.182 216.316 L +121.911 216.125 L +122.64 215.933 L +123.368 215.74 L +124.097 215.547 L +124.826 215.354 L +125.554 215.16 L +126.283 214.965 L +127.012 214.77 L +127.741 214.575 L +128.469 214.379 L +129.198 214.183 L +129.927 213.986 L +130.655 213.789 L +131.384 213.591 L +132.113 213.392 L +132.841 213.194 L +133.57 212.994 L +134.299 212.794 L +135.027 212.594 L +135.756 212.393 L +136.485 212.192 L +137.213 211.99 L +137.942 211.787 L +138.671 211.584 L +139.399 211.38 L +140.128 211.176 L +140.857 210.972 L +141.585 210.766 L +142.314 210.56 L +143.043 210.354 L +143.771 210.147 L +144.5 209.939 L +145.229 209.731 L +145.957 209.522 L +146.686 209.313 L +147.415 209.103 L +148.144 208.893 L +148.872 208.681 L +149.601 208.47 L +150.33 208.257 L +151.058 208.044 L +151.787 207.83 L +152.516 207.616 L +153.244 207.401 L +153.973 207.185 L +154.702 206.969 L +155.43 206.752 L +156.159 206.534 L +156.888 206.316 L +157.616 206.097 L +158.345 205.877 L +159.074 205.656 L +159.802 205.435 L +160.531 205.213 L +161.26 204.99 L +161.988 204.767 L +162.717 204.542 L +163.446 204.317 L +164.174 204.092 L +164.903 203.865 L +165.632 203.638 L +166.36 203.409 L +167.089 203.181 L +167.818 202.951 L +168.547 202.72 L +169.275 202.489 L +170.004 202.256 L +170.733 202.023 L +171.461 201.789 L +172.19 201.554 L +172.919 201.318 L +173.647 201.081 L +174.376 200.844 L +175.105 200.605 L +175.833 200.365 L +176.562 200.125 L +177.291 199.883 L +178.019 199.641 L +178.748 199.397 L +179.477 199.153 L +180.205 198.907 L +180.934 198.661 L +181.663 198.413 L +182.391 198.164 L +183.12 197.914 L +183.849 197.664 L +184.577 197.412 L +185.306 197.158 L +186.035 196.904 L +186.763 196.649 L +187.492 196.392 L +188.221 196.134 L +188.95 195.875 L +189.678 195.615 L +190.407 195.353 L +191.136 195.091 L +191.864 194.826 L +192.593 194.561 L +193.322 194.294 L +194.05 194.026 L +194.779 193.757 L +195.508 193.486 L +196.236 193.214 L +196.965 192.94 L +197.694 192.665 L +198.422 192.388 L +199.151 192.11 L +199.88 191.83 L +200.608 191.549 L +201.337 191.266 L +202.066 190.981 L +202.794 190.695 L +203.523 190.408 L +204.252 190.118 L +204.98 189.827 L +205.709 189.534 L +206.438 189.239 L +207.166 188.942 L +207.895 188.644 L +208.624 188.343 L +209.353 188.041 L +210.081 187.737 L +210.81 187.43 L +211.539 187.122 L +212.267 186.811 L +212.996 186.499 L +213.725 186.184 L +214.453 185.867 L +215.182 185.548 L +215.911 185.226 L +216.639 184.903 L +217.368 184.576 L +218.097 184.248 L +218.825 183.917 L +219.554 183.583 L +220.283 183.247 L +221.011 182.908 L +221.74 182.566 L +222.469 182.221 L +223.197 181.874 L +223.926 181.524 L +224.655 181.171 L +225.383 180.815 L +226.112 180.455 L +226.841 180.093 L +227.569 179.727 L +228.298 179.358 L +229.027 178.986 L +229.756 178.61 L +230.484 178.23 L +231.213 177.847 L +231.942 177.46 L +232.67 177.069 L +233.399 176.674 L +234.128 176.276 L +234.856 175.872 L +235.585 175.465 L +236.314 175.053 L +237.042 174.637 L +237.771 174.216 L +238.5 173.79 L +239.228 173.359 L +239.957 172.924 L +240.686 172.482 L +241.414 172.036 L +242.143 171.584 L +242.872 171.126 L +243.6 170.662 L +244.329 170.192 L +245.058 169.716 L +245.786 169.233 L +246.515 168.744 L +247.244 168.247 L +247.972 167.743 L +248.701 167.232 L +249.43 166.713 L +250.159 166.186 L +250.887 165.65 L +251.616 165.106 L +252.345 164.553 L +253.073 163.99 L +253.802 163.417 L +254.531 162.834 L +255.259 162.241 L +255.988 161.636 L +256.717 161.02 L +257.445 160.391 L +258.174 159.75 L +258.903 159.095 L +259.631 158.426 L +260.36 157.743 L +261.089 157.044 L +261.817 156.328 L +262.546 155.595 L +263.275 154.844 L +264.003 154.074 L +264.732 153.283 L +265.461 152.471 L +266.189 151.635 L +266.918 150.774 L +267.647 149.887 L +268.375 148.972 L +269.104 148.026 L +269.833 147.047 L +270.561 146.033 L +271.29 144.98 L +272.019 143.886 L +272.748 142.746 L +273.476 141.556 L +274.205 140.31 L +274.934 139.004 L +275.662 137.63 L +276.391 136.179 L +277.12 134.642 L +277.848 133.005 L +278.577 131.258 L +279.306 129.378 L +280.034 127.344 L +280.763 125.125 L +281.492 122.683 L +282.22 119.959 L +282.949 116.88 L +283.678 113.326 L +284.406 109.112 L +285.135 103.916 L +285.864 97.083 L +286.592 86.986 L +287.321 66.511 L +288.05 107.215 L +288.778 120.201 L +289.507 125.455 L +290.236 128.764 L +290.964 131.167 L +291.693 133.043 L +292.422 134.577 L +293.151 135.87 L +293.879 136.983 L +294.608 137.958 L +295.337 138.825 L +296.065 139.602 L +296.794 140.306 L +297.523 140.948 L +298.251 141.537 L +298.98 142.081 L +299.709 142.585 L +300.437 143.054 L +301.166 143.493 L +301.895 143.904 L +302.623 144.291 L +303.352 144.655 L +304.081 145 L +304.809 145.327 L +305.538 145.636 L +306.267 145.931 L +306.995 146.212 L +307.724 146.479 L +308.453 146.735 L +309.181 146.98 L +309.91 147.214 L +310.639 147.439 L +311.367 147.655 L +312.096 147.863 L +312.825 148.062 L +313.554 148.255 L +314.282 148.44 L +315.011 148.619 L +315.74 148.792 L +316.468 148.959 L +317.197 149.12 L +317.926 149.276 L +318.654 149.427 L +319.383 149.574 L +320.112 149.716 L +320.84 149.853 L +321.569 149.987 L +322.298 150.117 L +323.026 150.243 L +323.755 150.366 L +324.484 150.485 L +325.212 150.601 L +325.941 150.713 L +326.67 150.823 L +327.398 150.93 L +328.127 151.035 L +328.856 151.136 L +329.584 151.235 L +330.313 151.332 L +331.042 151.426 L +331.77 151.518 L +332.499 151.608 L +333.228 151.696 L +333.957 151.782 L +334.685 151.866 L +335.414 151.948 L +336.143 152.028 L +336.871 152.106 L +337.6 152.183 L +338.329 152.258 L +339.057 152.331 L +339.786 152.403 L +340.515 152.474 L +341.243 152.542 L +341.972 152.61 L +342.701 152.676 L +343.429 152.741 L +344.158 152.805 L +344.887 152.867 L +345.615 152.928 L +346.344 152.988 L +347.073 153.047 L +347.801 153.105 L +348.53 153.161 L +349.259 153.217 L +349.987 153.271 L +350.716 153.325 L +351.445 153.377 L +352.173 153.429 L +352.902 153.48 L +353.631 153.53 L +354.36 153.579 L +355.088 153.627 L +355.817 153.674 L +356.546 153.721 L +357.274 153.766 L +358.003 153.811 L +358.732 153.855 L +359.46 153.899 L +360.189 153.942 L +360.918 153.984 L +361.646 154.025 L +362.375 154.066 L +363.104 154.106 L +363.832 154.145 L +364.561 154.184 L +365.29 154.222 L +366.018 154.26 L +366.747 154.297 L +367.476 154.333 L +368.204 154.369 L +368.933 154.405 L +369.662 154.44 L +370.39 154.474 L +371.119 154.508 L +371.848 154.541 L +372.576 154.574 L +373.305 154.606 L +374.034 154.638 L +374.763 154.669 L +375.491 154.7 L +376.22 154.731 L +376.949 154.761 L +377.677 154.791 L +378.406 154.82 L +379.135 154.849 L +379.863 154.877 L +380.592 154.905 L +381.321 154.933 L +382.049 154.96 L +382.778 154.987 L +383.507 155.014 L +384.235 155.04 L +384.964 155.066 L +385.693 155.092 L +386.421 155.117 L +387.15 155.142 L +387.879 155.166 L +388.607 155.19 L +389.336 155.214 L +390.065 155.238 L +390.793 155.261 L +391.522 155.284 L +392.251 155.307 L +392.979 155.33 L +393.708 155.352 L +394.437 155.374 L +395.166 155.395 L +395.894 155.417 L +396.623 155.438 L +397.352 155.458 L +398.08 155.479 L +398.809 155.499 L +399.538 155.519 L +400.266 155.539 L +400.995 155.559 L +401.724 155.578 L +402.452 155.597 L +403.181 155.616 L +403.91 155.635 L +404.638 155.653 L +405.367 155.672 L +406.096 155.69 L +406.824 155.708 L +407.553 155.725 L +408.282 155.743 L +409.01 155.76 L +409.739 155.777 L +410.468 155.794 L +411.196 155.811 L +411.925 155.827 L +412.654 155.843 L +413.382 155.859 L +414.111 155.875 L +414.84 155.891 L +415.568 155.907 L +416.297 155.922 L +417.026 155.937 L +417.755 155.952 L +418.483 155.967 L +419.212 155.982 L +419.941 155.997 L +420.669 156.011 L +421.398 156.025 L +422.127 156.04 L +422.855 156.054 L +423.584 156.067 L +424.313 156.081 L +425.041 156.095 L +425.77 156.108 L +426.499 156.121 L +427.227 156.135 L +427.956 156.148 L +428.685 156.16 L +429.413 156.173 L +430.142 156.186 L +430.871 156.198 L +431.599 156.211 L +432.328 156.223 L +433.057 156.235 L +433.785 156.247 L +434.514 156.259 L +435.243 156.271 L +435.971 156.282 L +436.7 156.294 L +437.429 156.305 L +438.158 156.316 L +438.886 156.328 L +439.615 156.339 L +440.344 156.35 L +441.072 156.36 L +441.801 156.371 L +442.53 156.382 L +s +78.19 227.244 m +78.919 227.062 L +79.648 226.879 L +80.376 226.696 L +81.105 226.513 L +81.834 226.329 L +82.562 226.144 L +83.291 225.96 L +84.02 225.774 L +84.749 225.589 L +85.477 225.403 L +86.206 225.216 L +86.935 225.029 L +87.663 224.842 L +88.392 224.654 L +89.121 224.465 L +89.849 224.277 L +90.578 224.088 L +91.307 223.898 L +92.035 223.708 L +92.764 223.518 L +93.493 223.327 L +94.221 223.135 L +94.95 222.944 L +95.679 222.751 L +96.407 222.559 L +97.136 222.366 L +97.865 222.172 L +98.593 221.978 L +99.322 221.783 L +100.051 221.589 L +100.779 221.393 L +101.508 221.197 L +102.237 221.001 L +102.965 220.804 L +103.694 220.607 L +104.423 220.409 L +105.152 220.211 L +105.88 220.013 L +106.609 219.813 L +107.338 219.614 L +108.066 219.414 L +108.795 219.213 L +109.524 219.012 L +110.252 218.811 L +110.981 218.609 L +111.71 218.406 L +112.438 218.203 L +113.167 217.999 L +113.896 217.795 L +114.624 217.591 L +115.353 217.386 L +116.082 217.18 L +116.81 216.974 L +117.539 216.767 L +118.268 216.56 L +118.996 216.352 L +119.725 216.144 L +120.454 215.935 L +121.182 215.726 L +121.911 215.516 L +122.64 215.305 L +123.368 215.094 L +124.097 214.883 L +124.826 214.67 L +125.554 214.458 L +126.283 214.244 L +127.012 214.03 L +127.741 213.816 L +128.469 213.6 L +129.198 213.385 L +129.927 213.168 L +130.655 212.951 L +131.384 212.733 L +132.113 212.515 L +132.841 212.296 L +133.57 212.076 L +134.299 211.856 L +135.027 211.635 L +135.756 211.413 L +136.485 211.191 L +137.213 210.968 L +137.942 210.744 L +138.671 210.52 L +139.399 210.294 L +140.128 210.068 L +140.857 209.842 L +141.585 209.614 L +142.314 209.386 L +143.043 209.157 L +143.771 208.927 L +144.5 208.697 L +145.229 208.466 L +145.957 208.234 L +146.686 208.001 L +147.415 207.767 L +148.144 207.532 L +148.872 207.297 L +149.601 207.06 L +150.33 206.823 L +151.058 206.585 L +151.787 206.346 L +152.516 206.106 L +153.244 205.865 L +153.973 205.623 L +154.702 205.381 L +155.43 205.137 L +156.159 204.892 L +156.888 204.646 L +157.616 204.4 L +158.345 204.152 L +159.074 203.903 L +159.802 203.653 L +160.531 203.402 L +161.26 203.15 L +161.988 202.897 L +162.717 202.643 L +163.446 202.387 L +164.174 202.131 L +164.903 201.873 L +165.632 201.614 L +166.36 201.354 L +167.089 201.092 L +167.818 200.83 L +168.547 200.565 L +169.275 200.3 L +170.004 200.033 L +170.733 199.765 L +171.461 199.496 L +172.19 199.225 L +172.919 198.953 L +173.647 198.68 L +174.376 198.405 L +175.105 198.128 L +175.833 197.85 L +176.562 197.57 L +177.291 197.289 L +178.019 197.006 L +178.748 196.722 L +179.477 196.435 L +180.205 196.148 L +180.934 195.858 L +181.663 195.567 L +182.391 195.274 L +183.12 194.979 L +183.849 194.682 L +184.577 194.383 L +185.306 194.083 L +186.035 193.78 L +186.763 193.475 L +187.492 193.169 L +188.221 192.86 L +188.95 192.549 L +189.678 192.236 L +190.407 191.921 L +191.136 191.603 L +191.864 191.284 L +192.593 190.961 L +193.322 190.637 L +194.05 190.31 L +194.779 189.98 L +195.508 189.648 L +196.236 189.313 L +196.965 188.976 L +197.694 188.635 L +198.422 188.292 L +199.151 187.946 L +199.88 187.598 L +200.608 187.246 L +201.337 186.891 L +202.066 186.533 L +202.794 186.171 L +203.523 185.807 L +204.252 185.438 L +204.98 185.067 L +205.709 184.692 L +206.438 184.313 L +207.166 183.93 L +207.895 183.544 L +208.624 183.153 L +209.353 182.759 L +210.081 182.36 L +210.81 181.957 L +211.539 181.549 L +212.267 181.137 L +212.996 180.72 L +213.725 180.299 L +214.453 179.872 L +215.182 179.44 L +215.911 179.003 L +216.639 178.56 L +217.368 178.112 L +218.097 177.657 L +218.825 177.197 L +219.554 176.73 L +220.283 176.257 L +221.011 175.777 L +221.74 175.29 L +222.469 174.796 L +223.197 174.295 L +223.926 173.785 L +224.655 173.268 L +225.383 172.742 L +226.112 172.207 L +226.841 171.662 L +227.569 171.109 L +228.298 170.546 L +229.027 169.971 L +229.756 169.387 L +230.484 168.79 L +231.213 168.182 L +231.942 167.561 L +232.67 166.927 L +233.399 166.28 L +234.128 165.617 L +234.856 164.94 L +235.585 164.246 L +236.314 163.535 L +237.042 162.806 L +237.771 162.058 L +238.5 161.289 L +239.228 160.499 L +239.957 159.686 L +240.686 158.848 L +241.414 157.985 L +242.143 157.093 L +242.872 156.17 L +243.6 155.216 L +244.329 154.226 L +245.058 153.198 L +245.786 152.13 L +246.515 151.016 L +247.244 149.854 L +247.972 148.638 L +248.701 95.265 L +249.43 93.925 L +250.159 92.513 L +250.887 91.021 L +251.616 89.437 L +252.345 87.751 L +253.073 85.948 L +253.802 84.008 L +254.531 81.914 L +255.259 79.635 L +255.988 77.137 L +256.717 74.37 L +257.445 71.271 L +258.174 67.749 L +258.903 63.665 L +259.631 58.799 L +260.36 52.774 L +261.089 44.836 L +261.817 33.16 L +262.546 10.25 L +263.275 102.86 L +264.003 118.171 L +264.732 124.159 L +265.461 127.877 L +266.189 130.553 L +266.918 132.626 L +267.647 134.311 L +268.375 135.722 L +269.104 136.932 L +269.833 137.986 L +270.561 138.919 L +271.29 139.753 L +272.019 140.505 L +272.748 141.189 L +273.476 141.814 L +274.205 142.389 L +274.934 142.921 L +275.662 143.415 L +276.391 143.875 L +277.12 144.305 L +277.848 144.709 L +278.577 145.088 L +279.306 145.446 L +280.034 145.784 L +280.763 146.105 L +281.492 146.409 L +282.22 146.698 L +282.949 146.973 L +283.678 147.236 L +284.406 147.486 L +285.135 147.726 L +285.864 147.955 L +286.592 148.175 L +287.321 148.386 L +288.05 148.589 L +288.778 148.784 L +289.507 148.972 L +290.236 149.152 L +290.964 149.327 L +291.693 149.495 L +292.422 149.657 L +293.151 149.814 L +293.879 149.965 L +294.608 150.112 L +295.337 150.254 L +296.065 150.392 L +296.794 150.525 L +297.523 150.654 L +298.251 150.78 L +298.98 150.901 L +299.709 151.02 L +300.437 151.135 L +301.166 151.246 L +301.895 151.355 L +302.623 151.46 L +303.352 151.563 L +304.081 151.663 L +304.809 151.761 L +305.538 151.856 L +306.267 151.949 L +306.995 152.039 L +307.724 152.127 L +308.453 152.213 L +309.181 152.297 L +309.91 152.379 L +310.639 152.459 L +311.367 152.537 L +312.096 152.613 L +312.825 152.688 L +313.554 152.761 L +314.282 152.832 L +315.011 152.902 L +315.74 152.97 L +316.468 153.037 L +317.197 153.102 L +317.926 153.166 L +318.654 153.228 L +319.383 153.29 L +320.112 153.35 L +320.84 153.409 L +321.569 153.466 L +322.298 153.523 L +323.026 153.578 L +323.755 153.633 L +324.484 153.686 L +325.212 153.738 L +325.941 153.789 L +326.67 153.84 L +327.398 153.889 L +328.127 153.937 L +328.856 153.985 L +329.584 154.032 L +330.313 154.078 L +331.042 154.123 L +331.77 154.167 L +332.499 154.21 L +333.228 154.253 L +333.957 154.295 L +334.685 154.336 L +335.414 154.376 L +336.143 154.416 L +336.871 154.455 L +337.6 154.494 L +338.329 154.532 L +339.057 154.569 L +339.786 154.606 L +340.515 154.642 L +341.243 154.677 L +341.972 154.712 L +342.701 154.746 L +343.429 154.78 L +344.158 154.813 L +344.887 154.846 L +345.615 154.878 L +346.344 154.91 L +347.073 154.941 L +347.801 154.972 L +348.53 155.002 L +349.259 155.032 L +349.987 155.061 L +350.716 155.09 L +351.445 155.118 L +352.173 155.146 L +352.902 155.174 L +353.631 155.201 L +354.36 155.228 L +355.088 155.255 L +355.817 155.281 L +356.546 155.306 L +357.274 155.332 L +358.003 155.357 L +358.732 155.381 L +359.46 155.406 L +360.189 155.43 L +360.918 155.453 L +361.646 155.476 L +362.375 155.499 L +363.104 155.522 L +363.832 155.544 L +364.561 155.567 L +365.29 155.588 L +366.018 155.61 L +366.747 155.631 L +367.476 155.652 L +368.204 155.672 L +368.933 155.693 L +369.662 155.713 L +370.39 155.733 L +371.119 155.752 L +371.848 155.772 L +372.576 155.791 L +373.305 155.81 L +374.034 155.828 L +374.763 155.847 L +375.491 155.865 L +376.22 155.883 L +376.949 155.9 L +377.677 155.918 L +378.406 155.935 L +379.135 155.952 L +379.863 155.969 L +380.592 155.986 L +381.321 156.002 L +382.049 156.018 L +382.778 156.034 L +383.507 156.05 L +384.235 156.066 L +384.964 156.081 L +385.693 156.097 L +386.421 156.112 L +387.15 156.127 L +387.879 156.141 L +388.607 156.156 L +389.336 156.171 L +390.065 156.185 L +390.793 156.199 L +391.522 156.213 L +392.251 156.227 L +392.979 156.24 L +393.708 156.254 L +394.437 156.267 L +395.166 156.28 L +395.894 156.293 L +396.623 156.306 L +397.352 156.319 L +398.08 156.331 L +398.809 156.344 L +399.538 156.356 L +400.266 156.368 L +400.995 156.38 L +401.724 156.392 L +402.452 156.404 L +403.181 156.416 L +403.91 156.427 L +404.638 156.439 L +405.367 156.45 L +406.096 156.461 L +406.824 156.472 L +407.553 156.483 L +408.282 156.494 L +409.01 156.505 L +409.739 156.515 L +410.468 156.526 L +411.196 156.536 L +411.925 156.546 L +412.654 156.557 L +413.382 156.567 L +414.111 156.577 L +414.84 156.587 L +415.568 156.596 L +416.297 156.606 L +417.026 156.616 L +417.755 156.625 L +418.483 156.634 L +419.212 156.644 L +419.941 156.653 L +420.669 156.662 L +421.398 156.671 L +422.127 156.68 L +422.855 156.689 L +423.584 156.697 L +424.313 156.706 L +425.041 156.715 L +425.77 156.723 L +426.499 156.732 L +427.227 156.74 L +427.956 156.748 L +428.685 156.756 L +429.413 156.764 L +430.142 156.772 L +430.871 156.78 L +431.599 156.788 L +432.328 156.796 L +433.057 156.804 L +433.785 156.811 L +434.514 156.819 L +435.243 156.826 L +435.971 156.834 L +436.7 156.841 L +437.429 156.849 L +438.158 156.856 L +438.886 156.863 L +439.615 156.87 L +440.344 156.877 L +441.072 156.884 L +441.801 156.891 L +442.53 156.898 L +s +78.19 227.796 m +78.555 227.699 L +78.919 227.602 L +79.283 227.504 L +79.648 227.407 L +80.012 227.309 L +80.376 227.211 L +80.741 227.113 L +81.105 227.016 L +81.469 226.917 L +81.834 226.819 L +82.198 226.721 L +82.562 226.622 L +82.927 226.524 L +83.291 226.425 L +83.655 226.327 L +84.02 226.228 L +84.384 226.129 L +84.749 226.03 L +85.113 225.93 L +85.477 225.831 L +85.842 225.732 L +86.206 225.632 L +86.57 225.532 L +86.935 225.433 L +87.299 225.333 L +87.663 225.233 L +88.028 225.133 L +88.392 225.032 L +88.756 224.932 L +89.121 224.832 L +89.485 224.731 L +89.849 224.63 L +90.214 224.53 L +90.578 224.429 L +90.942 224.328 L +91.307 224.227 L +91.671 224.125 L +92.035 224.024 L +92.4 223.923 L +92.764 223.821 L +93.128 223.719 L +93.493 223.618 L +93.857 223.516 L +94.221 223.414 L +94.586 223.311 L +94.95 223.209 L +95.314 223.107 L +95.679 223.004 L +96.043 222.902 L +96.407 222.799 L +96.772 222.696 L +97.136 222.593 L +97.5 222.49 L +97.865 222.387 L +98.229 222.284 L +98.593 222.18 L +98.958 222.077 L +99.322 221.973 L +99.686 221.869 L +100.051 221.765 L +100.415 221.661 L +100.779 221.557 L +101.144 221.453 L +101.508 221.349 L +101.872 221.244 L +102.237 221.14 L +102.601 221.035 L +102.965 220.93 L +103.33 220.825 L +103.694 220.72 L +104.058 220.615 L +104.423 220.51 L +104.787 220.404 L +105.152 220.299 L +105.516 220.193 L +105.88 220.087 L +106.245 219.981 L +106.609 219.875 L +106.973 219.769 L +107.338 219.663 L +107.702 219.556 L +108.066 219.45 L +108.431 219.343 L +108.795 219.236 L +109.159 219.129 L +109.524 219.022 L +109.888 218.915 L +110.252 218.808 L +110.617 218.7 L +110.981 218.593 L +111.345 218.485 L +111.71 218.377 L +112.074 218.269 L +112.438 218.161 L +112.803 218.053 L +113.167 217.945 L +113.531 217.836 L +113.896 217.727 L +114.26 217.619 L +114.624 217.51 L +114.989 217.401 L +115.353 217.291 L +115.717 217.182 L +116.082 217.073 L +116.446 216.963 L +116.81 216.853 L +117.175 216.743 L +117.539 216.633 L +117.903 216.523 L +118.268 216.413 L +118.632 216.303 L +118.996 216.192 L +119.361 216.081 L +119.725 215.97 L +120.089 215.859 L +120.454 215.748 L +120.818 215.637 L +121.182 215.525 L +121.547 215.414 L +121.911 215.302 L +122.275 215.19 L +122.64 215.078 L +123.004 214.965 L +123.368 214.853 L +123.733 214.74 L +124.097 214.628 L +124.461 214.515 L +124.826 214.402 L +125.19 214.288 L +125.554 214.175 L +125.919 214.061 L +126.283 213.948 L +126.648 213.834 L +127.012 213.72 L +127.376 213.605 L +127.741 213.491 L +128.105 213.376 L +128.469 213.262 L +128.834 213.147 L +129.198 213.032 L +129.562 212.916 L +129.927 212.801 L +130.291 212.685 L +130.655 212.57 L +131.02 212.454 L +131.384 212.337 L +131.748 212.221 L +132.113 212.105 L +132.477 211.988 L +132.841 211.871 L +133.206 211.754 L +133.57 211.636 L +133.934 211.519 L +134.299 211.401 L +134.663 211.283 L +135.027 211.165 L +135.392 211.047 L +135.756 210.928 L +136.12 210.81 L +136.485 210.691 L +136.849 210.572 L +137.213 210.452 L +137.578 210.332 L +137.942 210.213 L +138.306 210.093 L +138.671 209.973 L +139.035 209.852 L +139.399 209.732 L +139.764 209.611 L +140.128 209.49 L +140.492 209.368 L +140.857 209.247 L +141.221 209.125 L +141.585 209.003 L +141.95 208.881 L +142.314 208.758 L +142.678 208.635 L +143.043 208.513 L +143.407 208.389 L +143.771 208.266 L +144.136 208.142 L +144.5 208.018 L +144.864 207.894 L +145.229 207.77 L +145.593 207.645 L +145.957 207.52 L +146.322 207.395 L +146.686 207.269 L +147.051 207.143 L +147.415 207.017 L +147.779 206.891 L +148.144 206.764 L +148.508 206.637 L +148.872 206.51 L +149.237 206.383 L +149.601 206.255 L +149.965 206.127 L +150.33 205.999 L +150.694 205.87 L +151.058 205.741 L +151.423 205.612 L +151.787 205.483 L +152.151 205.353 L +152.516 205.223 L +152.88 205.092 L +153.244 204.962 L +153.609 204.831 L +153.973 204.699 L +154.337 204.567 L +154.702 204.435 L +155.066 204.303 L +155.43 204.17 L +155.795 204.037 L +156.159 203.904 L +156.523 203.77 L +156.888 203.636 L +157.252 203.502 L +157.616 203.367 L +157.981 203.232 L +158.345 203.096 L +158.709 202.961 L +159.074 202.824 L +159.438 202.688 L +159.802 202.551 L +160.167 202.414 L +160.531 202.276 L +160.895 202.138 L +161.26 201.999 L +161.624 201.86 L +161.988 201.721 L +162.353 201.581 L +162.717 201.441 L +163.081 201.301 L +163.446 201.16 L +163.81 201.018 L +164.174 200.877 L +164.539 200.735 L +164.903 200.592 L +165.267 200.449 L +165.632 200.305 L +165.996 200.161 L +166.36 200.017 L +166.725 199.872 L +167.089 199.727 L +167.454 199.581 L +167.818 199.435 L +168.182 199.288 L +168.547 199.141 L +168.911 198.993 L +169.275 198.845 L +169.64 198.696 L +170.004 198.547 L +170.368 198.397 L +170.733 198.247 L +171.097 198.097 L +171.461 197.945 L +171.826 197.793 L +172.19 197.641 L +172.554 197.488 L +172.919 197.335 L +173.283 197.181 L +173.647 197.026 L +174.012 196.871 L +174.376 196.716 L +174.74 196.559 L +175.105 196.403 L +175.469 196.245 L +175.833 196.087 L +176.198 195.929 L +176.562 195.77 L +176.926 195.61 L +177.291 195.449 L +177.655 195.288 L +178.019 195.126 L +178.384 194.964 L +178.748 194.801 L +179.112 194.637 L +179.477 194.473 L +179.841 194.308 L +180.205 194.142 L +180.57 193.976 L +180.934 193.809 L +181.298 193.641 L +181.663 193.472 L +182.027 193.304 L +182.391 193.134 L +182.756 192.963 L +183.12 192.791 L +183.484 192.62 L +183.849 192.447 L +184.213 192.273 L +184.577 192.098 L +184.942 191.923 L +185.306 191.747 L +185.67 191.57 L +186.035 191.392 L +186.399 191.214 L +186.763 191.035 L +187.128 190.854 L +187.492 190.673 L +187.856 190.491 L +188.221 190.309 L +188.585 190.125 L +188.95 189.94 L +189.314 189.755 L +189.678 189.568 L +190.043 189.381 L +190.407 189.192 L +190.771 189.003 L +191.136 188.813 L +191.5 188.622 L +191.864 188.429 L +192.229 188.236 L +192.593 188.042 L +192.957 187.847 L +193.322 187.65 L +193.686 187.453 L +194.05 187.254 L +194.415 187.054 L +194.779 186.854 L +195.143 186.652 L +195.508 186.449 L +195.872 186.244 L +196.236 186.039 L +196.601 185.832 L +196.965 185.625 L +197.329 185.415 L +197.694 185.205 L +198.058 184.994 L +198.422 184.781 L +198.787 184.567 L +199.151 184.351 L +199.515 184.134 L +199.88 131.817 L +200.244 131.597 L +200.608 131.376 L +200.973 131.153 L +201.337 130.93 L +201.701 130.704 L +202.066 130.477 L +202.43 130.249 L +202.794 130.019 L +203.159 129.787 L +203.523 129.554 L +203.887 129.318 L +204.252 129.082 L +204.616 128.844 L +204.98 128.604 L +205.345 128.362 L +205.709 128.118 L +206.073 127.873 L +206.438 127.625 L +206.802 127.376 L +207.166 127.125 L +207.531 126.872 L +207.895 126.616 L +208.259 126.358 L +208.624 126.099 L +208.988 125.837 L +209.353 125.573 L +209.717 125.307 L +210.081 125.038 L +210.446 124.767 L +210.81 124.494 L +211.174 124.217 L +211.539 123.939 L +211.903 123.657 L +212.267 123.373 L +212.632 123.086 L +212.996 122.797 L +213.36 122.504 L +213.725 122.208 L +214.089 121.91 L +214.453 121.608 L +214.818 121.302 L +215.182 120.994 L +215.546 120.681 L +215.911 120.366 L +216.275 120.046 L +216.639 119.723 L +217.004 119.395 L +217.368 119.064 L +217.732 118.728 L +218.097 118.389 L +218.461 118.044 L +218.825 117.695 L +219.19 117.34 L +219.554 116.982 L +219.918 116.617 L +220.283 116.248 L +220.647 115.872 L +221.011 115.491 L +221.376 115.103 L +221.74 114.71 L +222.104 114.31 L +222.469 113.903 L +222.833 113.488 L +223.197 113.067 L +223.562 112.637 L +223.926 112.199 L +224.29 111.753 L +224.655 111.297 L +225.019 110.833 L +225.383 110.358 L +225.748 109.874 L +226.112 109.378 L +226.476 108.871 L +226.841 108.353 L +227.205 107.821 L +227.569 107.276 L +227.934 106.717 L +228.298 106.144 L +228.662 105.556 L +229.027 104.951 L +229.391 104.329 L +229.756 103.689 L +230.12 103.031 L +230.484 102.352 L +230.849 101.653 L +231.213 100.932 L +231.577 100.19 L +231.942 99.426 L +232.306 98.638 L +232.67 97.827 L +233.035 96.995 L +233.399 96.143 L +233.763 95.275 L +234.128 94.395 L +234.492 93.515 L +234.856 92.647 L +235.221 91.816 L +235.585 91.055 L +235.949 90.419 L +236.314 89.987 L +236.678 89.872 L +237.042 90.228 L +237.407 91.227 L +237.771 93.008 L +238.135 95.595 L +238.5 98.813 L +238.864 102.357 L +239.228 105.928 L +239.593 109.305 L +239.957 112.388 L +240.321 115.15 L +240.686 117.611 L +241.05 119.793 L +241.414 121.734 L +241.779 123.473 L +242.143 125.035 L +242.507 126.442 L +242.872 127.722 L +243.236 128.889 L +243.6 129.959 L +243.965 130.941 L +244.329 131.852 L +244.693 132.693 L +245.058 133.477 L +245.422 134.21 L +245.786 134.895 L +246.151 135.537 L +246.515 136.14 L +246.879 136.711 L +247.244 137.249 L +247.608 137.759 L +247.972 138.243 L +248.337 138.702 L +248.701 139.14 L +249.065 139.557 L +249.43 139.955 L +249.794 140.335 L +250.159 140.699 L +250.523 141.048 L +250.887 141.382 L +251.252 141.704 L +251.616 142.013 L +251.98 142.31 L +252.345 142.596 L +252.709 142.871 L +253.073 143.138 L +253.438 143.394 L +253.802 143.643 L +254.166 143.882 L +254.531 144.115 L +254.895 144.339 L +255.259 144.557 L +255.624 144.768 L +255.988 144.972 L +256.352 145.171 L +256.717 145.364 L +257.081 145.551 L +257.445 145.733 L +257.81 145.91 L +258.174 146.082 L +258.538 146.25 L +258.903 146.413 L +259.267 146.572 L +259.631 146.727 L +259.996 146.878 L +260.36 147.025 L +260.724 147.169 L +261.089 147.309 L +261.453 147.446 L +261.817 147.579 L +262.182 147.71 L +262.546 147.837 L +262.91 147.962 L +263.275 148.084 L +263.639 148.203 L +264.003 148.32 L +264.368 148.434 L +264.732 148.546 L +265.096 148.655 L +265.461 148.763 L +265.825 148.868 L +266.189 148.97 L +266.554 149.071 L +266.918 149.17 L +267.282 149.267 L +267.647 149.362 L +268.011 149.455 L +268.375 149.547 L +268.74 149.636 L +269.104 149.724 L +269.468 149.811 L +269.833 149.896 L +270.197 149.979 L +270.561 150.061 L +270.926 150.141 L +271.29 150.22 L +271.655 150.298 L +272.019 150.374 L +272.383 150.449 L +272.748 150.523 L +273.112 150.595 L +273.476 150.667 L +273.841 150.737 L +274.205 150.806 L +274.569 150.874 L +274.934 150.941 L +275.298 151.006 L +275.662 151.071 L +276.027 151.135 L +276.391 151.198 L +276.755 151.259 L +277.12 151.32 L +277.484 151.38 L +277.848 151.439 L +278.213 151.497 L +278.577 151.554 L +278.941 151.611 L +279.306 151.666 L +279.67 151.721 L +280.034 151.775 L +280.399 151.828 L +280.763 151.881 L +281.127 151.932 L +281.492 151.983 L +281.856 152.034 L +282.22 152.083 L +282.585 152.132 L +282.949 152.181 L +283.313 152.228 L +283.678 152.275 L +284.042 152.321 L +284.406 152.367 L +284.771 152.412 L +285.135 152.457 L +285.499 152.501 L +285.864 152.544 L +286.228 152.587 L +286.592 152.63 L +286.957 152.671 L +287.321 152.713 L +287.685 152.753 L +288.05 152.794 L +288.414 152.833 L +288.778 152.872 L +289.143 152.911 L +289.507 152.949 L +289.871 152.987 L +290.236 153.025 L +290.6 153.062 L +290.964 153.098 L +291.329 153.134 L +291.693 153.17 L +292.058 153.205 L +292.422 153.24 L +292.786 153.274 L +293.151 153.308 L +293.515 153.342 L +293.879 153.375 L +294.244 153.408 L +294.608 153.44 L +294.972 153.473 L +295.337 153.504 L +295.701 153.536 L +296.065 153.567 L +296.43 153.597 L +296.794 153.628 L +297.158 153.658 L +297.523 153.688 L +297.887 153.717 L +298.251 153.746 L +298.616 153.775 L +298.98 153.803 L +299.344 153.832 L +299.709 153.86 L +300.073 153.887 L +300.437 153.914 L +300.802 153.941 L +301.166 153.968 L +301.53 153.995 L +301.895 154.021 L +302.259 154.047 L +302.623 154.072 L +302.988 154.098 L +303.352 154.123 L +303.716 154.148 L +304.081 154.173 L +304.445 154.197 L +304.809 154.221 L +305.174 154.245 L +305.538 154.269 L +305.902 154.292 L +306.267 154.315 L +306.631 154.338 L +306.995 154.361 L +307.36 154.384 L +307.724 154.406 L +308.088 154.428 L +308.453 154.45 L +308.817 154.472 L +309.181 154.493 L +309.546 154.515 L +309.91 154.536 L +310.274 154.557 L +310.639 154.578 L +311.003 154.598 L +311.367 154.618 L +311.732 154.639 L +312.096 154.659 L +312.461 154.678 L +312.825 154.698 L +313.189 154.717 L +313.554 154.737 L +313.918 154.756 L +314.282 154.775 L +314.647 154.793 L +315.011 154.812 L +315.375 154.83 L +315.74 154.849 L +316.104 154.867 L +316.468 154.885 L +316.833 154.902 L +317.197 154.92 L +317.561 154.938 L +317.926 154.955 L +318.29 154.972 L +318.654 154.989 L +319.019 155.006 L +319.383 155.023 L +319.747 155.039 L +320.112 155.056 L +320.476 155.072 L +320.84 155.088 L +321.205 155.104 L +321.569 155.12 L +321.933 155.136 L +322.298 155.152 L +322.662 155.167 L +323.026 155.183 L +323.391 155.198 L +323.755 155.213 L +324.119 155.228 L +324.484 155.243 L +324.848 155.258 L +325.212 155.272 L +325.577 155.287 L +325.941 155.301 L +326.305 155.316 L +326.67 155.33 L +327.034 155.344 L +327.398 155.358 L +327.763 155.372 L +328.127 155.386 L +328.491 155.399 L +328.856 155.413 L +329.22 155.426 L +329.584 155.439 L +329.949 155.453 L +330.313 155.466 L +330.677 155.479 L +331.042 155.492 L +331.406 155.505 L +331.77 155.517 L +332.135 155.53 L +332.499 155.542 L +332.864 155.555 L +333.228 155.567 L +333.592 155.579 L +333.957 155.592 L +334.321 155.604 L +334.685 155.616 L +335.05 155.628 L +335.414 155.639 L +335.778 155.651 L +336.143 155.663 L +336.507 155.674 L +336.871 155.686 L +337.236 155.697 L +337.6 155.708 L +337.964 155.719 L +338.329 155.731 L +338.693 155.742 L +339.057 155.753 L +339.422 155.763 L +339.786 155.774 L +340.15 155.785 L +340.515 155.796 L +340.879 155.806 L +341.243 155.817 L +341.608 155.827 L +341.972 155.838 L +342.336 155.848 L +342.701 155.858 L +343.065 155.868 L +343.429 155.878 L +343.794 155.888 L +344.158 155.898 L +344.522 155.908 L +344.887 155.918 L +345.251 155.927 L +345.615 155.937 L +345.98 155.947 L +346.344 155.956 L +346.708 155.966 L +347.073 155.975 L +347.437 155.984 L +347.801 155.994 L +348.166 156.003 L +348.53 156.012 L +348.894 156.021 L +349.259 156.03 L +349.623 156.039 L +349.987 156.048 L +350.352 156.057 L +350.716 156.065 L +351.08 156.074 L +351.445 156.083 L +351.809 156.091 L +352.173 156.1 L +352.538 156.108 L +352.902 156.117 L +353.266 156.125 L +353.631 156.133 L +353.995 156.142 L +354.36 156.15 L +354.724 156.158 L +355.088 156.166 L +355.453 156.174 L +355.817 156.182 L +356.181 156.19 L +356.546 156.198 L +356.91 156.206 L +357.274 156.214 L +357.639 156.221 L +358.003 156.229 L +358.367 156.237 L +358.732 156.244 L +359.096 156.252 L +359.46 156.259 L +359.825 156.267 L +360.189 156.274 L +360.553 156.282 L +360.918 156.289 L +361.282 156.296 L +361.646 156.303 L +362.011 156.311 L +362.375 156.318 L +362.739 156.325 L +363.104 156.332 L +363.468 156.339 L +363.832 156.346 L +364.197 156.353 L +364.561 156.36 L +364.925 156.366 L +365.29 156.373 L +365.654 156.38 L +366.018 156.387 L +366.383 156.393 L +366.747 156.4 L +367.111 156.407 L +367.476 156.413 L +367.84 156.42 L +368.204 156.426 L +368.569 156.433 L +368.933 156.439 L +369.297 156.445 L +369.662 156.452 L +370.026 156.458 L +370.39 156.464 L +370.755 156.47 L +371.119 156.476 L +371.483 156.483 L +371.848 156.489 L +372.212 156.495 L +372.576 156.501 L +372.941 156.507 L +373.305 156.513 L +373.669 156.519 L +374.034 156.524 L +374.398 156.53 L +374.763 156.536 L +375.127 156.542 L +375.491 156.548 L +375.856 156.553 L +376.22 156.559 L +376.584 156.565 L +376.949 156.57 L +377.313 156.576 L +377.677 156.581 L +378.042 156.587 L +378.406 156.592 L +378.77 156.598 L +379.135 156.603 L +379.499 156.609 L +379.863 156.614 L +380.228 156.619 L +380.592 156.625 L +380.956 156.63 L +381.321 156.635 L +381.685 156.64 L +382.049 156.646 L +382.414 156.651 L +382.778 156.656 L +383.142 156.661 L +383.507 156.666 L +383.871 156.671 L +384.235 156.676 L +384.6 156.681 L +384.964 156.686 L +385.328 156.691 L +385.693 156.696 L +386.057 156.701 L +386.421 156.706 L +386.786 156.71 L +387.15 156.715 L +387.514 156.72 L +387.879 156.725 L +388.243 156.729 L +388.607 156.734 L +388.972 156.739 L +389.336 156.743 L +389.7 156.748 L +390.065 156.753 L +390.429 156.757 L +390.793 156.762 L +391.158 156.766 L +391.522 156.771 L +391.886 156.775 L +392.251 156.78 L +392.615 156.784 L +392.979 156.788 L +393.344 156.793 L +393.708 156.797 L +394.072 156.802 L +394.437 156.806 L +394.801 156.81 L +395.166 156.814 L +395.53 156.819 L +395.894 156.823 L +396.259 156.827 L +396.623 156.831 L +396.987 156.835 L +397.352 156.84 L +397.716 156.844 L +398.08 156.848 L +398.445 156.852 L +398.809 156.856 L +399.173 156.86 L +399.538 156.864 L +399.902 156.868 L +400.266 156.872 L +400.631 156.876 L +400.995 156.88 L +401.359 156.884 L +401.724 156.887 L +402.088 156.891 L +402.452 156.895 L +402.817 156.899 L +403.181 156.903 L +403.545 156.907 L +403.91 156.91 L +404.274 156.914 L +404.638 156.918 L +405.003 156.921 L +405.367 156.925 L +405.731 156.929 L +406.096 156.933 L +406.46 156.936 L +406.824 156.94 L +407.189 156.943 L +407.553 156.947 L +407.917 156.95 L +408.282 156.954 L +408.646 156.958 L +409.01 156.961 L +409.375 156.965 L +409.739 156.968 L +410.103 156.972 L +410.468 156.975 L +410.832 156.978 L +411.196 156.982 L +411.561 156.985 L +411.925 156.989 L +412.289 156.992 L +412.654 156.995 L +413.018 156.999 L +413.382 157.002 L +413.747 157.005 L +414.111 157.008 L +414.475 157.012 L +414.84 157.015 L +415.204 157.018 L +415.568 157.021 L +415.933 157.025 L +416.297 157.028 L +416.662 157.031 L +417.026 157.034 L +417.39 157.037 L +417.755 157.04 L +418.119 157.043 L +418.483 157.047 L +418.848 157.05 L +419.212 157.053 L +419.576 157.056 L +419.941 157.059 L +420.305 157.062 L +420.669 157.065 L +421.034 157.068 L +421.398 157.071 L +421.762 157.074 L +422.127 157.077 L +422.491 157.08 L +422.855 157.083 L +423.22 157.085 L +423.584 157.088 L +423.948 157.091 L +424.313 157.094 L +424.677 157.097 L +425.041 157.1 L +425.406 157.103 L +425.77 157.105 L +426.134 157.108 L +426.499 157.111 L +426.863 157.114 L +427.227 157.117 L +427.592 157.119 L +427.956 157.122 L +428.32 157.125 L +428.685 157.127 L +429.049 157.13 L +429.413 157.133 L +429.778 157.136 L +430.142 157.138 L +430.506 157.141 L +430.871 157.143 L +431.235 157.146 L +431.599 157.149 L +431.964 157.151 L +432.328 157.154 L +432.692 157.156 L +433.057 157.159 L +433.421 157.162 L +433.785 157.164 L +434.15 157.167 L +434.514 157.169 L +434.878 157.172 L +435.243 157.174 L +435.607 157.177 L +435.971 157.179 L +436.336 157.182 L +436.7 157.184 L +437.065 157.187 L +437.429 157.189 L +437.793 157.191 L +438.158 157.194 L +438.522 157.196 L +438.886 157.199 L +439.251 157.201 L +439.615 157.203 L +439.979 157.206 L +440.344 157.208 L +440.708 157.21 L +441.072 157.213 L +441.437 157.215 L +441.801 157.217 L +442.165 157.22 L +442.53 157.222 L +s +78.19 230.21 m +78.919 229.983 L +79.648 229.756 L +80.376 229.529 L +81.105 229.302 L +81.834 229.074 L +82.562 228.847 L +83.291 228.619 L +84.02 228.391 L +84.749 228.163 L +85.477 227.935 L +86.206 227.707 L +86.935 227.479 L +87.663 227.251 L +88.392 227.023 L +89.121 226.795 L +89.849 226.567 L +90.578 226.339 L +91.307 226.111 L +92.035 225.884 L +92.764 225.656 L +93.493 225.429 L +94.221 225.201 L +94.95 224.974 L +95.679 224.747 L +96.407 224.52 L +97.136 224.294 L +97.865 224.067 L +98.593 223.841 L +99.322 223.615 L +100.051 223.39 L +100.779 223.164 L +101.508 222.939 L +102.237 222.714 L +102.965 222.49 L +103.694 222.266 L +104.423 222.042 L +105.152 221.819 L +105.88 221.596 L +106.609 221.373 L +107.338 221.151 L +108.066 220.929 L +108.795 220.707 L +109.524 220.486 L +110.252 220.265 L +110.981 220.045 L +111.71 219.825 L +112.438 219.606 L +113.167 219.387 L +113.896 219.169 L +114.624 218.951 L +115.353 218.733 L +116.082 218.517 L +116.81 218.3 L +117.539 218.085 L +118.268 217.869 L +118.996 217.655 L +119.725 217.44 L +120.454 217.227 L +121.182 217.014 L +121.911 216.801 L +122.64 216.59 L +123.368 216.378 L +124.097 216.168 L +124.826 215.958 L +125.554 215.748 L +126.283 215.539 L +127.012 163.232 L +127.741 163.025 L +128.469 162.818 L +129.198 162.612 L +129.927 162.406 L +130.655 162.201 L +131.384 161.997 L +132.113 161.794 L +132.841 161.591 L +133.57 161.39 L +134.299 161.188 L +135.027 160.988 L +135.756 160.789 L +136.485 160.59 L +137.213 160.392 L +137.942 160.194 L +138.671 159.998 L +139.399 159.803 L +140.128 159.608 L +140.857 159.414 L +141.585 159.221 L +142.314 159.029 L +143.043 158.839 L +143.771 158.648 L +144.5 158.459 L +145.229 158.271 L +145.957 158.084 L +146.686 157.898 L +147.415 157.714 L +148.144 157.53 L +148.872 157.347 L +149.601 157.166 L +150.33 156.985 L +151.058 156.806 L +151.787 156.628 L +152.516 156.452 L +153.244 156.277 L +153.973 156.103 L +154.702 155.931 L +155.43 155.761 L +156.159 155.591 L +156.888 155.424 L +157.616 155.258 L +158.345 155.094 L +159.074 154.931 L +159.802 154.771 L +160.531 154.612 L +161.26 154.455 L +161.988 154.3 L +162.717 154.148 L +163.446 153.997 L +164.174 153.849 L +164.903 153.703 L +165.632 153.56 L +166.36 153.419 L +167.089 153.28 L +167.818 153.145 L +168.547 153.012 L +169.275 152.882 L +170.004 152.754 L +170.733 152.631 L +171.461 152.51 L +172.19 152.392 L +172.919 152.278 L +173.647 152.167 L +174.376 152.06 L +175.105 151.956 L +175.833 151.856 L +176.562 151.76 L +177.291 151.668 L +178.019 151.58 L +178.748 151.497 L +179.477 151.417 L +180.205 151.342 L +180.934 151.271 L +181.663 151.205 L +182.391 151.143 L +183.12 151.086 L +183.849 151.033 L +184.577 150.985 L +185.306 150.942 L +186.035 150.904 L +186.763 150.87 L +187.492 150.841 L +188.221 150.817 L +188.95 150.797 L +189.678 150.783 L +190.407 150.773 L +191.136 150.767 L +191.864 150.766 L +192.593 150.77 L +193.322 150.778 L +194.05 150.79 L +194.779 150.806 L +195.508 150.827 L +196.236 150.851 L +196.965 150.88 L +197.694 150.911 L +198.422 150.947 L +199.151 150.986 L +199.88 151.028 L +200.608 151.073 L +201.337 151.121 L +202.066 151.172 L +202.794 151.225 L +203.523 151.281 L +204.252 151.339 L +204.98 151.398 L +205.709 151.46 L +206.438 151.524 L +207.166 151.589 L +207.895 151.656 L +208.624 151.723 L +209.353 151.792 L +210.081 151.862 L +210.81 151.933 L +211.539 152.005 L +212.267 152.077 L +212.996 152.149 L +213.725 152.222 L +214.453 152.295 L +215.182 152.368 L +215.911 152.441 L +216.639 152.515 L +217.368 152.588 L +218.097 152.661 L +218.825 152.733 L +219.554 152.806 L +220.283 152.877 L +221.011 152.949 L +221.74 153.019 L +222.469 153.09 L +223.197 153.159 L +223.926 153.228 L +224.655 153.296 L +225.383 153.364 L +226.112 153.431 L +226.841 153.497 L +227.569 153.562 L +228.298 153.626 L +229.027 153.69 L +229.756 153.753 L +230.484 153.815 L +231.213 153.876 L +231.942 153.936 L +232.67 153.995 L +233.399 154.053 L +234.128 154.111 L +234.856 154.167 L +235.585 154.223 L +236.314 154.278 L +237.042 154.332 L +237.771 154.385 L +238.5 154.437 L +239.228 154.488 L +239.957 154.539 L +240.686 154.589 L +241.414 154.637 L +242.143 154.685 L +242.872 154.733 L +243.6 154.779 L +244.329 154.824 L +245.058 154.869 L +245.786 154.913 L +246.515 154.957 L +247.244 154.999 L +247.972 155.041 L +248.701 155.082 L +249.43 155.122 L +250.159 155.162 L +250.887 155.201 L +251.616 155.239 L +252.345 155.276 L +253.073 155.313 L +253.802 155.35 L +254.531 155.385 L +255.259 155.42 L +255.988 155.455 L +256.717 155.488 L +257.445 155.521 L +258.174 155.554 L +258.903 155.586 L +259.631 155.618 L +260.36 155.648 L +261.089 155.679 L +261.817 155.709 L +262.546 155.738 L +263.275 155.767 L +264.003 155.795 L +264.732 155.823 L +265.461 155.85 L +266.189 155.877 L +266.918 155.904 L +267.647 155.93 L +268.375 155.955 L +269.104 155.98 L +269.833 156.005 L +270.561 156.029 L +271.29 156.053 L +272.019 156.077 L +272.748 156.1 L +273.476 156.122 L +274.205 156.145 L +274.934 156.167 L +275.662 156.188 L +276.391 156.209 L +277.12 156.23 L +277.848 156.251 L +278.577 156.271 L +279.306 156.291 L +280.034 156.31 L +280.763 156.33 L +281.492 156.349 L +282.22 156.367 L +282.949 156.385 L +283.678 156.404 L +284.406 156.421 L +285.135 156.439 L +285.864 156.456 L +286.592 156.473 L +287.321 156.49 L +288.05 156.506 L +288.778 156.522 L +289.507 156.538 L +290.236 156.554 L +290.964 156.569 L +291.693 156.584 L +292.422 156.599 L +293.151 156.614 L +293.879 156.628 L +294.608 156.643 L +295.337 156.657 L +296.065 156.671 L +296.794 156.684 L +297.523 156.698 L +298.251 156.711 L +298.98 156.724 L +299.709 156.737 L +300.437 156.75 L +301.166 156.762 L +301.895 156.774 L +302.623 156.787 L +303.352 156.799 L +304.081 156.81 L +304.809 156.822 L +305.538 156.833 L +306.267 156.845 L +306.995 156.856 L +307.724 156.867 L +308.453 156.878 L +309.181 156.888 L +309.91 156.899 L +310.639 156.909 L +311.367 156.92 L +312.096 156.93 L +312.825 156.94 L +313.554 156.949 L +314.282 156.959 L +315.011 156.969 L +315.74 156.978 L +316.468 156.988 L +317.197 156.997 L +317.926 157.006 L +318.654 157.015 L +319.383 157.024 L +320.112 157.032 L +320.84 157.041 L +321.569 157.049 L +322.298 157.058 L +323.026 157.066 L +323.755 157.074 L +324.484 157.082 L +325.212 157.09 L +325.941 157.098 L +326.67 157.106 L +327.398 157.113 L +328.127 157.121 L +328.856 157.128 L +329.584 157.136 L +330.313 157.143 L +331.042 157.15 L +331.77 157.157 L +332.499 157.164 L +333.228 157.171 L +333.957 157.178 L +334.685 157.185 L +335.414 157.192 L +336.143 157.198 L +336.871 157.205 L +337.6 157.211 L +338.329 157.217 L +339.057 157.224 L +339.786 157.23 L +340.515 157.236 L +341.243 157.242 L +341.972 157.248 L +342.701 157.254 L +343.429 157.26 L +344.158 157.266 L +344.887 157.271 L +345.615 157.277 L +346.344 157.283 L +347.073 157.288 L +347.801 157.294 L +348.53 157.299 L +349.259 157.304 L +349.987 157.31 L +350.716 157.315 L +351.445 157.32 L +352.173 157.325 L +352.902 157.33 L +353.631 157.335 L +354.36 157.34 L +355.088 157.345 L +355.817 157.35 L +356.546 157.354 L +357.274 157.359 L +358.003 157.364 L +358.732 157.368 L +359.46 157.373 L +360.189 157.377 L +360.918 157.382 L +361.646 157.386 L +362.375 157.391 L +363.104 157.395 L +363.832 157.399 L +364.561 157.403 L +365.29 157.408 L +366.018 157.412 L +366.747 157.416 L +367.476 157.42 L +368.204 157.424 L +368.933 157.428 L +369.662 157.432 L +370.39 157.436 L +371.119 157.439 L +371.848 157.443 L +372.576 157.447 L +373.305 157.451 L +374.034 157.454 L +374.763 157.458 L +375.491 157.462 L +376.22 157.465 L +376.949 157.469 L +377.677 157.472 L +378.406 157.476 L +379.135 157.479 L +379.863 157.482 L +380.592 157.486 L +381.321 157.489 L +382.049 157.492 L +382.778 157.496 L +383.507 157.499 L +384.235 157.502 L +384.964 157.505 L +385.693 157.508 L +386.421 157.512 L +387.15 157.515 L +387.879 157.518 L +388.607 157.521 L +389.336 157.524 L +390.065 157.527 L +390.793 157.53 L +391.522 157.532 L +392.251 157.535 L +392.979 157.538 L +393.708 157.541 L +394.437 157.544 L +395.166 157.547 L +395.894 157.549 L +396.623 157.552 L +397.352 157.555 L +398.08 157.557 L +398.809 157.56 L +399.538 157.563 L +400.266 157.565 L +400.995 157.568 L +401.724 157.57 L +402.452 157.573 L +403.181 157.575 L +403.91 157.578 L +404.638 157.58 L +405.367 157.583 L +406.096 157.585 L +406.824 157.587 L +407.553 157.59 L +408.282 157.592 L +409.01 157.594 L +409.739 157.597 L +410.468 157.599 L +411.196 157.601 L +411.925 157.603 L +412.654 157.606 L +413.382 157.608 L +414.111 157.61 L +414.84 157.612 L +415.568 157.614 L +416.297 157.616 L +417.026 157.619 L +417.755 157.621 L +418.483 157.623 L +419.212 157.625 L +419.941 157.627 L +420.669 157.629 L +421.398 157.631 L +422.127 157.633 L +422.855 157.635 L +423.584 157.637 L +424.313 157.639 L +425.041 157.64 L +425.77 157.642 L +426.499 157.644 L +427.227 157.646 L +427.956 157.648 L +428.685 157.65 L +429.413 157.652 L +430.142 157.653 L +430.871 157.655 L +431.599 157.657 L +432.328 157.659 L +433.057 157.66 L +433.785 157.662 L +434.514 157.664 L +435.243 157.666 L +435.971 157.667 L +436.7 157.669 L +437.429 157.671 L +438.158 157.672 L +438.886 157.674 L +439.615 157.675 L +440.344 157.677 L +441.072 157.679 L +441.801 157.68 L +442.53 157.682 L +s +P +0 g +0.144 w +[ ] 0 setdash +3.25 setmiterlimit +450.12 237.508 m +70.6 237.508 L +s +70.6 237.508 m +70.6 2.952 L +s +1 g +[ ] 0 setdash +70.6 2.952 m +450.12 2.952 L +s +450.12 2.952 m +450.12 237.508 L +s +0 g +[ ] 0 setdash +p +0 setlinecap +78.19 237.508 m +78.19 234.611 L +s +P +p +np 74 239 m +74 253 L +82 253 L +82 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 75.19 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.19 -1.668 m +-2.19 14.332 L +7.81 14.332 L +7.81 -1.668 L +cp +clip np +/MISOfy +{ + /newfontname exch def + /oldfontname exch def + oldfontname findfont + dup length dict begin + {1 index/FID ne{def}{pop pop}ifelse}forall + /Encoding ISOLatin1Encoding def + currentdict + end + newfontname exch definefont pop +}def +%%IncludeResource: font Times-Roman +%%IncludeFont: Times-Roman +%%BeginResource: font Times-Roman-MISO +%%BeginFont: Times-Roman-MISO +/Times-Roman /Times-Roman-MISO MISOfy +%%EndFont +%%EndResource +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(0) N +P +[1 0 0 1 -75.19 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +92.764 237.508 m +92.764 235.77 L +s +P +p +0 setlinecap +107.338 237.508 m +107.338 235.77 L +s +P +p +0 setlinecap +121.911 237.508 m +121.911 235.77 L +s +P +p +0 setlinecap +136.485 237.508 m +136.485 235.77 L +s +P +p +0 setlinecap +151.058 237.508 m +151.058 234.611 L +s +P +p +np 147 239 m +147 253 L +155 253 L +155 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 148.058 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.058 -1.668 m +-2.058 14.332 L +7.942 14.332 L +7.942 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(1) N +P +[1 0 0 1 -148.058 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +165.632 237.508 m +165.632 235.77 L +s +P +p +0 setlinecap +180.205 237.508 m +180.205 235.77 L +s +P +p +0 setlinecap +194.779 237.508 m +194.779 235.77 L +s +P +p +0 setlinecap +209.353 237.508 m +209.353 235.77 L +s +P +p +0 setlinecap +223.926 237.508 m +223.926 234.611 L +s +P +p +np 220 239 m +220 253 L +228 253 L +228 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 220.926 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.926 -1.668 m +-1.926 14.332 L +8.074 14.332 L +8.074 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(2) N +P +[1 0 0 1 -220.926 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +238.5 237.508 m +238.5 235.77 L +s +P +p +0 setlinecap +253.073 237.508 m +253.073 235.77 L +s +P +p +0 setlinecap +267.647 237.508 m +267.647 235.77 L +s +P +p +0 setlinecap +282.22 237.508 m +282.22 235.77 L +s +P +p +0 setlinecap +296.794 237.508 m +296.794 234.611 L +s +P +p +np 293 239 m +293 253 L +301 253 L +301 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 293.794 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.794 -1.668 m +-1.794 14.332 L +8.206 14.332 L +8.206 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(3) N +P +[1 0 0 1 -293.794 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +311.367 237.508 m +311.367 235.77 L +s +P +p +0 setlinecap +325.941 237.508 m +325.941 235.77 L +s +P +p +0 setlinecap +340.515 237.508 m +340.515 235.77 L +s +P +p +0 setlinecap +355.088 237.508 m +355.088 235.77 L +s +P +p +0 setlinecap +369.662 237.508 m +369.662 234.611 L +s +P +p +np 365 239 m +365 253 L +374 253 L +374 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 366.287 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.287 -1.668 m +-2.287 14.332 L +8.713 14.332 L +8.713 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0.75 10.5 m +(4) N +P +[1 0 0 1 -366.287 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +384.235 237.508 m +384.235 235.77 L +s +P +p +0 setlinecap +398.809 237.508 m +398.809 235.77 L +s +P +p +0 setlinecap +413.382 237.508 m +413.382 235.77 L +s +P +p +0 setlinecap +427.956 237.508 m +427.956 235.77 L +s +P +p +0 setlinecap +442.53 237.508 m +442.53 234.611 L +s +P +p +np 439 239 m +439 253 L +447 253 L +447 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 439.53 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.53 -1.668 m +-1.53 14.332 L +8.47 14.332 L +8.47 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(5) N +P +[1 0 0 1 -439.53 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 210.041 m +73.497 210.041 L +s +P +p +np 61 203 m +61 217 L +69 217 L +69 203 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 62.44 203.666 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -1.666 m +-2.44 14.334 L +7.56 14.334 L +7.56 -1.666 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(0) N +P +[1 0 0 1 -62.44 -203.666 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 157.941 m +73.497 157.941 L +s +P +p +np 61 151 m +61 165 L +69 165 L +69 151 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 61.69 151.566 ] concat +1 w +[ ] 0 setdash +p +np -1.69 -1.566 m +-1.69 14.434 L +8.31 14.434 L +8.31 -1.566 L +cp +clip np +%%BeginResource: font Mathematica1 +%%BeginFont: Mathematica1 +%!PS-AdobeFont-1.0: Mathematica1 001.000 +%%CreationDate: 8/26/01 at 4:07 PM +%%VMusage: 1024 31527 +% Mathematica typeface design by Andre Kuzniarek, with Gregg Snyder and Stephen Wolfram. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00] +% ADL: 800 200 0 +%%EndComments +FontDirectory/Mathematica1 known{/Mathematica1 findfont dup/UniqueID known{dup +/UniqueID get 5095641 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +20 dict begin +/FontInfo 16 dict dup begin + /version (001.000) readonly def + /FullName (Mathematica1) readonly def + /FamilyName (Mathematica1) readonly def + /Weight (Medium) readonly def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + /Notice (Mathematica typeface design by Andre Kuzniarek, with Gregg Snyder and Stephen Wolfram. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00]) readonly def + /em 1000 def + /ascent 800 def + /descent 200 def +end readonly def +/FontName /Mathematica1 def +/Encoding 256 array +dup 0/NUL put +dup 1/Eth put +dup 2/eth put +dup 3/Lslash put +dup 4/lslash put +dup 5/Scaron put +dup 6/scaron put +dup 7/Yacute put +dup 8/yacute put +dup 9/HT put +dup 10/LF put +dup 11/Thorn put +dup 12/thorn put +dup 13/CR put +dup 14/Zcaron put +dup 15/zcaron put +dup 16/DLE put +dup 17/DC1 put +dup 18/DC2 put +dup 19/DC3 put +dup 20/DC4 put +dup 21/onehalf put +dup 22/onequarter put +dup 23/onesuperior put +dup 24/threequarters put +dup 25/threesuperior put +dup 26/twosuperior put +dup 27/brokenbar put +dup 28/minus put +dup 29/multiply put +dup 30/RS put +dup 31/US put +dup 32/Space put +dup 33/Exclamation put +dup 34/ForAll put +dup 35/NumberSign put +dup 36/Exists put +dup 37/Percent put +dup 38/Ampersand put +dup 39/SmallMember put +dup 40/LParen put +dup 41/RParen put +dup 42/Star put +dup 43/Plus put +dup 44/Comma put +dup 45/Minus put +dup 46/Period put +dup 47/Slash put +dup 48/Zero put +dup 49/One put +dup 50/Two put +dup 51/Three put +dup 52/Four put +dup 53/Five put +dup 54/Six put +dup 55/Seven put +dup 56/Eight put +dup 57/Nine put +dup 58/Colon put +dup 59/SemiColon put +dup 60/Less put +dup 61/Equal put +dup 62/Greater put +dup 63/Question put +dup 64/TildeFullEqual put +dup 65/CapAlpha put +dup 66/CapBeta put +dup 67/CapChi put +dup 68/CapDelta put +dup 69/CapEpsilon put +dup 70/CapPhi put +dup 71/CapGamma put +dup 72/CapEta put +dup 73/CapIota put +dup 74/CurlyTheta put +dup 75/CapKappa put +dup 76/CapLambda put +dup 77/CapMu put +dup 78/CapNu put +dup 79/CapOmicron put +dup 80/CapPi put +dup 81/CapTheta put +dup 82/CapRho put +dup 83/CapSigma put +dup 84/CapTau put +dup 85/CapUpsilon put +dup 86/FinalSigma put +dup 87/CapOmega put +dup 88/CapXi put +dup 89/CapPsi put +dup 90/CapZeta put +dup 91/LBracket put +dup 92/Therefore put +dup 93/RBracket put +dup 94/Perpendicular put +dup 95/Underbar put +dup 96/Hat put +dup 97/Alpha put +dup 98/Beta put +dup 99/Chi put +dup 100/Delta put +dup 101/Epsilon put +dup 102/Phi put +dup 103/Gamma put +dup 104/Eta put +dup 105/Iota put +dup 106/CurlyPhi put +dup 107/Kappa put +dup 108/Lambda put +dup 109/Mu put +dup 110/Nu put +dup 111/Omicron put +dup 112/Pi put +dup 113/Theta put +dup 114/Rho put +dup 115/Sigma put +dup 116/Tau put +dup 117/Upsilon put +dup 118/CurlyPi put +dup 119/Omega put +dup 120/Xi put +dup 121/Psi put +dup 122/Zeta put +dup 123/LBrace put +dup 124/VertBar put +dup 125/RBrace put +dup 126/Tilde put +dup 127/DEL put +dup 128/FractionBarExt put +dup 129/EscapeChar put +dup 130/SelectPlaceholder put +dup 131/Placeholder put +dup 132/Continuation put +dup 133/Skeleton put +dup 134/LSkeleton put +dup 135/RSkeleton put +dup 136/Spacer put +dup 137/Cross put +dup 138/DblEqual put +dup 139/Grave put +dup 140/Acute put +dup 141/DoubleAcute put +dup 142/OverTilde put +dup 143/OverBar put +dup 144/DblUpDownArrow put +dup 145/DblUpExtens1 put +dup 146/DblLongLArrow put +dup 147/DblExtens put +dup 148/DblLongRArrow put +dup 149/DblLRArrow2 put +dup 150/DblLongLRArrow put +dup 151/UpDownArrow put +dup 152/LongLArrow put +dup 153/LongRArrow put +dup 154/LongLRArrow put +dup 155/ColonEqual put +dup 156/Diamond2 put +dup 157/NotSquareSprsetEqual put +dup 158/AtSign put +dup 159/Solidmedsqr put +dup 160/OverDot put +dup 161/CurlyCapUpsilon put +dup 162/Prime put +dup 163/LessEqual put +dup 164/Fraction put +dup 165/Infinity put +dup 166/RuleDelayed put +dup 167/ClubSuit put +dup 168/DiamondSuit put +dup 169/HeartSuit put +dup 170/SpadeSuit put +dup 171/LRArrow put +dup 172/LArrow put +dup 173/UpArrow put +dup 174/RArrow put +dup 175/DownArrow put +dup 176/Degree put +dup 177/PlusMinus put +dup 178/DoublePrime put +dup 179/GreaterEqual put +dup 180/Multiply put +dup 181/Proportional put +dup 182/PartialDiff put +dup 183/Bullet put +dup 184/Divide put +dup 185/NotEqual put +dup 186/Equivalence put +dup 187/Approxequal put +dup 188/Ellipsis put +dup 189/ArrowVertEx put +dup 190/ArrowHorizEx put +dup 191/CarriageReturn put +dup 192/Aleph put +dup 193/IFraktur put +dup 194/RFraktur put +dup 195/Weierstrass put +dup 196/CircleMultiply put +dup 197/CirclePlus put +dup 198/EmptySet put +dup 199/Union put +dup 200/Intersection put +dup 201/ProperSuperset put +dup 202/NbSpace put +dup 203/NotSubset put +dup 204/ProperSubset put +dup 205/ReflexSubset put +dup 206/Element put +dup 207/NotElement put +dup 208/Angle put +dup 209/Gradient put +dup 210/RegTM put +dup 211/Copyright put +dup 212/TM put +dup 213/Product put +dup 214/Radical put +dup 215/DotMath put +dup 216/LogicalNot put +dup 217/Wedge put +dup 218/Vee put +dup 219/DblLRArrow put +dup 220/DblLArrow put +dup 221/DblUpArrow put +dup 222/DblRArrow put +dup 223/DblDownArrow put +dup 224/Lozenge put +dup 225/LAngle put +dup 226/Diffd put +dup 227/Expe put +dup 228/Imagi put +dup 229/Sum put +dup 230/LParenTop put +dup 231/LParenEx put +dup 232/LParenBot put +dup 233/LBracketTop put +dup 234/LBracketEx put +dup 235/LBracketBot put +dup 236/LBraceTop put +dup 237/LBraceMid put +dup 238/LBraceBot put +dup 239/BraceEx put +dup 240/Slot put +dup 241/RAngle put +dup 242/Intergral put +dup 243/IntegralTop put +dup 244/IntegralEx put +dup 245/IntegralBot put +dup 246/RParenTop put +dup 247/RParenEx put +dup 248/RParenBot put +dup 249/RBracketTop put +dup 250/RBracketEx put +dup 251/RBracketBot put +dup 252/RBraceTop put +dup 253/RBraceMid put +dup 254/RBraceBot put +dup 255/Wolf put + readonly def +/PaintType 0 def +/FontType 1 def +/StrokeWidth 0 def +/FontMatrix[0.001 0 0 0.001 0 0]readonly def +/UniqueID 5095641 def +/FontBBox{-120 -220 1544 923}readonly def +currentdict end +currentfile eexec +D8061D93A8246509E76A3EC656E953B7C22E43117F5A3BC2421790057C314DAE3EFBFF49F45DA34B +424A961BE670A194E7E4BF0FF295DEE23134A14A7C08B6602621D885EE631B1D8D3003CF2093E039 +4D77FCEFCA8BA8965D1783DCAD9EFE6C7E420CF7B898933309A89F84721ADE7F3AE4443C5EAE8155 +759A9EB6F738F7BA81C192EE45DAD00F398FFD6904111BBD91BFEE328004F2A18BCCD98DCDB2CE23 +961B00F204E50EA022D00CE2B68E653626D4BB5AFA334A0D657307416FAF7AA8C43ED4DC541F1B7B +B7500B3F423D9D369F8192FD00A59FD5E6ABC70F788FB70976CC1907DDC309F4B690AA2D2BF12CAE +C493958CC0E76CE9EB5FF8BD1F1650F659E5C123EE455B7D77C39952C212AF319BF19A91E36DE52F +0EF84B602704BD6C7C94E1B0E067210DB919F6231755A2CC5D5FE129279B43A2E2CCD27F56F00B05 +C8AC10AB07FABBEFB3509088301FE78CAF8B440C5BA2FFE06BBFCD066046618F3B6AA2E4B17B296E +1F3F1560E19CBAD54E16E4B7A65622E468F6BDF97C50277E355D1DD8843D0A449A147FFBA071BA99 +CF70D7576DC18B96FEAF8070BF25F3A3141F873241EF4A07F332306B56F1D5909A4F233A9DB3A08E +E43DF38DD6DB2D6DAB908967A907303EE1FA04F048FA6EBC531738C170B8B0F095FF3B05D14C2BDC +272F7EDA8926A77D9CDA49A90AE1387A51A24ECDB2E4E287B0F95A83AD2EC0310F9B6F396AC10479 +835035FD5D4C84D91917FE8A8755C976504AB1A830ED516B5F325EA4ADFD115900D23039A2BC84EE +D21CC21E2BBE29A5E0CF28BE047CBD515DF7785F37DDD4474042B102A1F28193BB8567FF6FDEF811 +25CE9A504BE5011C0010DCEBCF321824C9DA249D8DB11F79298F7B674CEDB6F33C111F8B0115E407 +99E0FE1B6CE9F6B2A3EED1827A9CEB453D643FE5CE2540DCBCAF7B2EA2C8F0AE9434D4BAAEAB3488 +FEC7541D57179BDEAA0BB6145EA783E73E70E0AA71A4FA58E81EB510E9FD2CF4ACFBF28E48CA313C +CF5ED2BE032B7C9A07ABBEC9CCD8D5AC9F775D36BECC252B9FE01B6AA79A70E22478904DADDA08BB +7CA3B66F3A83AEEBA37406127353790348AE5FBD144ABD8E1B32D1BCC70E2BC22A63E854D38317E6 +BB97C52A6C9B0C6AB5C336CE2D417A714825DCD237F7486C33925A995CD8AD3B359176C4CA775FE2 +2C6F8A7C7343F31F39D1B9A5A744973BF65D655DDB59E3495B28DBE2A877EBB32A22A4AB8EB13C67 +02B239F6932E2DC8B4B88915923B1C2AFF2F876A31F765B91747D5A858BD632B0BE4F135AC484016 +AE1BC01E44B6653A58EE1380B6DF24AEB73220A36FA8FDE9A152C16E049D2F247A9AA71CD2DF3D9E +653054FAF518BBC1EEB43200DB0BACA93FEA9445FA5F32A99926C4F1F2370B2E3E03CEFBEECE5D5C +DE299FE1641A9CE0C90E42E7C465DF8C19C153EA76C11791F8E112853E708CD0F6EFC22E44808A44 +3686442645D643E7118D128BF34188FD313E53B8E951E605E96825738C4DC893D942C145C3E1153F +CDED16D1EE10374626F45781461FFC94C77D431BCF167FD29136A0B369631E139A59B06AC5281B3D +52470B38237B469C0E49CBE74C82A940F8AAD10E05C5DD2A8F3218C4BE40DCED0982770869190845 +D2E8BA2A1A2F9CF16DDDE418B6E2013C3258FBE9AFCDACCD57B01C4FEF3526C06FD5BAB6F195C557 +23A96FA3E5CDB2ADC9AA8989DF78F163F19E26F3B9EAF60D44B745FCA49B09D9CE5CC16D1E2C78C5 +A28F14D3374956E9721C0542A01A731A3D5A771C7B51FB72960BB22C99BC8F216530AA86C1A0346B +3D986EF1DF68BCC46EC4C809C6D54FB9C50F225ABA2C96965F4DE77E2C5D131318231C6773F0976C +DBD016B803C38B8923822BDF160FB87BBE744D0953EDEBDE87394282B9950C89F58824D731355E8F +A3CE364F742E3337A983BD1219CE8787CFA6925B560001F6F78343647821D408B60965966C16C454 +394D33268483F51984614FD9964CCE5F1AA4AB12144874A72D3FE97F4416ABE4213B4EDCA9ECF73A +937B562F978E059705E561A6D0C8B89B59B4CAB2248BFC4A6D3D98F89FF38A4D1C3E5A4570E2E2E8 +D328772E11DEA2116C260B13328045139A819F5489A2C302F311898230CD26DD03E6CE3BE75EDB86 +0D982FBC8E1E24D1F83E8CA64174495B2F32BDF0505FC96E9C65FDB0EB0C4ADA410F0A1C4BB4D551 +93B1AA5EA1F988882A56413F77CF24FF6516CD5095077BA566116797895FD7EA616D10510957025A +1DA05064FD9176303A248C94CE2A9875C03C7D8755A1A8010B712B64BAD73BEA4195D51A328F076D +12C0E52E87D98A5269D157D544CD7B4E1CAAEA6BDD59F08D0C5FBB84A6B099BECF8BEB721A874BAA +1BD1F8CDB5ED5CD3B49A763EAA36D33837667A6643B83537EF5515DBF7659E28692E0ACEB48FD051 +45534A443092E0A510B05B5B51B402CB3372A4E8BAF98A7FEB6D8BEF6B364A5EA0F3FDF301A44EE2 +3485D4493E7B4C33E0E352202931188D156983C40F7A4B615B6F5281B33FB32A2E6A0F7AE84BEA2C +5C3635D7DA17371D608847EB402270B83293EC09E4B1D85996C1CC81196BE590EC9EF0F258364746 +BC4763976FDDB3707D37AFCBDC0F4EB2B1502F137EBBB1541B992EAD43B5D64CCDF505FF2F127115 +4857C69B689A11760979F6D2BB032CF0DCCBB33D2B6F6D6BB29A1D0E371AA5869A408225C0AFF523 +CEFB08A7D3D17DF9064010363196CC569C436E720B1B4573CDAE1CD93A10FD2D4ACB14E47046B5B7 +66B75B40BA97027FEC4218980B002FAB60A9F1F5A37861B9A63F696732F8127B2C6F036BF32311B8 +FF08A489E88218092D57A99C8981EF8CBBD09DF49CC836B9C1968692D1FB551F47619F020289F1A3 +D3898A96DC1C7D39A21B49C2D0DD9508CB8A8BD6A5EB40FF86F555FA32017B67AEC07BECD659E8C4 +8DD1D43E8D1EE08A117479015F78BF20D3318224572D9C90884856E4307A8AFFC83EDD317B008484 +3BBE8EB2A4E2D70077A639FE3637C3DCF87C67F1BE62E63CC67BCEF8EBD07E030524A53DD440F2A1 +3A019B7AA89E155AAD1497974258A01DA45DE1D94BB9F925290FE9BDDA29EA3F2BF1E64DF7EBCFC4 +23AB2C7310D9D87A5CA673EE95189135E1B134B431B231428FF2BF64C8F155CBDDAD17BCB524CF7E +ABD66B75705BFFB1DDB27B72D681D7AA19FAD0FF23EEF028B606DD20D58588AACB299A3CF38372A8 +E7494A65227C1E4A231AC7843962B8A4A7BDD90F3F8B83277E696F3514139BB8DD207641B62286F0 +0E32C7FAD7029DC0E092564C7CE1BC5240FCBFB00A06F075D9536F0326CFFBA958BA7A1A11AA047B +B14E7DE16761BB0FEF19ABE85154359807C339961B9695CDED59E489CA4D9BB75D86D9EDDF0502BC +0B4EC36B1D71FCD4E03881ECEC703E5DA23B3F5DB98CB8DAED81D5BA20B844A92A4B92FE5B082952 +6837237C7F994786878404BE744D0953C676E52CB05FCE193E8827F977B31929E36E320E770A6F1E +972B84AFA21E01719759DF0132C5CF9692BAA487E86E8453C09FF97642600D1024ED5D6C7D5B387D +CB5E6147D20B4A14D7D485B5747D765C5A1CA40B9416DC4EF5DC08F43F0706B27B1C90E901556E1C +EFF304EA8DF8D727E4B7E7CEAD14E4FC7E76002DBC37B92BD0E58AF29DA7DA91E6F99DADF1A9CBDD +04648E2F5B811F2AF0729646B17D32D7EF25AD47E267EE53A61A7CD744C9ABFDB1EDB71C0705412B +15769188CA1601AF0395796FAC0E2139EF90FAA65D27AAEEEE319D2831A56CE82203523097D0574D +22742D17E519D95560B8445B5B29033BF3454B94F20C23EBE8B90DDF26D903F1D66CB39D3A64579D +39D136C537CCD9FF2D6ACE5F79DE696C7330C0C4EA179D7911B7B67858D86CEE0833AB1E105B1993 +2A9BD572C41E78FB4A2A2421514DC2066E2F56101A9027F4BBA5D48E5DA9218E81CE46B95B50380F +560C67A5D547A8D32610AECECBB6F5A09DF44994F6DAC64F7399C07E182033BC1B9A32B69C41FDFC +7E1DCDDF273F045F154D727AFEE3CDB816CF2ECDB6733C579276353DD59C9D2AFA455935C1FCD0AB +7D57F9DD79FBCC7A45E5E5A501FF17EE1C5FF0823C6FDE29C60F85F6786940D8E3540E891B5BF7F5 +D36C57AC3AD359BFAB12B215E7FC94B933386C250E6B236506FA3D151ABAD893A817772071C09144 +6E5FB23A17591A4CECAA46DD46E3C41B4578F21D8894A59B72FAF6F88EE6E514FBD2FE69447D2B59 +9A6AA8BC5C1FD5C25E50BFB5CE5DBF5AD5771BC42FCC3706B6E9F7E4FAAFF2E63ED1684C5A4C136D +609D03E31EBCF31E864AAA74D8DDBCA52F85CCF14AB739CC55617EFC964D6CC6988AA45245B19CE2 +B63CB865DF1F1DA4A200A4A043C5CB706CD303EB31C32866ED92077AB11BF136D158840EAC67E7A1 +1BC2BFDCD5894AF735D677E1AC98BF3F19F69AF75355F168632037F6EDEBF61BE5854057AD05972C +7DA8D37AE65D35738547A9D835C6351D8D732F8C0DC49D7605F00A6A6045559D3A0B0CC21DFDD75E +2FCF25522001741EBBEB5CC97DDBD9DDCE490FE3CB186C101940DD02CACB685ECCB8C1DEDCDD4156 +F5F9F6B3CA53EC6E7E8A2F8159451CD5479D91BFBF91C6B32A1E0053017369330EAD2DDE323BCAC5 +EEC91A595473F447355B1BDFB873D0B538BF54AFB8EAADE5309E1B74283218F59272E59619D66405 +E74B5A9D6323CB76AF00FB27FD984F740601248C7206F59EF7FF4E95AF95327D12C47D2D34CBFF33 +29F28236831D7F0FD9D633B940D8282E1F1D5D95616CD839C6B41A7195A22B7A320864E48943CE99 +C68E3591A97689986A6536C3A1C37DA9838FF71A8C1A03094537C95D3585DF5AD9292874D8D05720 +030133B02E2754DA24712B52D6D8871422C709532B09D147EC8ACD861FA5392A82A6C2981347E327 +1986017B8315822B5FCB19A1B31BF6909E0D6490EC823434BFCE031045D20FFC675A73EBD7A39A50 +44C3B40906186CCF6E8502CD7915F053BC8CF6BE9FDD9A0330AE943D5C9A11D60D43F5BBE8A045EF +CDB7D19C0053F4F082303442C35C432E0DA4408C5917D24A6658DB807BD754AF140CE84A34F79851 +9323060D60F4EAC0E82D3E17042BB6729C69A8B8774904C6214C2EB016C528DC1428DB41075AA6C5 +4E4D849901838C6B6DADF7B67CD0CBC6EE1B415B44654D89397274D4A6AD2BA69DD81567F5B802F2 +684DD3516ECA0F6D66046EDA0B2B38F420A238D67D98676925ECBE6711D64DAE5DBE8AC5473A2EE9 +97AE65F0715A2E5DB2C75F20D9939EF58692EDA3AEA81F25AEC888327CFA6CC13D496714E63A1A87 +11FC50250D6D23FC7A8017038281F659A685ED7F1BB1ADBF8833ABC6DBEC0D96D08334E58C67E0F9 +0046132F0D4FBCB9CDF52EE74561498B42701AB826A6DD77F46C14753748E1EC66F4BD3583FCB4F1 +DC91050CF18B0D51BC366549261282779FC9694A7B987973C0D59F65CFF3CDB88D23952E46F5EEC1 +BDA0DC354188C11B0FA191F5C11A45BB9093701B33B8E7AC1A8621793D5326E92CDD3A76FB9B67D6 +860567B9CEE7B9F9056C6E9971F642DC0BCC1D2882B5BDF1B1CDCAA3FC61775A74E70CDFC128DE0F +9606091BB8E53905108EE77A1D3C506550FCFCAE454B020379BE3A7D6C62D162697AF1383F7BC8F2 +34FD616324C1E56E3E440277862CAB2C02395D9937816117F71A6828344182E6B5AF2799E29A6611 +9C0543E135300E44746EF2EBA57C4EABB1A15D8AC6D037F4BA2BE1EB4D1A7496F9711FC67E56D4D0 +FDA4D810B5383A72ACA15872DE9F3A9890E33759CE4DA493691BCA47A92E42D950DF588C3574D6FC +72B5AF7DDE915E3F5925B3E97DEBE6A4208CE7337754045607679296C4EEEA3A3EF1915E85EB1A32 +F1BBADB2904B09806631E20BBF5E57AF11BC784C75BF049B4BC7E479F1D4AE7754CBED7B11ED80A5 +2DD0006FAE5CC23A7C234CF628B42F4104A490E7D59E8B1F2A1395D37641C478FF8CB9FB602B29FD +3E48D6087CAEE48B523887F27F69DB32BF80B760B030A6766F8F9B19DE70704DAF81D3DED2BF663D +89B5BD8AF253BB8FA59DF84A52FDED83D2737532B6D66AFB9EF0446ACD44BFAB797AB9FDB47F2E18 +8F0A55887853772EBFD029B5FA0AFBAF10A88D464BD6F634C5839B230D583E36671A51DDB1EBF471 +8ABB84D057729D514751B0EEF4F756B91DEDAD7D9AD529B3886008F1F32645F3A205F304B2A8D808 +D37A38E389B8587E8D3654DC374568FCEBBA160395BE1D132B1ACB434164525FBF250A5AA30F3520 +F0F2A75F9D1B7C6EAB0223C209A120D894D6ECA336B57B7F2AB0E2A94E6616D7539010C700311966 +7B4A9EB3A812FEF4D100AB4C036401D21DDF8AEB393074638D154418D3A7AE51CD1F0C2D5CF4B475 +3B582D5071F91E6D3AFBFB09EAABBEAB2A60A5D388D16232939B5483CF7D5C1C9D05DDC85B266F39 +6F61E179AB9FAB644040014293EB53F2B50E8751F9D92D1DAE8DC89594905A39357C0098265FBD24 +E407F9A22027E36D5608FAF15BD9E354228E3BA943EC30301ABB2CB105F3B6D57C001EBF8328A8CA +318A7B1B999AE8BF9E2FD530B669640116149B622EB3C0A8CCDE62449DE9D39E0F5E5E01CBBF4F5E +52B33A2BD60B52FA4A5CE98B7313FE4C3FA30FA07DE4516109C3EAEE97ABE430C505647DD8B3DBF2 +BB4B3A806B14A9E0F629863F15D12A1CA62E8E055FA6ACABDE1926D3231CAC271C30A3F7AAC6084D +D88628B943052B618E4E39148600AC1EDB57B0835C291F73D29C51FCA3C5EFB1DB18A5CA47433B45 +C57EB3CB28AEBC68E4171E6DE357793B0FD2A1F4E76180248017112674DAD7ACA6ECAAF209CA174A +5603CEA5CE528F20919443900636E2FC79C303EA7B467A3F001EA8CB849BCF28BF40A3364C08ABC9 +B5D935CFEDA827A8C3FE2F6ABA54017D2AD0393A05AE21F7AE1F58AE1E992B5C889219DA157FA7EE +92E291DE2F9DFC244F2CF0FDCEFCACC487F0EA830985B687556D5AF53B81814DE9CE0C6C430DCBCE +51EBC6091B4E0227732E07DF20D59E9EED4D8A83761CED84CCE244BFD6A39103A2B48A684AEC9210 +5C94495D54FD6690AF857E5A3915E6A15689F1816F75F1FC8D5769B240639B339EBE54BC6D84794D +50F4EBE892530351984C6F8BEBE097CD46F4FED7D46E32830A16F77C13F13D17003737482F02D1B6 +F04C57B4C2B1929AA315B4BE1C7C9CB55F8981508546B67E4EBF84B6026C355C5E4E07CD98C95F07 +56F6643FB1DD7D8C77C4AF4C4F23602DD3F61D1C46583071AC460E74084F7F7CF812BC53975CAAF8 +B3C1160B5D6864AF08A1733FA893CE4248C8F8B46AEFCCF317DC726BC1E5F2445E462E0591BEAAEA +49AD8E4E2D3CF07F900EC46D596E9CDB3A8710A0B93CE5DA9D35E612596A6374F35AED0EF55DC46A +8E14A91163B87417259DE926BBC3FC5423FF0AE2AA6D740BFFD26981A57C8C1D97FB04A90A567296 +B07437F94C8FFF4709213DD5D8862A777CF3F97723F43A4F913F4A30F7554ACDAE34713654E21731 +C594C3718C0547FCDAF7BB1620B2D6BB19E65596A585290CC43F50B34A2FE6EB1E495ACFFB16DFEE +8784B66FCB9359600412969E2BDA330C421C68345A797C49728450A6CF41C926AE8EBBE80BD97863 +593C3E8AB5415A8BA625957F242378948F5C7EA9C2641312E8E46955FE5C1A1824C71D3B0C9FD211 +7CC965DA7C21D257587F291AB7C594459A11B977A278C74CF5E6598B0C75ABBB2FC1B3D167D7E31D +B519F4A0BDA650A3FE7F1A281DB9B69B57A23656BD820B22D0E30B07361FE7C90933E24A32B6DE2D +F240635D543315226E32487F264AFE83EFEAC74117321A82A92F4FC7B259F26DBE74C966B4CB3F4E +3499E30B9B326F72D73919D9FA9024AAC0B95B6751AD4CE569CC3DDFC399428DF1298FB1733FFCE6 +240FB3BE3A2837E1A66E08B784CDD131B5A61423508000785CDC5610CE4DA1DD314131767C8857E3 +3D9741EF3FD7B8D0AF0371CFFA3DCF74FF2D3B421AC0339EBC05FB4B61A2F46D6BD1144747AD148B +7968D7DF4D0F742AB39B0A04A88D66F5CF9876970879879872BFDA0C56C253DE5C72016B4990CEBB +2455DCDEC7465EE7C7E1C557B570E9F3EF3E31A22DC8AB9B17F34F48E135BE7820ACE383DB7C5B05 +8A9DC855C6850E49AB7D7D00ED2D23C50AEE671B11752F0A682EFE179CECBFAB47B76676AC0E8FD1 +0A47353D3AC3477A59B0F3CAF22849DE97AAC8B79935F7C57F3052DE7E13BA0FE7CEC4685C86E841 +EA8C7D6734D0FEEFF538CC2AA1414EC9126637E169FBE4ECAFDFA29A08451B25954F0094710195E1 +69E0A6004419E432E9319BE2AEC8D93407B25B758336946C6E30032657DD857BE9C0A05F487423D2 +0C2772D4416D27FEB5CCC971DDEDFE9B8C2D7DF9DEC90D0868D5DD18850BE567A04D08310883D9B2 +D50B817D0690F8550E238C1139C0A117B48199B1B4D489C9E52C58E0CA75E6C386ADD447D8AE52D1 +D979FD52A50D82BBCB8867B5D49C19BDEC414929DB67A00AF7C6482A87E46BD11D8E4E52783E5845 +FB2CC7439F99FF6552C7288354B436B1C361AB8C2932F77B14A50A7407FC0BCC29662D710248CA46 +AC42A03FBBEF9C972783942F3721BD32BDA3273D1E353D9E013D4CFF630BFE4E7C2963FECFE350A2 +860421D17D6ACA888FA26403726A0E16BD10638D8511A2C8A36C99E9B72A422038E1149BF88A7CA1 +B2DB196333F3AD32D6FE28F18FE1ADA06FD25E4597936A924E71C188B9E8779BDBA928A182042C96 +F6A14A6FAB5C1A819DB8E9374E32F77393734A65606809E90D188F749180A3E6CA7AD8044E168677 +15FDFF350D70B14454B692715DC3AE2CAA561FB953B478E873EB19C41A8D83288451E4E910D5988F +33D83C5974DD0EE4DF2E6C849C500D0714038ECB1E9336D71D852889F2FBCA6769949699BE6BBF19 +A9318CCD7C845D0EC7FF3CFD871328CF7B45E6BBBBD16094E8ABE4D3789DEAD2C930AC8D810C911C +03AF2BDA4EBA62810F19B4506592005ACFF16EB58E33D6A71EA4DAD28A2D7B10FF89ACAB4BCC0F96 +E622EBA20347AE04C62A298257D1623977D185BB46B42CCDD733F72B37072B8DFAA5D7FF77E35618 +3F6D25EE1D951F7EBFBEA8FA17A859B8545BDB212A6BFE9C3078C32124C4E5D4DA09404E40498F76 +7B7164C13E12BF006EE8DE408070296EF0A08AF759206DB3C445BF49EAB18ECDE1FEDEFFAB653FDC +B13FA4649B35D12266FD42D0498331560E96F54D4238678F70233F56097E1944FC671D6BB1AB66CD +E0E9DC24349E44D67C36B3E3A00B07755749A597DF31C25F28D55779841BD3F6221BCDE389852DD4 +590E075C1298B36181D9F64BDCB54A556C05A9EF661EA1CC7C7549A3E1CCF79B87A6E71C3ACDECC9 +C4EFB05B643744407029258B8225DBF960DE529EEC262D1D3113F6CDDBCF4BDAB706BF085C0FF2EE +E8420CF755E46B053B774DF37C5683F8432EEC183C96176BFB24B3F478FACACBF1FCB73D83D4D857 +2D0F15783C8AE95D7CE959D794FDE1D973867D8F30A301598BDB7F2824B2629D64B88F0FF4498B6F +3728CF8916EA884C5930677E7481F90C85ED41DD28AA00E714D3A4F2CC136E417A4C591C7944C409 +70D2BCBE410A940937C3CAA118FA32317444B401968B8ECB2F0B3C8DAF6D4886C2015000A71FDAD4 +066B82312A4CD1E49A9ACFA05C3E7CA5A5CB3FA713CA0AD9E66A34730A36612C72D1F803D4CB1501 +9184FA2FDB3E5D067BC64B29299D5531565264B70FFFF86F5A5B03848E55D73337650208D21F35BB +D5C14748CBE17EB3A7E02BE53136DC69E8740C597CE28472CAEEB96EF2A5752CF17CFBB82F6C104F +2BBB50C216C49E8AB7E661207E1742B35392752051A1E409BEDCDA2654CB5155B770C8C5E4A2968A +A35CF1924D0C74A8D23AB811B7DCE60F1EBC1494A295C8D670E84B7B886A6064151065BD96F2D364 +7BA049A444CF16EB6432CAFCC70FF2E8B354F55A192C94BF08D156856A6512403F5140DF4C8D254E +DA59B2B3ADEE19A35E9A61202B711193A7E7BA8EF427152157DA5A51084EA46510864E1CD7B4FD11 +16E74D7BA769ABCFAC556BBA7CC528C18003A2AE059CC97C18263500667F6A9A8C421F2ABDD73EAD +82D9D08940DEE1688D4AA200ED80C3AFEF6A4979154D99478D4F4EB3738333E8581A755190D87BE3 +09A319ED170A107512F056E4C619D4BB1615BA6C0680117610A26462F267B76F9DBC9A7A3AC08C9A +512A44697082A2718B62FD1D565010AC96E1006F958070AB7567A33E1FF7BD450681DF6BD4EBD265 +CF12726A7EFDEFBB9BA1E596BC5C2940A4FC9DE60116C9082138F1C672977F4AA6B7986ADABBB2B0 +651A7B0C06C4BD405C58B8C81BE075997E118E10FC008AD1F9ACF9F6AAC6C9E3F6DC7FCB838720E8 +D0D9BB99F203EEA2566F18232711E832810F10DD4E2DE44A0A81603EB1162E1BDB60AA1E2D771EC2 +E6E2C3B39508E5CA03A1E2A7F2860BC6F0B57240880DF54C325F4729EEFA1D2753D57F055CDFCA5C +E9C6630980C7121FC21E2F7223E6111C18FFDA0A0A7643A213DE9525AE138814C4183BF03A26A36F +EE9199821464C845A746945413629DC53F5A2B0D8CE9282B580ED662F2F07398D6A8B7093CFCC6F5 +E0F5A7926D2862AD1CCACB84D85902038551EE3EAED02AC7754E3B65818C530A0252C049A68862DC +A459DDD937F7BA64DB16AC86885B68AF46422D7C4923070E20CBAAC9F14E43979C5D5AC4C9321016 +7CCC52E7DA272146D68D8F61DB4D60063E74F6673B41ACB601DEEB1DF73517A8C5388F00E8A9D236 +9C85DBFE4C1E9864AB52E09F465EE98C194151E66CB98E6981EFFCADBC48532E9795290CF745FDA9 +CB7FD40BB77C148A8B90C0CA50176103E2ECCAA57030F7C0882F1E891F9EEBA077AA4992FAE38C75 +5470A4C35454EBAB45271DD76E4DBB2D9147817F7D3FB800EA136D3F27C84B4D45ACEAD13D3C91EE +BD6836AC04F95E578D1B7B8CE5B718E42FD1BBE91EF9A7873F0C6DC59AD231D08CEB4AE312F83F1A +9310155D2C4F216A6FC72385C899B5390EBADE7CF7BEB028F73DD76EDEEF639E9EDE034ACB25BA57 +8C7BEC3581FEE0B221F7340570489596FC60EC805405E0D2ACF0687A62A75358B3878972D4C039D9 +07D868DD00C5F7F3342384C09B01F1926F7D37C2B862FC516361253CBBDAB354898B31B1FE58F773 +61517F2C2E106708AB659D95CE3E9E7777B7327DE01AE2D1E0C84B9EE3887C094C149700CB095D5D +A5FEAF1AA9027AF257576A7370D79FF4DB98511AA27C5C083FA0CA60A3A5E66B8DA9A22FE5DD3DDF +C529BEA71E83881D8B010D140AD686DBEC9AF4B331086F6556B695CAB14BF24B5FE64382E6F9BC21 +5E9EC40C0914B1E27BC25F43E6F555B551726A8D9CD590D5AD3732777EF3B00CBAA89F8E8E0E0347 +7D45B00181A7B196FD4919F110C0F2C0F1B41231051AB5BC8F8025ED00C10F2C2F51E97B622A4E84 +E6AADA59F896F83EFADE779B37FACC56BDCA1892A3BD965B65D51B55AC1D73ABCD16D0EADE55C0BD +3C1BE9FDB323E9FBC9835C63F757051E29E773D3491706DEEBAA0F1182F0E02D0CB701661B30770D +94E240E1358195781006E18CBFC2D83F229A89C3066E35CAE1384A0266D5A37824A089E855F11209 +9F723AF03BC1C8B1C0BCFFDEBE3EF73A40BF83F5E038B63267DE5413B88D30155E62EDCFA35C0047 +0178E5558CDA2B30C4EE2A9854C93E0E484D4363E3614E5BE829FAEAE51935386D20DBFC00B42952 +7F56FB045EC4D97B3D649415045337AF44BCF4AD9B9F9BF3EA72151DB616FF8F6B13EF66516D9640 +67460FF123C7EA525A97F1D04BDE9D3D495602620659F6E5DCF1AFC5360D1C371BDF9984C4A7B462 +180A3CAA7098E0FB0BDCE694806BA466883BD28D77DB4CFB6635BB7DB45B4D83AAD4260A4CA0D411 +0E251AE7476A95327BD6AC1AC88F85CCB705FBD09993B9E2990D51C37F1110F78B887C54E4EFDA80 +4ADAE5D81477913B6938FE1B39913C6582021A1ACA834500D9D75C9942CE2375D0A2A73805751EC0 +970D6FA62D4354337A43D85DEA6C6F3334F40221FC473DD79344D028FAC689645963B371A55CDA92 +F6BC989F4F1880AC11D8A056DF7A0EE60471800126526488B95B785698D2AC488CC027D31190ECE2 +54F80757DC9B4FF18171409C457F5FC58DD1D43E8D1EE08A6AA2F374B7C245B7A21287DC333BCB1E +EB498A2BD723EE6BB30B76E574773F70A61F1E163A25941531C049ADEDDB59AE75B7A6725D997752 +10ED356DD93D0F0AD7EE6495F07869C570125125BC84946F6AA1152CA18FCAD3B68004A7F7AFC6E0 +9EE6E1B365A4DA15DA95AB980A566DEC7394F385FE041B3B7F46D62424F4726B55DCB4BD3625CA68 +1221CE01DAE78E24F8045EF21F85C13D27D1F0F1B589F936F62F699B93EF05930C6F5730B0AFDB7A +7A829F2ECBF0DD196ED66C7E4E4E7E05B565BB98385F017D01B1305856DB747C3634BF05DAB08836 +029674ED3A4053CC3DC696F758F86970F94034B4F0DFEAA4DBDE449800DB040B55E7FC83E6B5584F +A00F7C70ED18524037CCB48A22A5A69506A825DED81D98FE93177DEEFD42B485013D4C4184CD690D +714530B56E355FB01BC92DD90A7AE8095748A872F79121A28E965E6149F37C9D5F4DF64574F605E1 +B929174AE0CF533F77EBA0E10862BBAC46BEBF583B951BD0BFC5128B08CD998DE897033E7CA8B198 +625D865701394543845CDB7A45BF82DD8B6319658E28B34FD319E38E650869742BD2672623ED9810 +8DF14CE5C911AE0AF2035B4F9CC0D1D84176CF3AEBC4834E8BBF07B7720521C4E6C081A962FE29E0 +700C4F4ECFE92C39BEDD64C3DDF52959A4102CC242C35F56139643F22613D675F970CFDF8C6A57BE +9D168C9CDF93915F66A5CB37DDB9B8B729F8131391F9D6EADC8BDD5C13D22A0EF967622F3F7C08DC +C422E3D41B3BDA6B4520C98FD08262303E042DF37B9911C1447F3DC8A854973323E1C380A02DACDF +8A53A0D1EDE9BF34A50E8B76C5AD5B15F8E25B2783BCF4B1247309E9B70CC951CF3D98C1A25E9CB7 +11235352F3BA91FABA828F2D7D91F0FFC50852860C531C20A5FAAFBCE1197CA367F0F84DEB86A8FF +A9FF4C252EB2633AA2BDAB30F2094721F482CF926DA3299452177B79251B311AA60D4CC82F5A9F50 +E942703877AF1C10CD92DCFD16CF19BC7314FDA5A85284BDE964DE2BEE782F4D52D17FD2084E0A95 +59EBD5AADCC74A6DE64C1F799385F5EC2E2F5F869F78F7289A26B03A9FD906934C3A6BA4A7B36E7C +3B77A7581BE9CD62B49C34572A870053CBA85DCDB9FDDE407B29CB3D02AD1C752B54DBB995DF0F8F +CB117CF8500B97849275A4625EF9937AFD7C8751B9B021E778F7DE9A9B191BFC470823FB8EA919BA +DB8B68755DD83C6905B118FA18FAAE216E2494FDEE9C1125C3941092C592DEC7A5B0C763E7E0D3CF +DA02AF9FFCD234A01D75C661603820C37E9A681356A6DB424F5F991FACCFF78EAE3518C0747C35E0 +8EDEA2E108CBBFFA0B2D3BFD612B5743AC95CC4A0580A6739BE4EDE6CB1E8B2F4CB5C6FA4572329A +06080E0085748067462F3EAEBCAD734DDA18BF206EAEFE9F882807694B80A63AF2F40625E98DF55F +BE08AEEEC2C1BFBC16F1BB4D391B17122EFB5FB539005A613EF5C9F154BD50F05862F813F2083CEA +149FEDC651191259BA4FAA658A42AF151B03A7B553AA79726A44AF9175A724E0D65CE84F41F3B7B0 +E0B684656EA56B4E7E654946AEFABDABCC4F3876B3C3A67373F4133FA8498DCFEBDC27476FBB28C4 +003FBFB063267FEAB2B2BB8DC647345803B2723DBA8905AB0741137C43768B434CE118954AE55DD6 +61AAA1BB60B87ADE97D4197211A0C70CDD7855783B7C570FD742AE5A5940A3D90F9EFF687C4F1E4A +D3C4F5C3B9FF20B61C0076B9FF060EB99860F31C41B5AEC0D2C4DE626C6059678DFA35BAC5F2F171 +A3BD00065AB2007EABA499E4E7F30EB994F8BA757FF9BB0C9A2E3E384BC1DD4B62036E2D70819AD0 +A535816761800CFEA50681AFBF0057E69A0CDBB4BAAFB8B15F7727BE1169BDD4FAF692F9CEC12893 +E4E4425DE4CB20F330951EB5A7FBB0FC10DE78A7F9B0EF6FA629CA13E8C2F616A1BD42B1E26E2A0B +533DEA701AB7BA93526E2439755FB9BD5CB076C3309E73807A5935AF6CDBBDABD5DD7B036324130B +8BC40163FA0999364553CFBE351509F8A5167F205E228ECD66EC9375D0E0713563AE75B6C23423AE +63EB67167F2F4512BEFFE96B90913E6920F1F8B7139F1CAC9E38482B6CD26576776A01103FDEB164 +A176023370D08906E2EF27E1E8B6F6C27EC67A86EA36A6C82B681385F3A60AD53A8512E0880D7ACB +5567F2654731CCC1796C47E18DD6CCE30F65E27DDC5A3E56BFA0460DFC3F9FF1E98B7BDA9DDCC615 +718D7C6CD8DC1270E70FDD4973B287396E2B85ADFCC83C71DBEBB97346E858CFDA78713C0EDEFEF6 +B84984D719C4729C0A3F2A7422DFFBB2AA5FE61891D3D16CDC1BA4A84E7A74B0076FEBE0C2C74F4B +B9314E5246D7E67DE23466D47C8AA93AC4859B347D8CE31FCFB4D41137B51C41BF19D626A59D0999 +FF2A4FA5FE6FA60E029494EF99C8B00700E6E752F4F9ED52F2AF5845563ED8AA5D4E19F82DC0F87E +255ADA53AC62E3D7BC788EAA135431DFF49F2D3ACB61798353B27409D149FD635690F8AD324804DE +A99D12B02F15D9C6DAA01BE2C1512BB8DBE86EB31D7034866C10558C008D69DAD8830745F2BEFC2F +FCD957D0FEC30BFEC54F3C440F3A99BFDD7C6D0D657402A064F2656694E5F5A5524CF4A7A2AD4625 +5DE9D2E9916DB9DC2C39986A221C31F89A1884ADBF7DD62D4EBD47957E7A359F2ACFD38E073E8502 +5F907941ED233EE3582AA955CEF67A8ECE6D8B301EF37B7D40ED84FA9DD604C74C8E870F9C26A2D4 +DEC8F03563D29E1DFB974CA191D4696D877A468082951B02A88884B9B760961D9C37154F32D54512 +4F0E4357B68547CAE9CDB571089752D7881613E7FD8DAA8CFB98CA9E930B48B78AE13523E43A3568 +7B42DD2F0A99034ECA1DD782DA692EFF6AC99D6734DF1AED3616B198E6C242EA7A9954B7337ABA3D +13EBF06B95E16F19047AB0EDBAB6A8928D81003E593C1F134B0E2B8C250EA251B59CD04905F57016 +1662514225C393C42BCC172DD23D8871908522CFA5CE113EC05F39E4583EBDEB5DA425E4627A4A2B +D5C511F9C9C155BC81D0EFAFB0D0F2E96BD49A5C942933336EDF9AE0CDCBB159761DFC50F6180FB5 +024D2E5C2A20233AFF282FD3B1AAE9B65D2989BB8176AA8C7A1F58E43A9AF3A6D0168CADB6930706 +C4F974282D4A23F71B0A41C75086DC1C45CB98ED68ED0E4FC62807EDEF13C6C85741B11FA957D791 +D92B750F3B7BDFCA7E148149E55EDED66700483C4D5BFC3973580F7199FD99CE6B358B508FFF5DF1 +78A5E495977D851B0B06DC7F6B38388D5C94BC8934584D8EE2F4E0CCD3332A737BC066F042B14931 +57BE93622E346FC6B293B8DA0D3EED02508AD2183454FD4D5D21235268834B257EA8B06117F67589 +3E0505E64709FDE03F2D5C82B163C29629EEBF5D408547AC363758D8D134AD7B9A55AD9C7D90B67E +6DF3AAE6867759D2A75993265118BF6C5A261C6D80EF039A0163BCF8E6B40E643D1BF87BD2592BFD +6A41EFDF9CFC2E6DDCA83FEC28B5EEEA2E363C20AFB4A13B5EEB2CA3BAEB2CA6F4824AF1A59E4CBD +884CA4C62852A095E4B13CD018669AF3898DFC4840C874B97408C811E431163887E5BB8CEAC12FA4 +A1EC50E8305137437441AE0FDF8FA4DFFFC77C05FCCC41696E40889A02DC4B1267D5B892647D6AFB +BA549F737543652D3C3A051E36BDB692DD7EA0D8964EEC27BCAE8EF5FA853C54D67A8ABEF7B01FB5 +AA473DF48CFBD3D697C9B55C852773A35B9A7C97908DB701AB66DCFB274A824B60E275E1CB341147 +36B26E50EFB1DF01F01688E09E096533E95B3AF5D09D7823DED38487C58B4F10D6AC76EB48731CED +78AB098C452AC83CCEDFE4E8E4AEB4A93A715306A096F262BFDE5036F38A3B733B1A0134904A3EE0 +8A9F7C2723FB5D0535C5B57CB80C29E292A49AF936DAC66CDE5C01640490109E679FBDC13F892438 +D70CAFB12909FD2ABFEAB23ABF6D129F5628B36FA00548ACCC39C8312030DBB87364DA44FACF3818 +D4C8ACFE3302B1487D5CFED16E17B05CE9889219C13C9DEA28C9BAE5D788578C309CB7781244E30B +7DFFFAF5A9F594B8781F849EB20B1F3A346C2D697CFFEA6AB4134DD46C86BD0F89AB3EE9DBB2F87E +988D906C21A43E5ADE58BFE82D4D4009A39EA3D1E779FC374FF21B86BE48AA4A33186DFA0F97BBB3 +218CE963643EF2A35788D613DFF934139B3EEA36377E67A713D20BD3DF04720AB49834E3FCD78908 +1FB726CF642A5B87D5D23609661F7D694EA0256F3EA0DBAB7C2CF79CF89CA0FC9D25281EE0FC16B0 +D089DC5B156462343A375F1EA2682A53F70A1F239D58080F4F884DBFFA68CC6D63365A8CC308DC5C +BC2387BF3D3107C95FF4DDC3A902B31C3F414355748B10518EBE00C92682CFA413FD071A16B8D129 +4021B0ECC5025E33F6116C89C7B983C6BFC31C5C8D7FB5E5E81D3AC500123CC05B3C8DE01357E192 +0DCFD172EB4B488CEB9E1ED5FA1D235C96FAD22B319239FDBA08ECA2C5C1192B4D7A797ECE135228 +6BBF1E59AB3B54B8886E67A82AD971DFD1EB21CC5E3512CA922F9B870A48E6DC94F94181E422D274 +2D3A14FCB3939FC8C1D62CAF79033D6EF4DCC93751BDABE588BF5D97B52AFC5084C5BC17246FF977 +7AA4D738BB9B15E534ABFD68848B879A9840EEF4774734F0BACED5E7B6177DFD430E0497E36D1077 +7654F351348BCEAAA18C3B362B2791A006782C25C9D544CF1594EA623BB4C782C6AABCA95F9CBB1A +8C86318834E1430376406D2B6CD5AB09644361B83AFF66C96CF549C2D309F7439254C6C3A5B210E8 +23F83647FF420BE66901C317349C1B305014EE7E9F90DDA917E3F853F1A8AF3DF1528A81C50B76A0 +F02E933229C2743BFA639003025697612BFD8575DCEA0BA5FFF805EEB4D9FBFA8D2014BC239E9D5C +4C87E36D1C83E010B92F43C06733976BC84AAF1C05C0A0CF45CA7746ED7E1DF5A12F2401C0FFBEAB +EFA199A7299E4BE5089C2CD83E7838F163F6284FC299B213513F803E93ABD8D759595DBF513D68BF +96031B9FB95A945B7C9153B0B315436C850FA5F1415AA2C9565F6FA39E9F5C5FB265CEEEA8C98E4D +00A72CE7F9F6677DCA7E58C1A8C111A9C6C44781867AA5FC71F36486AEE73FB81C03BF4EC728E43D +75564244AACA3D66B6D36DFD38332AD05F150D4972FD475FD087E13C9312D5A17A83411B45740153 +81CB568CF85BD66428FF9EA2C07E7BF8D0AF4469AA367DAC0230650036240634AB81766E832EBA8A +2D8BFA402DA804D264757E74B3465EE21A1EA1C92929444DAB2EC83050AD169F257B77D3F4B9BA61 +B11361F5DA6DD2DD933E101B64F9DC82945A2D421807F09F3E587D4B13BE0FDD6D7133CE890C3AC7 +1D0880418880362E27635986795E2E8426A0A7D7E8E5C41317209D957B53B6CB9E4EE7C3EEAF3315 +E006B7FD90E7A58FAE5289AE513D7751201459BE029563B58D967AD24E90DE5E96357D37E86CAEB1 +6059CD8593F92617AF636C7D32E2B074A40B6A1C40828313C8DF1BCBB002DF276D424519EEE2F234 +FF9B9B27126996834BEFB6E05A7BFE958B4AFC810E8B77F0EDDCD43E81549154F81E282276A7133C +23650ACB159EFEA53ABABAE1C1CCEF5642898A5605A285205DB40DF6C768029D8CAF85C520AA66CE +5BF1E0A0520CC94917FCC3118953403A1B16312096DBD56883F03E78A14315A5F97345E13C363C06 +3A01D49492349D60A9114A77BB9FD48FC0C3AC76D190204DC69C3ED4A265B8148F2C5F2E147A504E +4F373637C065FB894446031F78C4BDEA68088681E0C5099CAB1D13833FA87AB96E511013A9B3D806 +E71EF6E0A1442C91FC2A1795A13145ACBBF5D18880695EF11832ACD328BDE6E0A7308B12759D12C8 +6B558CDA038590787704BC1BF49EE8C788C41594332624D56082ED8627EC110233CA328D2A0BEDAE +3511719EDAA726F338D324D1577593948A8B9F0300F27FD4420638C6972EEE2D6248B87643275DB7 +69F72E8F86803184035F6A539A7CDF43A79886ECF110ECD7053FC04EB5E51B3C7625B3BF95C0F5EC +044FB7226281BC723988AC2498ACC0489DF0BFD1DA82D04FE3ACF6B63EB269BA9489F8D5D07DA9A3 +AD04BA924B99B9C1EA64AFE7BB2886513EA6730462D4FB5DD82659C7DB7687F4CD8E006581A15EA7 +715E274C9B89F66F1ACE9C2AE7698FCF7479A04F2208DEBA6DE801A6D184A8A9AF6BC1B0E37CEE3B +323DC4EF93EAA8219F946DB9F4D9C133C6CE0FCC6884F9C2F3A816C4ABEF44DD6256E7BC4574600E +1D825080660BF6858B415648258399839118F3C11410C1C29B3C208A3B54AD5D7484708DCFBCA04C +849F2AAB79E4D96328D990C63DE05DC8E804DDAE255F94FC3D56270BAFF6F86190796F91BFA018C4 +FE4FAEC3F1ABE8ADE43D0DA18E710BC1F419F77DEBAACD3BE011BB93E111B18CCEDA8EB0352934F9 +690F3E73D71655191F150BC3788677D1FE46070FDB354BCCAC8C179009553A7D67C87518131A4D8B +4FFF85FB9485C9F30F4CD31EECBC4A44CC267F6C57AA05A11C6FCC09B5CCC83F189F6A32F8EA56F2 +2D20DA4D4008F08EFB1487675CCAE22BC9494441682F4E46839F0F4D2D16AD58AD0886C60C925DB9 +C7D9AA1A7FF41C94B6289E1B72382789006F40B99B78B05ED1FB1F715CE4C0A1078AAC02EFBE6306 +F53F5F7E73DAD249995DAEBF17E5F55082CC6885A54F93F1A935E0389FE54E8B1B6C5ED19D483620 +A697873D5F18CE9A48E3C2C1C871FD4739A78782D8112602DDF8D4FC497C459067A6B118AA998740 +6C8DE97C2F09CB9D388D341EAEC0A5BB4BBBED92BE59B273C77F3D6965418669BFCE0C43D5C86275 +D8E658BE1893DA8E698DC858CA459711969B2CBF4CE294071EA572496575CC35CAF57ED49C2FF1AC +CC21E19D189B7C2A1ABB1AEAD7185675413C224CC4C0E1AF4EB76BA9F44148A95D8609838B967784 +2391DECB30BB0FEB92CC890F224FD2E9E9D34466D6091443CB69E845D4419F9A04664706FC8D2D15 +9002422367F39CA1B1CB1A6C32A65F46230CA2376C3E5125CDFD367514E087E59873EEE569B7F376 +227DB126060F063662F118C7BD01946BD04172147B601BABA952A01E0AD31AB1147D48FC2C3F52BB +75D9618E6F03F1F1EA393AF0E8474025F451616C4ED236ED831E14F40BEF5B86806B73ED64AFE7B1 +2A3C1F5036BB9AE79862EDAF13BDFFF06C94939AFDA1A749364BEE73449520111CF56181527F9568 +F3189652FA7FFDF4BA1086DC5992C6E0282B6F88D7CB73F485F8A27A77453C151B0BA40E294653A9 +73298EBECE8132B440ED4B4437283356B79CADA8198512A45044A7AF04CD02CD4DF7F47D5D1E7FE9 +52C346B01D03D1F69904D1AB5E8C433B0615E88CF4D01B3C96361F82B5CF7CB4D92FF3971E44F0C3 +317D3C5B0BC8DF4BCD4DC63474D0E0B5BEFF3177E2722D4AD4AC4B4AB6269EE948BBAB6019ECF2F5 +846A3D215F6C0D999D489215D4328875DE21F2CA243CF184280B229ECA4B8A9C5290973503AE5883 +03C67DDECC577F12B41F0D4DA266772867F9D93E1863BF76C6AEA5DE3FC6567EECE93D96F717E39E +DF536ABDFEC14DF6748DC90A2CDF6066246DF69D2745D2944123AB3A6ADCFBE7C74EA8E8D712AE86 +F76B3059178E78FB2FBE8D1F25831C70F58FB6B5922B371A27501E7463E01C844A2226CBA263B570 +4E5C4D3E50FE31435437E1ED39E6E3BF47B4E2C4588274F044B3294E7B2BFE302E76EFA3CB74108D +A6CFBBBF383F5C456128ADB5E667E1F7ECB4C3E00AB8937769E5A2830520E9FC0A1DA1662F881ECC +DD7967647B5841D8FDE1DF7C9F5475523F236005EEC0DF307BFCEF379355C30A83EDC96D6086E224 +388DC7B5E951B819347AC5A1F9FBE7EF1907726EE7E972AA1DDECBF7F72658C20FDE99FEE686D7E5 +01A7759DEF55169938F34978A6BD4DB49E494883F67E868A9AB177FA8E6F81157A95A03B4D9DB572 +EC1CFF33B450BC13E00830BBA20AFF928CCE04B4F79F3795DB54A4A8B5A2F3CB323194990050CBBB +C7CD32103E0911160FF4DFF135A77DD0CC15867B994CC88E1EC10E3A097D329DBB90FBB62981FF61 +C2521F9AB4B9393C6764E5B4361D0FBB1938456CE437142F0AAD9341588BD15EA0F6EDDACF12A62E +C025F3294AB1AC45719C5EFEE94067390D579AED4D1D36041D358CF1A24446176DCC808CE2D6CE02 +7A2C2F6E517A5ADAC722EE94A1710BF61254DB4693B30225C12B9C4F856E1D24075327017D6B288B +B52EDE713B3710778A565EFF6C89656BE3C5F590F6ECA600390C1BFDE9B0EEBC2E4FBB9E0E2F405B +39738F7969F64E8228494B298C3FFF4C7DED00B0EEC336B7EFEAA892C4A80CE9ABAAB4318FB34348 +93AAE6A90FF00B892D1DEE254DACDB268A6308E91FB628A98989956958C9634896B878CF93F4E0AB +A0170C1B7BE2A0C4A0D514D7BBBC4CA114349D4D4985E96DB7E2ABB752C7828A9E36D9B0B4551DCD +D878C06C3C68D2C214EC8121F6675D8D03545606B582B09B76B6D8608DFCED5C4A721F7008FC2014 +1D877353E8BA5DEB1CB61F7C956D4A9F8CFFC8C4FF81B2660AE4BE45F7A63141BAEDEA05C43CAC2F +A04163ACECAEF90F61E0473E5CBF1F1994076B6A72CB5C33B17DD57E2632F7C6DEF7837F8A939055 +CF357795865D86C7745DE54C6079C791850B20C0C7349472FB6018521DD5924ED1505A1B8C8F9CF4 +C892CB40795C4ADBA3CC11C8A52A1DCFA8FD334D7F3C344CB4057E80A4B66AF6A97799F8DE817CDD +0202870BBF42E76C9BABD2D9B66D10F1A68388AF1A511887FFC50EC7D07581D4C4FC3F6C4F7EFCA9 +4799D74132B5EA25DF0C9557902C7EF1E04E612D9E40DBA459513E584C3A3EE5614ACEBA165E07A7 +CA3394969C2CF1FFF28B1C7DA85451270DF0FB71DE22A03FC2F17531FD59B12B55DEA7F5F56B0DF9 +34C96E26124342571BD04FAE6A12C6EB0E21F06275605FABE91C6EDCF55B298BC4CC52891DD90360 +AA5FDC150004BAE65225FFC42D13026F9C6FE343D7CE2F52229B4846F6E23BB2BBDB6DCF60F07A74 +8F19F74A1168DC5C67BEF840A3C68CA7A4D8CE7F94610F4CEE989459D0CDF1B194C63A2B82479746 +03A89150C4C6AC67AE3A1341F9516887C6BA254F81C5B552C527765A52ED5C4FC45D575F606E465F +7C2EDD2F5927319BE737D48099C333BBA84486F5F8CC0B32052DB6E57DC55A68019788DEABF8D649 +A531C1880D07E425D55D4DD4F3966B2FBD2A0B55E5C429051DCF0E3B9CB1DE6A5B3DA05DDBBDD3CC +1C81877AED2BD272FC7A10707C07DAA54FD6CD37E15C247E3100A1A0C527727B73D45C8E02798C44 +1864A2D1D30FAF94121F0CADA24753221C85DD5B43E5F00FCAF73A1E531D9453307581C6FA28BB5C +54D93F149D3871D2E017E6E7FFF7B0BEC71B83AD12AF353CD13311D3A6F16C51938986C9B6A24EAA +06B8BE6DF27A5090B3B120D1D1E064C6C1745536C6B0AE5C899C3DCA91BC38B7E900D9614F291B9D +BAA85AFBBBB57D58C3E1FA8713CCA1BF4EED469773EB4B9605125C08A8F7E998E37BD893F3533232 +ECDF47D26E8B2A0437220FAA760DC8E90FCBEF59AF6C1C55FE1A28A4C98E2A67DD5A7E55BD4FE272 +15533A56561F0D80989BFE15B321976CDEF26FD6530EF7A368A7239CC55D7AE2B8F0E980DF63CFE6 +F562F3ADD0AB906B60682BA447CE4A86E6D5538E13C6847791D8A16F5BA29E5840847A7E33AFF57E +BBEC1A5B329A461FD0E858DE5163A2120BA12839C3A216C44F364452A2D6401EB549791012C4B65F +4FEECA2B73B2D88CA49B44493802B01A23321470A2593A8F8ADE3F88D87247851561372137E11D10 +11A733C671C71D33EC939B05060C73697EE577A8F2BAE08309585887E5F314BCC642BA2715B51E0B +4D093F6B11CD37BD9728EE90A0C92D15BD1105637052F89B417F6F36340588601C9C2BE9526D01B2 +E88EEAFA300E38B0EB5E2B54341533B31DA1193588974DC054FFBDF374960D28F0C8C1AB8505CF5C +64988DB86E17213EF0D9D6D52ADB1BCEBCD02B5E16F0866D21D7C0FE108D551E695CEAFBB83AADC7 +362727C47BF24C482EAD6F122F1F35923DE5D6A248BB36433D044F73C944E6CE4D9FBDF0417ED53C +8F56B6D389519E7A539D6BE9444A4747957FEABCFFDCA5FA54BFF46F637B3A3299988929CDE008E9 +E3C4CCA97054A822C4AAD01ECF9861AF6238E6643358B0EA141B0E161C6ABCB45F38740B344D4112 +9D4E898DAEA8F2065263D68D97966FA24BF88E61BD86CF2C7BFD1F058FCF04AEB88F3A30C9D446C6 +3611112CCF30F163C103D6C7C5AF946ADD33B50A58FCD8EC612268F7E119BDF387104F22E4C2CAE2 +DAC407F206186F12D93BA87711FB05E6E96F17DE333305196FA7C33DC06255828744B590FA0E67CC +E4B0375276FC7AB9B324978B15FC228DAC17F955C7B3D441C540192145FBD002EE20FBDF6F397F95 +336C0A056609E28430E123A432EE91594E20A8D9E5774FE8768C84CC040A706D8D3590BBC9AFE373 +4A5536162846B6B7BB7B248924F1DAFD768B9546BD2A2CFA1203D6A3FF45C8EBDE2355F01744484C +DA9FC337CF7D9D54106407F54FEEA81B77BFEBEE088F344CA2E537644B615D8D6B6A79D03F6CBD9B +573FBA87EB00410B5251A29007CFB60E711F642C19847F58333B48F66B6758E4ABE524A4671B0635 +C491341B3CDEE650CB9F774A5B6FB92AA70FEC7D9A057084214D81EA5A36A7F8EBE9922A70F2B102 +121736E0BFB178A08ADC0A58E2A9DB347FB9B0BE707C038CC19A5519C3FA9AAEF660653086191E59 +320D0E696218CE1E8EA7DB5FB3318EEC98C130F3F1C45D0D2401223421FF99BD6E1546873C6F12C6 +F45D2E1C3EE41634F5A415FC8338A18EF2299EFCECEE00F9025E79610168548BF2A52623A479EEAE +CC55BE5172B0CF07F9B04E2B2AC08A2BC839DFBA9680F2BB2E438519BF3B434C71B4AD9E64262C76 +9F6C1AD174FCFF3F41B5BD7652AB296C543F323F0A16E88C6D1FD3025C33794551DCAED303B57A87 +CD1E7FA46E16ED357CE0FEBAAD2678A4D84E4D6B1635F412465C0F3E7246407BDDF934F0E2E0F5D3 +EFE318D7D63A7B6BD0CD5556B6DA811D521408EF336CCE2D2B6777AA472A9EE0DB9FE0D6914059FE +25EFF5D7E3D2A6EB96B23669118B9EFFEE5B7FB8F1EEEA355998BB48430156F14766FD77605BBF80 +CDEF19879E8F8C07B6998AF145B0AED86FA94A952F2F49D2891D41AB0184EFA8616B139E640A3A9B +D808BDF79E283B0CC4686935D0D96630D590A5F4A7C71C05E110BF3CDB5150D8CAC418AB25419BCF +DD5EF0A2015305561CA26494E267BA89892AD21E0BFF44D48E330694A1CE12183B9A7E4E25D78EA3 +498EDF9A22DD7718ABD06DE2C28D7762F609A1E9A5C40A878FB8AB33A60383152A119B9FA077B109 +90AF19C261CE43AFC116593C30BC27EE4D8CBCA0C0298BB84327FDBDD93F073B1E06F71933C0323A +6E7FD2AABA783E9BC995C4AED621434B82B34424B768B4427EE65228E612581B0B8A7AAC3149788D +FDD106A6AB93E01771802AD93B63EC386B057690E5C34A409421E532C6614EC61A0197C13EAEE438 +35ED9BB38CF811E39CF8F154684E3D8B12E3B673152B82DD9B15A2A68A6163D2CCD72D3117F7C24C +F1575671832ECC4AC6B912882C0231050D60ACDC7D6F36C6BB4E32B6019B32D1DA08C7ACFC1DF451 +3A338FACC16D297C56C6138FB02FC7FB5BF7B9DE4C61B63C6B37B0F9CB42E6FF86693BAE1CEBB60C +9C15CB6222F547E0D0776BC5545A73A2ED3C7799F0CA3C2D5EE17849DFB15C6ECF7C2846AD10870A +00E0810F35A57770EEE9D49D05B54500DF164A02FFC324CCFDB5F828AF7307DDFBBE647E98909C73 +A3CC6BFB360042823C678EE6ACA0D658C12776F2A573656073E4F40505F5CB4A3D340B034B0FBC29 +C3B6B055D23F0BD47E44B441D430B8703883AF8EBA79081D528AF5646340A27291472B8E1F19C8BB +B4AD17C7EA1FCFCC7B52E6EBAE0BC8204AA52C08B3A63B2F07FBFF20092139143A24130191C41D2D +69077D71FC204C5FD41275DCEBEF7E5701BFF6C0A4217F6F60C2E37697C7F1C35D2451B040BA0D28 +0C9D23AAEF592BFFB436165C314C3CB75223D15694B6EF312CBCE8035A1A9172365FCCD119CD5DCC +569B84C6BBC5AC9CAB6942096034523671156FAE2012F6A24A001DCB2F35A8A031A2366CA98F1E52 +944B58FBF1852710CEE0116BD2C7D68DB956B15FB6AAA147155E9F179E67357F231F8252D728AF12 +49DAFCF6AB4EBC8637E1BA10D27555D2FAAC9EEE5E51C8BBC793ECC6011C4FEDDF7116E719147927 +0BC11D5EDD9215A4E8087A6A16A591BF7ADFDB69C4A03361C0DD078017DC5851BBF60E06A86C6C0B +E08087C99F4F9002ED5206534913353AE16C4F358BC1564A442CAE506A107D1FBC0B9ED99AFC6633 +209BEDBBB681CCE475645E92155285C00FD6985216CCE60064946F94778F7AA85ED87F5762C20FE9 +DC007954281BD6FCA8554D2A0CA5B76A3ED42EE5F44F3B276E574F64B20E1AD489753903ECD20B4B +EC88ABD1E1CF5D06AA1815AB771E350EB6D04078EC04616B977CDA8CE88C483DDEE9F28D58366D3C +224C41D19E550B5ECB9775F569C2D391F61C4667A9BD11C69A88473AA7884B823F762195CC403823 +690A32087893C29C63100A5935842F6B612C95EC9B5F07459608786310C8AE65DBBDCFED21B43191 +874E20D08F12E1384DCE1A990CA5F07DE36BD012DC9EC558FE7CC44494F48CA3BCBF1F1F11BB98BA +EBBF8691F8590F84AD849923E656860EFC0EC27496FE6D6C185791E3261027DEEE4C57032547F94E +7D593F7885526AAF054BB850C02E863D831ECFEE61A781B89867139889A362F95F48A837ADB955C9 +939F609D2EBBD56775151D2EA4B09D38FC1D824A952EE7E52849260DE61F07333076CF887A3AC2F4 +CD088C29E47715C5242E2366CB493B2FEB38C19FC159EC50EAE88409CF0A30C0A6823C45D0532C2A +72E45A17916F6CEEB475C4D4A19372AE271326697CCCAFBB43D92C25A052797186FE8314FB41FD94 +2A4B24548866BAC19A83BCD2E6979FB3C7B70B075DCDAB3EF6B6181A00A98AE73B6D968EE2DDEF07 +503A92E4FE1E0B67A90A2F351D600DF960101A15808AA99B3C680A8F50D5CBBC96D98A3A2AD5F14C +43857F4CB9DF9372FE74B5D6CA9CD801667399E778B56EF702A56F0F393137B20BF11BCFF9DFC0F0 +4E67649D07A2A5F4C42C9A929231D5BACFB6B53210E9FE311642D8BAA7358E6A7370B6CE921765B3 +68A354B42C8877E59227146409DA83E407657BEB475329F228CDFDD11BC73123234649AEF0E2E9DA +76C12AF91713828368FB778905A6B7150258695D4D9DF6111E1B28A9462002D7C476FE44A9B13F32 +9FF84930D213787932A1BA01EA608ABE7054FFDAAE2176EB960005E5407D7C1D39AEF8EAD8683A50 +D93398C584312EE4A12C07E9D55AE9981D7EF57D66499CA93EA653EDDA1BECB494662E54CD7ED8A3 +0B2A8522CAB12B2751F7E9B3B66CA0B5C8905E0F3A51F68C96E9C02F10FE515FD6133D3ED298D15E +8B1649F3D13341BDADDA7FCB838720E8D0D9DD92D0A241A0CD8E25D7B313DDAA2F25B543BB0E7965 +402ED0F24AD146E49919ABD9604ECFEA5A7A49E58664DCEFDC893F5D722CF24A44D26369F5D86569 +9632141348086A80AE528EDAFFF9D9986D5665DBCD375DF221D5EF1757D79361E5A5BCBE333B6C9C +1CF46794A7C7C776477023BB298C970F6DFF9AE6C7ADDA8CEBE73B07117BFED6CED2A36548081C79 +BDA9A70C8FC5FA16D0EFD3E9F869DD5DBDF6E70CDA4217935FEFE577B64DA1DDBADB9A092B9D1E3C +E1FD3B6E0117DDED155313A5DB7D8742C3409FC18B9743F09ADEC49BFF2FDBBEF9D5FA7110266287 +7C65E3A1421BA56E258A49D76C436C97AE2116F772CA5EEA5726BF17AD5D5CE37DC5F45235730E90 +A1A1E3087132C820EB9E0E311500F2CA193C72E2ACCB4D77ADFA34E268B014FFAB5DD1BE7187251A +A69B7BB3A517796BACEFB9ACE0114FAAEFB0D9BCA028A52F5037291232A04C2353B9663BFCBEC2D9 +30CB007954B8C6396003214262AA9CA9FA876B4191313FD1831D664863C4A19946AEB8F4E21C468B +9C94B8CAA407A74AF418DFB60D46BEE5029C884950D3BE8A58023DA864E9AB34005337D335B0B35A +91DDEB53A54C3233150E27225B0800C841E489E9EA6C12F5D95112BB723C3E88AAEDA3A942D06242 +2CD80B04DDF024ED79224E166086C5770EB10C389D0BF0327B2F753358D9BD552C04EA52BA154EEE +5A84C51FBBFC79055AD0F1243E489F82FD3D1A3D2CCB24A02DB3F306767564A2451DA405BAFFED98 +20241A82C6F4F6E1D2A36FD954540EF3C091247120F5E1B362D1EBCBF80951A3158B1547E7A0CCFE +40C03F992BAD00496C32BBE9B0A06EE9554EA3FAA247A7399A37D05329CADB006F679B58F2E5969B +13A5516A9F6949505A64B7B3FE5E748B9B9662C05E6C5CB79D64FD72C54F557ADE72880B3F9D8718 +58D5EA3FEF2BD3FCFD670794AEA6144D13782BA89648C98E7CF5160089DB9798E49BE2DEBC2669C1 +0C42AE1AD7C1FF4921EF465300977E057C0AEE61579460F56E51B6EAFC1F7F41D669ADE9610EE777 +055F927444F971121CD76A55A273CEBB481B89D6B78D0066ECE31BE3EEF65CE9FD22D22EFD6E3ED4 +536E6439782AD53550CEF1A903125F228A3E2F1AA3342B0551C59924149358F8E941969125F6E54C +8B2C068F57FE0DF91912CD71BE9492768191F4F6B70BA45C72409D4AC6E9619E8A34398E6A66E984 +55F1793D8A2C0DF30EABE2B19679318AE09014262DFBB9AED5D637653FBA1C10821908ED6E088910 +AA033E2E0798A630966D29F84845A9C937C27A268CAB8BA1AE9B32E12B52EE4B64BE5388AD32035C +7F98904F052B31D7C4819E27BCBC597E8503A5614E0DC2BBE5C51922980E3F492B61BEEB169C3EB6 +AB9F1DA82EA6BCFBCC16199923C8399B0495EE9A9E1749DAD9FB289FF3FCCF34F55DF7D94F91EF31 +B3B4C8074C567C4FAB337A337BCC2F459075244F665B079C159AAB83781E465F5259D41313183EC5 +F5F53BFB4797942F93EAD6CBF9255F9B4DD0748A3BC6BF36ACBE0127AF68EA6566B65430D0595FD2 +A7A2FF74055BF2E70BFFF2B79131F1871CEDEFB495FF914B88770654F9E5556AF535E1B9AF812004 +99288AA39D1388ACCB5B11B13596B550F2746B2A83B076F4F606F7580156E54FBF6976AA4CFC9581 +0A7FAC41A1635FDCFD6F7CD6BF738190D7F9FEAAB8C0B7CE38DD1459E2465C3B75625C8BAFE3B60A +F66ED183965AB9681A8174C44311D8EE36E468A8FFE2035B7C5AB6A372FE37FF627F4697C0D19F7C +8A1E356F829BBF2B8F4A95A49F85CA16ADDCA5C0817D6A4F7C4EAEE908DA13E0C89C9AEFF6D1D7B5 +BCD1BBF672B46C4960720CD1C74E70C78784CBFA8930576AD4067D406A0FCE9248DCE6D610D7EF0E +97F3F9CEA1D08DDA90DFF0C484DD32265FD13A2571513F361DE3061F2EA76886112AC7589B290220 +E34610277CF81535CD628CA688CA812275D7B9909757DD519F1FC89FE4D0AFE5FDB999323A470C6A +A0D9BA9CC92BAC24514E7CB3A3EAD2A70271EA8B02B2DDFEC6CE803F1B761D9BB7099FFCBF918D8E +B602946FB0DC14F0FD1289037B15A4A96A4605BBE53BCEA9112BF9746F3BDDC06CCCD808C62F2B41 +D4F508EEFB03AD22E2E154888DC63C6BEC6A21D1851A5C82397FA49BD163BA8A72527827E4B6F50F +585325219FAA9B3A2CE14A0134C19DAE50373A0F9E6DDC8205467242D11A3989D17730C8169964B9 +CC9549BE20A84FD9137DD9C9F7DEBAC3A41345DFEB0AEDCD7ED408A909000FE8D9CAC85D93052256 +7A2C9312769DC85F902D4A5F5766EA3C561549F1F2B4C5269276946809ECA26B6F51DB4CBDA9E668 +BE1023D8962C0DBCACD5BBC9E5F61C459B825036E1322737C0F196C0DF93DA76011DF2CA06F7B383 +8C672A802EA24A474ACF7A51F2DE867ECAF5674B1053CD5419F5FAC20584A3F7565D7CE584CD395B +1968B622FE3C68DAD2F0E33274DFDF03B5A8EA047B077DA1316DF487C91ECEA84E9B417EA25EC9CF +1F1CEDD7A1C2CB0D51A58BD8F9772FAED8D553141ADAB148AFDA200479CD04C0FFD1478EBC618303 +5437A5BC1AEE3218E9B27D21656EB9F31BD4E7D3186C89DF251207E8B67265585083111BC1AFD4D6 +A2773629147A29CBB4BBC3935B83392487858E0D18FFC96E57C83C4C6744C8E0DBF001DFD64B660C +CC8064907AA4BECC12376A1EB55EDF655CBBB4744C1A6166590B9572073A2AB577EF446B80567241 +389F990AE6C90B286EB48454166BBF264422FF2A387FA0B413F2295B6B188A64927DF702C232CCAE +59A2CCD1D109840A464BAC74A45B865A6667C901D86F771C4A36421308551F532497990BC15DA648 +6E322566C210C517DAADDBC24DACB39CA41611B9F961E4696D1FEC46E71C608DFDEEF19ECDD88724 +24E1BEF7176B0AFA4888BDB4C56C8690AFD03428F32740DFBC4BE22B7583DC47BBA42DF4E83C14AA +29F4E79397BBDCD2EC43338BA1F0D2CA9DA6E64A065FCB0073ACD0D86E46EEBF1454C9C172C6BAA5 +0F37FA19756B05405763387237794E9FF74E6749BF6CE5EE9145413950D342DBE5B0EFDE57163127 +2A8D060E935547E1FB0FB1FD60400FF856D027671CCAECFF7E215BE61E8A77E9BF5C72C2C1E4501A +A11B2F8574BD823574EC9598D579A9C5C525867F4BAAAA78DDA0E5BD7AC832DEA41328A507874A85 +90B7F133E743471D4FA27DFAC39C6F99E233C913183B2A039CBEB5CB3BF8825A92D83D266246D5B3 +CC7B11469E611E260C6ED16D17C9693B13B78E3F0DE2F0ABBE73FD6AE1ED25F57B4894254FFDB332 +0E65ADA53669B95CA28BB4BD166507E9D8F12727E46D2B9593186C090764FE8A95F1594291FD551C +A96E4CBE1D6FD42852B2B65E7F10C4F17707F930DD934E2A2ACBDFA40E04EABC1E54632D67AB7D5C +00DC103A3D11DCF78B6771D98ABD5CCA0B253283C67B0863C80D1A78FE6D5422568207509FDB1946 +92706C7A211B29955F6354092C9732DB2ABA8CDAF407FF25B40BF9D73317D5985E19E6C12B6AB5F5 +DD59328905F822E1EBEB87C4E386EF20CEB7EAF842700F09BDC50D7AE6442D1C93804D56FE194785 +968373A154E1F426BF76692CE3E4474360ACB9FEBD3191B8EE909E7C224EF90EEF36CD660AC9A642 +4DB5BB20B8835D365D35A442ECB2444F30466C3323001A087639B73848E3EE275406CFFB495ECDB4 +52F2A357F45D2D32BB22CAF9F9C2C44741A5341B29BB6C4322287A2A3891E1E853B976E17DD9306F +D98BDE8A0C97ABDCC7C708BD1BF49B524CD0DCECB40DDC557E9C90A1EDDEE57BBABC4C338F08A625 +E03C1C1A20DEA709051EB1A3264D7002D6DD2BB7B622AF93C7DF54F49EFA5FBA58F115A049A91875 +A2476C19F9133D0BE1B07C8102746163CEA0A98F9B62EA59C9CED7AD808DD52DDF8687933AE52A7D +9D7081150A812801D3FA78F90C4933E5DF09C991325324DEC6ED364ADF73DFC7B78953B51FBA2F81 +F264C2A94139289CEBF7ECD6D8FEE9E579BE231FE1ABBCC07B4BF1884CABDCEBFC56610A5E2B7510 +37FE804511FB5443F7B21290E38855F6DE5EE38960E02481DC57F83CFCB87C8FF1CB196CDC19B2B4 +6C7C72B64D7D45C678851ED7E2860DE7DB772BE7B33C84ADF7E6F9B2063D1263CDF55E6220753ACD +67C4701EBDC0799AF47AC58A0CCE796384A50DE6A814DD994B372630E64E5FDFF57393522F689DF7 +81C44599B21AB1C214D4ACF94F07CFEE79C628C85378B96D9007ACD4225E14DAD8C70984DC596DF1 +96BC814E7346217C94F172FA40B3DFC73C30E6E530DDE4A91F6A5166AE19EAA3D2CC3D9D6917BFD3 +1EFAC19C236463AF8D7FB7D9B3A4D6FB6D962C59B296E988561957A3C3D11F33ACF69F5768A2DA30 +462BA9AFE67D3C41A1E8368099361F50D4F6368963A48C75F1590804E7918A02EB9DA5F60F49828C +95AC60E9D86DEB7699C35037669C02D408269D6BF481EC745E4EC630F68FF5168BE5E0BDF875EEC1 +5EB9CAA9CBED1CE2A0EB4CE97C14E10114087DCBE5DE1DB6A070CBC6B68EE141B7D62ED320F7B74C +07D9762E0F5C8AF0779D38B7677ED65B5DD2F83ED06F041B5701228EFEAA0FFC031E44D6D68B274A +45FF9EB62FDA007CDC98B86831E9A668098364B421E1FE2A45D85824612A521777C6177316F0C6DB +D9847A2F61326E74A3114EAC3ABAEE456156DF125E4EDAD6BDB66E7DA6370C90ADC4AF4EC1958170 +C32D5AAF3B45BF749EE1DFAD8A5D0C03973B7C1589EAE4E67F6C87213C721E4A33466F3F54253545 +5014417CFFC9AF461CF695F5E1D7A37E4C7680A5624AA11EEA65C59BB38A96734E4A4A238C636638 +9EB8083D5AD753FA7F74EDF10054315EDD53DE61D48846F70AA9272626E23BA3DAB7CCEE819B0A88 +9CE775AA7F5205C95A19AAF475729D2C0BCAD9AB5459380A5F034C108A64B80621820D779582A198 +14058DFDCC6C2D6461AC6F98C64AABA53A2EF77CEF612EA766FE15498F56F54106D70D0F8DD4134C +8C64B282E79A96F15D4BBE0297885ACF775BA5009006315637D60AD86D25A3CA6AC5BF5B4DB95B1A +672A8C589D89101809D838236560F7690E329784C1D335203BFED3A258CD79467D16B6DA06A282BC +4B9669986CB50C54DADE3D5B20FD5CD9FDDE60AF4B5D8D9C50E77CF50DF3B36F37F381FE7E28A719 +91C8199D18258C09BD0440F5E258A5D2E22C6CFBFDC5F91228992E86532F4C0A50067BCB2D4387B2 +9FEFB8043D7BC44A322FD3629D7D92B6EEBA21B035DAC884110BDC6B22CF59B08C195BBA1C682E5F +8CF32F479937D329FE8EC6E9A3A3AA63AE95EEB766E0EACEEAD4DCF46617888AE687009112732A7A +8FA8419A4652075EDDF83E291613B66BA793D3858CEA093C79B89B0C93E11CAAB86AD1980C401713 +0B6A02545DE484FDA6E4DFBCEBBC848751B2A8821CB3F7AC96D2EA9B83B575AB60CECA41EA567EE0 +8ADAC8F1060255057527485D6B12A26D2E7EF6892865FD65FEF28F8B46249C4F19341089D59DAF82 +582CAD1A3B53C763A0E57134A57C4A24AB4FD358592ADB01BE8F1D9FAE9CA97A3BC3A0E2939913E0 +62960CC1D29ECEF9E61BAC20E92F13C49078E7F1562C9D7C01C2B300ECD6ADCAC9AA0C1F1BA30401 +0573F79D158C66D91A8D987C5239C9D4FEED1DC5C9586C4129C33E7C737C624EE9BD1EDE38E9F72A +78DC19E144B5AABF3B0FB72216815E371A5D6452684965CB7208B87CE5A27E444820E03F80AD2DF4 +E280D25D9BDE41E719BB2C939E25B7965DB162CA8665CBFE65DFDC7992A508D54FE4EE8454C11482 +7186A280D3D9EDF397640A809E3646675AB6621B9CDF42808E2F19859AC975CF70F41D2B3D22F8E0 +C180126F4A12122B207A150BC8E1819962DAEBB821DE59B7EEF8FF9219474597AF859E353B4B713F +5F1350F4049DCE99627492E396038B10A2F8E82AB679F65BE14A166E4502921BA44CCFE04E5DD3FE +B04BA47C3F7A3E7DBF7D55328AD1E96092C73EDB16C3325537295DF768E2633D01304EA0B01379AA +DAFE6CCA2AB3E246768C5AC841A458A2F69141A9A9D716D129B26643F1BBC95D8CD0ED44148F7C6A +F370201E454F4AFF7C11A8080508FEDE242343E7ADDD5A8AAF079C50765A79BD25FBBDC59EDF980F +FE0B60435692C49D20AC22F8BD1FCA3F3D8200CA26D61A30212558B54671E63BC09776A3AE7A906F +CA63281F080A00500C42C02B8F82357A3EBF572CFC4BEBA3FE86537DDDF23A861C240A31778A131E +F638C1DBB7E87A45487C092D23FD9FB2C162C3253636ED263B141178FCA9F2D5BE2D81F99B2872C4 +B3C08749CE18C9C00E75DA6B3E4C6A6DD79EBA731618EF5B5E767BB40429D8FEEC6ABFE9975746FB +3A82A03A07B7D0DA8BAECFD1F72AFE17EB0C0CF100A2D48F4449A42C482BB1D64C19F8FEA73F5BA1 +8FC9BFEE3F640C39C081669A9B0EE3FF6B2BDC0A726301EF0E2259E65BE59275C76E7A3F1C76C3A3 +3AD0E2F79198DCA39C482B4E1196238DBA937692CF319436830BBF441C0A8A04161DC0814B5DF049 +83099439EF421B6D37D351D4A988A8DFE93D796B2E8B2564C602F79E6162F56F933196A0DCE91051 +600BAFFD9D439A91F17C20CD6A9104B553D823A0317E3C8344E9F0B2560AD583BF876AE307892500 +2177AF61BDBD745CEC2B2A7931D27423D2667584AE0D6CAA2B281EB9271ADE86C493791AC9A2265B +8324F764D20DE65F6267439F1574DB02C600E771D3C743D420FA6F8BF01A977B91AF035E0D5412AB +0B85D6ED3ED9D0345ED434F9B95FD911A9D3828CD162686E6ECCD2B6A5B1104F0E6838AAC7FB3FC7 +09F08F8E82B4BFCB55D8984771F9F4D339EE7FF391448C7807A436A78B6A487D3A5A86F314B302FA +C5BE1C1DB9844F975619D615D1C7A20EC2B144797E0648CF5C044C8DD1699EEA3AC9D7E3BAA54A85 +11A932623D5ED3F0A9C3028CA439AC395F58DEAF1C0354A169D9AC7F380900D9828C3ECBB975F6FE +6BD79EA4BEA9B71F3A9B1D2EAF8F1E475B4FB99758192EBCDA21B47EA33F57C58E904DA260C801AE +CF457C84592B81CFA96C10E448D705D24871F3D1AD1FE004406C8070A54FA3747788C5B55E9462E6 +F51378BF3F848360542CAD2D5FF9EDAC84C164DFEC115A2F3D873760EAE58CC8F361B37E6CE076EF +325A1C54DFF84DB95DB7DB5B56C48ED15C5426E6153D8A94B1A35B22EAB1D7B871097DF12C093BA5 +538957AB7D0AB2E39A2D1AECB91F0A693B8F6A00601929B6C55AAE8A227CAB6404FEBCC8BC4EEEE0 +ADD4CFD7DEC225170A0063457918B9875AD7F022A8F9932ADE316E4126EAB75C1D0B2B9F44E85F1C +54755D1301345665EA630C8E885A8D9AB069DEA2CC6D12E4A0BF6E80C3AE12BC7ABE507422A5D92B +65F70B4A472DD945EBB960880EC4C34E4C206C260FDF86A997D7D399A0219A51E6F8BC1189EC0ED0 +B8686F0243BFAFD829979747442AB1DA8282F10F4C37C4E6C88D231460DA3BF34C23A4755E2F8F21 +F6D138C86B6091CB3BCE5ED170242C4CC87C010FE63DA9A2ECBDD85311DA8572AD67E40F744D2EC9 +6607701D9790574F051C859EF29291AFA301C809E0A5513DF9A7FC2DB1776DBD4A2CB622BD17811B +C0E584A8D89D4BEB17C2D0AA8BF31CD7E2B36C0E1888409788E30EAC2E46AFA1CFE457D3A19C28C5 +80669CB82CF36BF40DFAFED78F99A5D5DF189CE293413FFBA9E8D2457986C68483242AD767D5F026 +1CE303153C35D5DEB1789448088B51C48A9B8E903BBC69ED7FB0129CCE33A0337198F11DBA94FDAE +AD74BBC5E519F559094E15B03AF3EFA89DC38B4278C68D2AEEEBE4B28B351D7B72F52D94480D8CBC +06544645F20800B8284A4E7C8FAD59D0AA8EC8EC0551912CBC62694A5DD15C469BBE614FB187C084 +838D676E5A1CC8DFA8676758EB7D53FD2BA8F1C434F4F70AED8F2CE27EED2F6A82E20E30EACF9BF0 +CE28B4BF7D556815A887D777B7D75F60D2C410AA2D378B7BFAA6C3A5BF0A5EF80D3E35A0CF393A5E +BC27A895D712C6F004F1A03F9FCF493973F33E6FB106407DFD992AB0E9E36EF395663A6EEFFD71BB +2DAD9DFE6AA4685F307B8FD69BB2F74E672666414A71FA479B37D3DEF6AEAF80FA14821C65F7ABBE +5CC6DD49002216E23B3D22C282F3DE0077EA81017E79D8C3BDB61EC031CFB13FA981120A6C518F4B +4C39C9DD5AC6EDA29ABA527C9948BC3162EF65E8251C9D4E3494C6A51A41D489F8BC89250128EBD7 +0EFD01B9F03C40B6E0384364F2571F7D969016A959E4243D7AB728F0F030B8E2FB456D8E47A85898 +313B0E91C68C7A8A7226603427AC6DCB5C69924FD026E90CB0EE89AC42D874BB15AFC981079E9DC7 +1271C3E461265AF327D4AAB4AEFAB6A9DC3F84DCE7C7868D4719006344B9055C8BA952A2250B8041 +E5EF519BB788201124BE90AECAC2293A310D9565E1EA4AEB99F7ED7106649169E8EBF3DCB210706A +F2FA65158D3059DFBD7AB5B00DBB06B016F73FEDB3AE1C1CC03BC2D94B143BFBD759338EB181B1F6 +841F7B539D8D126FC5B31B244A3A00ABBB121A98F73314D1F1683F513C3CCC9A0C2180824ACE5D45 +E454062CE1217F7AFBDC60A6022B0134EDA6C5D787DE8A5925BEB374F64D612329B9AC17B6EC03F8 +56CFEE502C1FC3AAC7B6F251E8DE4147DCB1580185C406F38E6B729307EADFF6D7A0E824C9830093 +F0C4FECEEFC7FF839306FEFEB780FFBD8879F22AFDADCA4C0767B8EEAE5DB869D7BD91355F0255E1 +CAC68A81A276967F233624DE0D2082B89E2C2D1DB0F8BE0B2BD7051C4DE9B1564AA6CBA03E50386F +D685943D1EDDF5BE0EB34418C48E5EA645CBA552A0444B254E82E9E84A5B7BD0064D5EA87B1716C1 +4EF7FD14E2187FCE16FF6ECBC0A6588B30B4329626F024BDBA65457028668BC6750A4DD668D017CC +EF35CD9CF45EC73885FC91B8AD78920919B4747A710D25BB850B558C5CCF7170C20012DD0F2874A6 +699DF53EDC6C75716A5B6B06C98F4904BAD22B18C77B3EEF32DE3F45A8AC12383DA16DF6F06CD923 +D344CFC2253ECF97A52F2DC6A22539231E29A324F851265BA82BE566E42167247CEBD914EBE42EAB +E189E74FE1FF17ADA8A5F47DC9866CF86694DEE28EED333B0A8AE380557820E3F1D32B05AE27FFE1 +68267FADD310C4EE550F71704EF71F4C669952F30E485AF37561884066C0E3425C748BBAC77CC7A9 +87EC336B8BEBA44358BEA4ECFEA32494A8006E9852C4BFCB1C0F00ACC247F1F8E3F3B34539C67638 +64527F9F885132DCACBFCB1DC80EE3B2A711145DDA1D3B5F83144E2CD698D43A02B0DEB706C78D75 +A24FA39B9374E1155969FAD7D5FB477F4C3C7440DBFE1BD1DC877360E8690A6D3A64EF0A1313A520 +C49320295302227CE5625E134EA442A537505D206EE46E7E218BDA38F7B6BA124ACA0E949725CEEB +961EF8B0727E3A78287E61E65A84AC14D1014A213259A48F04C634C1F04C73B4110D0DB491250CCD +7DAE323CC8BD29BEF605DD92AA5480009DEAD1256E5C9FFB18362D63B022C2A378D15ADE2C6AE603 +1DF385CF090226F0AF889464584A9EA63B884453DE231058C5661C4F7DCF7505D7B1E48C78AA5799 +8FF39084FFF1CD18CB2989D867F2E085B291FE52367B4E8667E5CDA4F41DDBC7129DA5BDC39549E0 +8B67D9C9E4C332D91FE261728521245CBF51870F24C42F5814F393719A9E126C5693C0E776E4D8F3 +0F0288537B8E9A7C5DA4F931FC83F96A1950B1DF26C0448B646D3BF42C0429E2BD0164703FC71833 +19DDEA75CE65D9DFFE8369787FE664306D843421C2C0C3A5B210E823F8366161A96CA88D95FD6486 +5F9E5B0D53DCEE4A98AC10A8FF1BC34A46DB6016E973C9FA298D3F58228E3D9FCCCF9632556CE2E7 +F9C81E05FDB0FC1071E69A4C158E742D0B8939CA3DE33A4BE8D01D679C81C6BA9CF396A6768CA626 +05D796AA5C323E3A6208F2DA5000FC4462ED81AD0044D9959E40E4764562ADA76E33EC63271966F1 +7A13AD376A54FFA40FA247896A22C1C4A4CE51094CA1892A0B9904EB762839AAE1EDB5A0A5A69343 +CCA45F88F61926B23E6C85688862B1D10F1ED827FCCE2A427E49D960DBEE0D39EDE08D2565C6F5AD +66CA83DAC1467C38F742132F715DA7E9757A5D067FDB4A8859D148473F302BF6ECB9B7EA5C0DF851 +8F58D4D076625B92BC8201028064950D70CD40492DA83B98FFD79BD3DCD9CEE95178B6DEA9AB10B3 +4C61A74B49C3697CF714F56A9B1E366B04500C9A67704AF6F721851EA4566B96E220E7B526873F94 +E6013EA8C489639D291167BCAEFB661C96C27DC7BCD5B317C1F101CD8DF2FB29F6162B5ECE903C0D +5570C8C6325F05218850E4A5B5F94C7CE334BADDFC5DB98EF5333B4A92129F41D046D0F5EB6F342C +F362BC8E45511FCF328E2CE2D4C579516307AEC400BA207071C887F5185AF536A6827307D7698095 +E8A6F8C19812A11C32AD207FBC353172C8DF626C5C05CD24C171B3C8DAB270A74AC48E35F0A8849C +E0934B93D56C93599F5235EC441B5232257C4C6767C7B5A35830010E60E56AFBE9199B42E725A216 +C49D45A3C554767DF192F04D1A192D5CE196831EB7A56201A4A96CD44DD084729774334F698E1902 +614405347A37A4C753C0211D7F24D5265CF033E3C4D30E61886CF781D2AC30385E6021A892AB116D +1F5505635BF5C915D0300ACA8AC32CAA0F3639550C4AEB390DA7B86E53B6BB0E4819626757BA9986 +BCA4B4B18F6BF3706577A3BE146F2585025D5A36EB14E587C5469EEEB622D31FD32EBFA812B9CCDC +CFF7D17D00D9F21BF8871661F20E89A25A4543068EC509D0DF7DEA2A24FF68957943BE0FC7BD319D +92A575E12E6FB8112D72A100FEF3EE85972304BBE07FC1026CCC068E8A414F641116E51F76009C5A +F9E664BCCF93C943CFF2115235BB3290F935AA3FAB4F2E73FCDB9FA5E1866EFE278FFE7EA9DD6F89 +38CD5E5F4F31F6C659AF7EFF5B291AFB86521678B42C99275024EDAD04E5929D201A36F4A6C4DCD7 +2E349D6B9A1F90B123DEDE3DA50EDF929F9285747CD504C8F4A73D522F312D623BD65A35419C0729 +30976129B2D99B32D1DEDD81D64F7879CEDDFF04BBBF1286E07BCE651E2DEC190DFB9D25F3C1EEC8 +D7277752BB7CCC313A0FEEBF0EFFA2A189861AD4EA13AD4FDBD5160DC273F12525815F0D693C1413 +AFE4FBE09BCBD71E16C33E6CF8D18732447FFDCC2E4AF9E270D725C1D262D96E72538CA3309BCAF2 +35A3FAFAB27D265C583E6F6F062B555C97DFD89AC46E6154670ADCA13FD99F7A4B9B8BB224D43764 +8CC9DB76E177F562862C912D8CBC248A4C628FF2D0C9688611CED3DFB89064B988F16655633B7CC7 +AFAE34B2937528EA0814ED245D6B0D2AEF87A5B63E2BD7E4F7D9CFE372C295A0891F97C3855C97EC +C03C7C2A231704BED419612255F8B2C9262D48CB4FB46D63CC9697A210FA9D7BFC3BAB7E46172D3F +52A4AF9C4114BC72A5C7CBA042FA633AFD5E404F29408D4485837E55E6F9702F04C7AB410C351039 +6A78C8672F8EAD53BB9CBCED63FB9E72E7238CC88FB4E7C48C3DE3E4B80E277B952727916A4127A2 +5CC1413C390F4DAFD5253B07BC96DAE8CF4DA08330DEE580CBC1E12B75A661819E96B018D47A8B71 +B6BFAFC5E3CBFE68E5193417B6E730E6A2820838D22049BE6BB64B74AA13779D46519965ED80D5FC +30B0A6F73D26DEBD8150B3D27B3F135F608D59BB1632AD9C2E11177FAF54CBD1C4E58D58C395BAE8 +AD7C7AEEB0E1C3F0C0B7A5E7142E9A1DCCC8B4EF56C319D4A4F750857D2FD180F871772B9CC69FB9 +B222F83C3ACCB66125AF6848B2EFDCE3D2284FA5844641FB32F701FBF32F1D2F2E2233B66E36CCD1 +49FCB3FCDB6EA04367D11624717D73D9128EA7D9AABB8658BE9E9986E532 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark{restore}if + +%%EndFont +%%EndResource +11.52 /Mathematica1 Msf +0.75 10.5 m +(p) N +P +[1 0 0 1 -61.69 -151.566 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 105.842 m +73.497 105.842 L +s +P +p +np 52 98 m +52 113 L +69 113 L +69 98 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 53.44 99.467 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -2.467 m +-2.44 14.533 L +16.56 14.533 L +16.56 -2.467 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(2) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -53.44 -99.467 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 53.742 m +73.497 53.742 L +s +P +p +np 52 46 m +52 61 L +69 61 L +69 46 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 53.44 47.367 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -2.367 m +-2.44 14.633 L +16.56 14.633 L +16.56 -2.367 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(3) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -53.44 -47.367 ] concat +1 w +[ ] 0 setdash +P +P +1 g +[ ] 0 setdash +p +0 setlinecap +78.19 2.952 m +78.19 5.849 L +s +P +p +0 setlinecap +92.764 2.952 m +92.764 4.69 L +s +P +p +0 setlinecap +107.338 2.952 m +107.338 4.69 L +s +P +p +0 setlinecap +121.911 2.952 m +121.911 4.69 L +s +P +p +0 setlinecap +136.485 2.952 m +136.485 4.69 L +s +P +p +0 setlinecap +151.058 2.952 m +151.058 5.849 L +s +P +p +0 setlinecap +165.632 2.952 m +165.632 4.69 L +s +P +p +0 setlinecap +180.205 2.952 m +180.205 4.69 L +s +P +p +0 setlinecap +194.779 2.952 m +194.779 4.69 L +s +P +p +0 setlinecap +209.353 2.952 m +209.353 4.69 L +s +P +p +0 setlinecap +223.926 2.952 m +223.926 5.849 L +s +P +p +0 setlinecap +238.5 2.952 m +238.5 4.69 L +s +P +p +0 setlinecap +253.073 2.952 m +253.073 4.69 L +s +P +p +0 setlinecap +267.647 2.952 m +267.647 4.69 L +s +P +p +0 setlinecap +282.22 2.952 m +282.22 4.69 L +s +P +p +0 setlinecap +296.794 2.952 m +296.794 5.849 L +s +P +p +0 setlinecap +311.367 2.952 m +311.367 4.69 L +s +P +p +0 setlinecap +325.941 2.952 m +325.941 4.69 L +s +P +p +0 setlinecap +340.515 2.952 m +340.515 4.69 L +s +P +p +0 setlinecap +355.088 2.952 m +355.088 4.69 L +s +P +p +0 setlinecap +369.662 2.952 m +369.662 5.849 L +s +P +p +0 setlinecap +384.235 2.952 m +384.235 4.69 L +s +P +p +0 setlinecap +398.809 2.952 m +398.809 4.69 L +s +P +p +0 setlinecap +413.382 2.952 m +413.382 4.69 L +s +P +p +0 setlinecap +427.956 2.952 m +427.956 4.69 L +s +P +p +0 setlinecap +442.53 2.952 m +442.53 5.849 L +s +P +p +0 setlinecap +450.12 210.041 m +447.223 210.041 L +s +P +p +np 451 203 m +451 217 L +459 217 L +459 203 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 203.666 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -1.666 m +-2.28 14.334 L +7.72 14.334 L +7.72 -1.666 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(0) N +P +[1 0 0 1 -452.28 -203.666 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 157.941 m +447.223 157.941 L +s +P +p +np 451 151 m +451 165 L +460 165 L +460 151 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 151.566 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -1.566 m +-2.28 14.434 L +8.72 14.434 L +8.72 -1.566 L +cp +clip np +11.52 /Mathematica1 Msf +1 g +0.75 10.5 m +(p) N +P +[1 0 0 1 -452.28 -151.566 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 105.842 m +447.223 105.842 L +s +P +p +np 451 98 m +451 113 L +468 113 L +468 98 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 99.467 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -2.467 m +-2.28 14.533 L +16.72 14.533 L +16.72 -2.467 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(2) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -452.28 -99.467 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 53.742 m +447.223 53.742 L +s +P +p +np 451 46 m +451 61 L +468 61 L +468 46 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 47.367 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -2.367 m +-2.28 14.633 L +16.72 14.633 L +16.72 -2.367 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(3) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -452.28 -47.367 ] concat +1 w +[ ] 0 setdash +P +P +p +np 210 257 m +210 272 L +310 272 L +310 257 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 211.235 257.578 ] concat +1 w +[ ] 0 setdash +p +np -2.235 -1.578 m +-2.235 15.422 L +99.765 15.422 L +99.765 -1.578 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +p +0 9 m +(I) N +P +p +3.75 9 m +(m) N +P +p +12.75 9 m +(p) N +P +p +19.5 9 m +(a) N +P +p +24.75 9 m +(c) N +P +p +30 9 m +(t) N +P +p +36.75 9 m +(p) N +P +p +43.5 9 m +(a) N +P +p +48.75 9 m +(r) N +P +p +53.25 9 m +(a) N +P +p +58.5 9 m +(m) N +P +p +67.5 9 m +(e) N +P +p +72.75 9 m +(t) N +P +p +76.5 9 m +(e) N +P +p +81.75 9 m +(r) N +P +86.25 9 m +(,) N +92.25 9 m +(b) N +P +[1 0 0 1 -211.235 -257.578 ] concat +1 w +[ ] 0 setdash +P +P +p +np 34 75 m +34 165 L +49 165 L +49 75 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[0 -1 1 0 35.28 164.105 ] concat +1 w +[ ] 0 setdash +p +np -1.895 -2.28 m +-1.895 14.72 L +90.105 14.72 L +90.105 -2.28 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +p +0 9 m +(S) N +P +p +6 9 m +(p) N +P +p +12.75 9 m +(a) N +P +p +18 9 m +(t) N +P +p +21.75 9 m +(i) N +P +p +24.75 9 m +(a) N +P +p +30 9 m +(l) N +P +p +36 9 m +(r) N +P +p +40.5 9 m +(o) N +P +p +46.5 9 m +(t) N +P +p +50.25 9 m +(a) N +P +p +55.5 9 m +(t) N +P +p +59.25 9 m +(i) N +P +p +62.25 9 m +(o) N +P +p +68.25 9 m +(n) N +P +75 9 m +(,) N +10.08 /Mathematica1 Msf +81.75 9 m +(c) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +P +[0 1 -1 0 164.105 -35.28 ] concat +1 w +[ ] 0 setdash +P +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +p +np 117 136 m +117 152 L +155 152 L +155 136 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 118.485 136.705 ] concat +1 w +[ ] 0 setdash +p +np -2.485 -1.705 m +-2.485 16.295 L +37.515 16.295 L +37.515 -1.705 L +cp +clip np +p +np -0.485 0.295 m +-0.485 13.295 L +35.515 13.295 L +35.515 0.295 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -2.485 -1.705 m +-2.485 15.295 L +37.515 15.295 L +37.515 -1.705 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +%%BeginResource: font Mathematica2 +%%BeginFont: Mathematica2 +%!PS-AdobeFont-1.0: Mathematica2 001.000 +%%CreationDate: 8/28/01 at 12:01 AM +%%VMusage: 1024 29061 +% Mathematica typeface design by Andre Kuzniarek. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00] +% ADL: 800 200 0 +%%EndComments +FontDirectory/Mathematica2 known{/Mathematica2 findfont dup/UniqueID known{dup +/UniqueID get 5095653 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +20 dict begin +/FontInfo 16 dict dup begin + /version (001.000) readonly def + /FullName (Mathematica2) readonly def + /FamilyName (Mathematica2) readonly def + /Weight (Medium) readonly def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + /Notice (Mathematica typeface design by Andre Kuzniarek. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00]) readonly def + /em 1000 def + /ascent 800 def + /descent 200 def +end readonly def +/FontName /Mathematica2 def +/Encoding 256 array +dup 0/NUL put +dup 1/Eth put +dup 2/eth put +dup 3/Lslash put +dup 4/lslash put +dup 5/Scaron put +dup 6/scaron put +dup 7/Yacute put +dup 8/yacute put +dup 9/HT put +dup 10/LF put +dup 11/Thorn put +dup 12/thorn put +dup 13/CR put +dup 14/Zcaron put +dup 15/zcaron put +dup 16/DLE put +dup 17/DC1 put +dup 18/DC2 put +dup 19/DC3 put +dup 20/DC4 put +dup 21/onehalf put +dup 22/onequarter put +dup 23/onesuperior put +dup 24/threequarters put +dup 25/threesuperior put +dup 26/twosuperior put +dup 27/brokenbar put +dup 28/minus put +dup 29/multiply put +dup 30/RS put +dup 31/US put +dup 32/Space put +dup 33/Radical1Extens put +dup 34/Radical2 put +dup 35/Radical2Extens put +dup 36/Radical3 put +dup 37/Radical3Extens put +dup 38/Radical4 put +dup 39/Radical4Extens put +dup 40/Radical5 put +dup 41/Radical5VertExtens put +dup 42/Radical5Top put +dup 43/Radical5Extens put +dup 44/FixedFreeRadical1 put +dup 45/FixedFreeRadical2 put +dup 46/FixedFreeRadical3 put +dup 47/FixedFreeRadical4 put +dup 48/TexRad1 put +dup 49/TexRad2 put +dup 50/TexRad3 put +dup 51/TexRad4 put +dup 52/TexRad5 put +dup 53/TexRad5VertExt put +dup 54/TexRad5Top put +dup 55/TexRadExtens put +dup 56/LBrace1 put +dup 57/LBrace2 put +dup 58/LBrace3 put +dup 59/LBrace4 put +dup 60/RBrace1 put +dup 61/RBrace2 put +dup 62/RBrace3 put +dup 63/RBrace4 put +dup 64/LBracket1 put +dup 65/LBracket2 put +dup 66/LBracket3 put +dup 67/LBracket4 put +dup 68/RBracket1 put +dup 69/RBracket2 put +dup 70/RBracket3 put +dup 71/RBracket4 put +dup 72/LParen1 put +dup 73/LParen2 put +dup 74/LParen3 put +dup 75/LParen4 put +dup 76/RParen1 put +dup 77/RParen2 put +dup 78/RParen3 put +dup 79/RParen4 put +dup 80/DblLBracket1 put +dup 81/DblLBracket2 put +dup 82/DblLBracket3 put +dup 83/DblLBracket4 put +dup 84/DblRBracket1 put +dup 85/DblRBracket2 put +dup 86/DblRBracket3 put +dup 87/DblRBracket4 put +dup 88/LAngleBracket1 put +dup 89/LAngleBracket2 put +dup 90/LAngleBracket3 put +dup 91/LAngleBracket4 put +dup 92/RAngleBracket1 put +dup 93/RAngleBracket2 put +dup 94/RAngleBracket3 put +dup 95/RAngleBracket4 put +dup 96/LCeiling1 put +dup 97/LCeiling2 put +dup 98/LCeiling3 put +dup 99/LCeiling4 put +dup 100/LFloor1 put +dup 101/LFloor2 put +dup 102/LFloor3 put +dup 103/LFloor4 put +dup 104/LFlrClngExtens put +dup 105/LParenTop put +dup 106/LParenExtens put +dup 107/LParenBottom put +dup 108/LBraceTop put +dup 109/LBraceMiddle put +dup 110/LBraceBottom put +dup 111/BraceExtens put +dup 112/RCeiling1 put +dup 113/RCeiling2 put +dup 114/RCeiling3 put +dup 115/RCeiling4 put +dup 116/RFloor1 put +dup 117/RFloor2 put +dup 118/RFloor3 put +dup 119/RFloor4 put +dup 120/RFlrClngExtens put +dup 121/RParenTop put +dup 122/RParenExtens put +dup 123/RParenBottom put +dup 124/RBraceTop put +dup 125/RBraceMiddle put +dup 126/RBraceBottom put +dup 127/DEL put +dup 128/LBracketTop put +dup 129/LBracketExtens put +dup 130/LBracketBottom put +dup 131/RBracketTop put +dup 132/RBracketExtens put +dup 133/RBracketBottom put +dup 134/DblLBracketBottom put +dup 135/DblLBracketExtens put +dup 136/DblLBracketTop put +dup 137/DblRBracketBottom put +dup 138/DblRBracketExtens put +dup 139/DblRBracketTop put +dup 140/LeftHook put +dup 141/HookExt put +dup 142/RightHook put +dup 143/Radical1 put +dup 144/Slash1 put +dup 145/Slash2 put +dup 146/Slash3 put +dup 147/Slash4 put +dup 148/BackSlash1 put +dup 149/BackSlash2 put +dup 150/BackSlash3 put +dup 151/BackSlash4 put +dup 152/ContourIntegral put +dup 153/DblContInteg put +dup 154/CntrClckwContInteg put +dup 155/ClckwContInteg put +dup 156/SquareContInteg put +dup 157/UnionPlus put +dup 158/SquareIntersection put +dup 159/SquareUnion put +dup 160/LBracketBar1 put +dup 161/LBracketBar2 put +dup 162/LBracketBar3 put +dup 163/LBracketBar4 put +dup 164/RBracketBar1 put +dup 165/RBracketBar2 put +dup 166/RBracketBar3 put +dup 167/RBracketBar4 put +dup 168/ContourIntegral2 put +dup 169/DblContInteg2 put +dup 170/CntrClckwContInteg2 put +dup 171/ClckwContInteg2 put +dup 172/SquareContInteg2 put +dup 173/UnionPlus2 put +dup 174/SquareIntersection2 put +dup 175/SquareUnion2 put +dup 176/DblLBracketBar1 put +dup 177/DblLBracketBar2 put +dup 178/DblLBracketBar3 put +dup 179/DblLBracketBar4 put +dup 180/DblRBracketBar1 put +dup 181/DblRBracketBar2 put +dup 182/DblRBracketBar3 put +dup 183/DblRBracketBar4 put +dup 184/ContourIntegral3 put +dup 185/DblContInteg3 put +dup 186/CntrClckwContInteg3 put +dup 187/ClckwContInteg3 put +dup 188/SquareContInteg3 put +dup 189/UnionPlus3 put +dup 190/SquareIntersection3 put +dup 191/SquareUnion3 put +dup 192/DblBar1 put +dup 193/DblBar2 put +dup 194/DblBar3 put +dup 195/DblBar4 put +dup 196/BarExt put +dup 197/DblBarExt put +dup 198/OverCircle put +dup 199/Hacek put +dup 200/VertBar1 put +dup 201/VertBar2 put +dup 202/Nbspace put +dup 203/VertBar3 put +dup 204/VertBar4 put +dup 205/FIntegral put +dup 206/FIntegral2 put +dup 207/FIntegral3 put +dup 208/OverDoubleDot put +dup 209/OverTripleDot put +dup 210/OverLVector put +dup 211/OverRVector put +dup 212/OverLRVector put +dup 213/OverLArrow put +dup 214/OverArrowVectExt put +dup 215/OverRArrow put +dup 216/OverLRArrow put +dup 217/Integral put +dup 218/Summation put +dup 219/Product put +dup 220/Intersection put +dup 221/Union put +dup 222/LogicalOr put +dup 223/LogicalAnd put +dup 224/Integral1 put +dup 225/Integral2 put +dup 226/Sum1 put +dup 227/Sum2 put +dup 228/Product1 put +dup 229/Product2 put +dup 230/Union1 put +dup 231/Union2 put +dup 232/Intersect1 put +dup 233/Intersect2 put +dup 234/Or1 put +dup 235/Or2 put +dup 236/And1 put +dup 237/And2 put +dup 238/SmallVee put +dup 239/SmallWedge put +dup 240/DoubleGrave put +dup 241/Breve put +dup 242/DownBreve put +dup 243/OverTilde put +dup 244/Tilde2 put +dup 245/Tilde3 put +dup 246/Tilde4 put +dup 247/BackQuote put +dup 248/DblBackQuote put +dup 249/Quote put +dup 250/DblQuote put +dup 251/VertBar put +dup 252/DblVertBar put +dup 253/VertBarExten put +dup 254/DblVertBarExten put +dup 255/Coproduct put + readonly def +/PaintType 0 def +/FontType 1 def +/StrokeWidth 0 def +/FontMatrix[0.001 0 0 0.001 0 0]readonly def +/UniqueID 5095653 def +/FontBBox{-13 -4075 2499 2436}readonly def +currentdict end +currentfile eexec +D8061D93A8246509E76A3EC656E953B7C22E43117F5A3BC2421790057C314DAE3EFBFF49F45DD7CD +91B890E4155C4895C5126A36B01A58FDB2004471266DA05A0931953736AD8B3DEB3BCB2A24BC816A +C1C90A1577C96B9096D6F51F9E21E625ADF6C3A49867A632A605C117E6325C820121799F412E226B +EFE61F2813676F172CBD7EC10FF1EFBB92DF3A88E9378921BBD00E6024CC08EF057CECD09B824E0A +CCDAA4644296DE34D19D779A21C30666026829D38FB35A2284CAED23C8B913E7B28BB3DA7C8CE390 +4C0BAE30B0287680CCCAB2E6D4CAB2E7D786ABF54068028FD7D94FAC236094761B62B7E76F68D2BE +58C23AF85001950EFC1A9C1BB71520B78DDF6AA0058D25D041E86D01878DF56A5C48D74DCB2BBD68 +D75C94A3CE878484D28049331CE3D4364B40FAA2C754E8F443D244C5BC44B1C7868E36EAF4F7EF1F +6CB81E6CF63FABD65C29A991EB7D724DA06535AE43F3D0E2D04F6113B493C15463B4CEBFB72AB879 +7E4645F9CC0BB17E02A9626BEA4B4259F798B53B18DF2ACCF2B86BF2209CF0265DE0A46869333F98 +CCF70BF2C9239A0ABD2E97923AA5695BAFEA31E27B8F532BAA45F2980A11D069265A5312A260A627 +A765C4A08897E5C500990AE6FDA4CD6D575905468417297380EB6400CB2CF001C4B8EB79811CD8D7 +C173A337922B99DAB1048D5D03C78F78F36FEE31673D8C5FF8AD689A63AEA055CA705DB47D3AF965 +73571985E62F63866018C96EC4CA7E735C9294D8C81C03806D23CB87C0C08F86F5FA68CFC9AE48F6 +958AE016DCE4D60EB64AEAAD59D8A2592BC398BCA479FBC2F0C20C3E7F730481494C88781A6A9E0E +4F47A94619A3841FAC76A4FB252EB6FB43628AE1A4944539B1DFF672940AA5E93FFACFAC04624EF6 +7ED9C691788F0004DB7FFD1C995F2C52C0042F02F5C789F85D9E51716F3B4EDB21D4B9E660E4A892 +B747201EEC6DD6A8881FA3039664061094D1892108A2AD068D7F0251BFA72D874ECB2F42D27CC343 +052156F6A3A66D2CA6DAEF046A433FD54FEB4410315690971D0F43363EC0119B21F4BE3DFDC8C28D +BF5D68F145D3AC932EE62C32DFDEB1C48C2455392193268C893093BF911645986607DD13D8285895 +1854A7FF81FC98ADD44742907818B3C8E3187371BD9FE6EF5B4315E82C359BF2EA91D2894EE7FD9A +734BF2745D7FE8D08D29DA2A03176C992E11F1AADCE219D5E33F325BCFF4521D6D04E61B913B6C41 +740AF8BD9EA83F3AE7C4E5402366A812B1724431EE78D2028317B975E91941B591CC6C97740C453C +C7D7AB3CE97AE2F4DFAB6B9C8810A52A276BEAABD687BDA0971EE623A40B7AD6A8E69ED8BE63959D +3DCF8799E2505AC7F7B0E2FFECB0B08027A6266B7F96311B0AD78B7B6C78392AA062A73FDF179FEC +7F748929D060289F899FE417D4028FF332204FE04146BB130EF05FB57AF4BF9337EF71094DC5922E +3EF2D6A9F8257AF242019C349B65C2A3972ACA842D14EAB6A8D287456C9D7D295198F4AB7632EE43 +7D006B8124A80BBF26C1B902379D7F90080B51F982630E27B6031CE20930C273EA5D7FF0FC7E996E +67B072796CD96A7739444D21FE10DE7B57D143E4155EEE1B89F32EBCBF4C64D6D3FA1C46E06C9E9F +F99BC9BBCC61A400C7DBF812C42AAED863FE9EE3B02E731D1809F3CAB941252DE486BFE641F60F60 +C788F68519A6B04A4D6F50F02F93C6B8774B960C1FE893373D2AC2D865C487CFFE7669E1F1E73630 +7D34C15835F0453C8B0C1AE352CE1F27065F1082E6C84F86330F76B39C246315D6996AB69F81A020 +30D92CCB4C2A5AA4F4B3CAC88B2C8C9C621294A5EAB6AC3778DB99BD2F411735DC1421861417B4FD +399B365AEA45185A3D9D687546E36BB73994FB7FA3EE890AE3734BD9381B0E7AE514E8C517B87268 +7364C38D0A8379039F33336799205F2F54BBF7C2E9B30B27BCFB9FF2CD64F5D700F2455EE66B6252 +6E79ED2B0E5FF9732281CA50D27A93F259B6D4B5C7F856BB7D4F2E0F7741FA2419BBAF86C372E34D +59BC7AABC4CEF4F47EE40E118AB95A4863E16C55824D34002D59844D1F51B5DC6FB6BB5D595C4404 +1E05A84FD453A129279F894D726F6CD53BA3E234518324C5F715DAE6E7B5695326FC0C9B6CA2B53D +B25EC76BE9662388058629E70DC7BD861E188F1FF32A754A455195163CB4754D116D24E6A6300B67 +1862625E260442DEA2505E36D5F7AA4AD1FEB3B42632E382959C7E84569B2A790A0D0A208C2D4816 +AD28046B42C7A4797D424277AD9425C04DB87DCF112AE431CFFF6E4FFA979E947502AE5E1755C112 +0AE2361888B956F3F513A5975680E6E8374D8BF26C32AADC826D729E9026B14A68BC3D065E11C697 +D4D474CF963AFE083DD7D9278A0C27447E25AD70DD40B2EBAB8040164E11CD75AE3125C29806DEF4 +AD1B989F7001E227685DEF6EBE3287DE43BBA5FE2123A0EC835AECF879C13F7CFDC409901F291E89 +06C26E42B189862AFAE029F03D86D35E44E318FE16537E2B73E4F39F1E6945A3A6432438DCB6D2B2 +09814B5228D08165568617C279499ECA1B88C90300F1C88C45D4BE3DC726A59E24B46C5B2FF228C6 +E6645819C6F1D5B05737BE7353F4787EE52A21DC47A44F3C44406E79BBFDDC164682B02F4C39300D +12EF37A58E317FC1B8CE58E04BE666ED5DA75DBF752BEDDA4C7491E4D6922BCCA9CF421CE6751002 +8638EF643119841F423626D6B19A5D2CFB193B093D7646913F64614C098E5F5FF9422EBA75FA9AA8 +4F8ED114AEAB011E6F0727FB96F26BECBBAFE3AA8D0ABC5A8E9906B6CBB9E03F8CC4FCA97C884B83 +7CC33C619CD3195C55633B72D3F2D45561CD226F42B859B8099812D591886FA851107A185169FA7C +944248DE28642FA3043FF3B60236BFD781257C6FE4D56174AD16ABBF9659C05F08673A70496A0787 +C187D4367CB0CF48BD9A4FE0E481273E4909A1092626A13917DCBDE920028B06E094F5B28887B990 +32521E1720B75EB284AA6FFE53FA5CD5B903F951FCF7B33CC981FE7BCC4BDF4907ACC3AA60B69969 +A9AF204C84EC8C0F5DCB8A85E39EA9F2D4B67095A44CA0C8B072D7A61F3015D502B1A0F660C22221 +3231986D5E8C04AECBAFE999D1735A80051C06CA279D0FF6215633FB7A706454DA7236DB17AD72EE +1F6044A26A0EB77AB3BCE823E3F5E0DD31ACB029A1D17665FF16E5D1ACDDFD83CAEE1D48666D7BC6 +DADC34D317C335855D118892CBD32412F5870C3D2E599A46AA997A5E2BBDD3001C2957D81345DBED +583B72C4FB356F0C872A31A35087775EF18601930C0874EEA1ACB3ED3690EF447926439CC383087C +C9D5C6EB21EDF5941CB4E99FDA434A91676D76DC1A1BD801EECA6B0A88370B7005D41A1253CF8217 +1285986DC302B51123DBA9733BDEF0361AE0580FE6FBA5F29CF1438801586559D7436436CFE33E6A +D6EFA850BB8C9382E1A068246F370388186DC278F31F770C2C96881AC6E40823B24D6D536514A2C7 +AF3D159621080442592CAC03D6967BCBDB38FCA1A45F76F1A10027F1DCC076533C6AFC097FBCF0DA +A0078BE0828247F938AF76A87EFC14D9F5444CBCDCE637E2325D86B9D99D1ED70F44194E19F6C7A9 +9E415DC8A6E484DAAE52AAC1307A5353E8F649A35214B3F43DB5F3DB3ED06580A570E60B3E52679F +F90A3B00D4EB4DFBCF0D2F9C5545A6DE10BCC849A0BA9E18F29C6F09ED0F0DD48AD37B7925654433 +A6D02965A3813BA2EAB2E6C2004ADD216DAE99471EE518BD0DA0F534F1512A8E26286B07FEDE71E6 +0A5A057A22AEF095A8B4752F54C04CB8BC170F3D6B725B83A6780176194B21BA906E37B7923E2548 +604F8DB18E0A3E1B5FF04D00898C29C6033DAC54637CF5B068291559D8526D5201F3503FBA4EE12D +D7A6CF6271618F41FE08384521CD771FA80364D747430A071EE3D3ABDB5400DD36A0285430D537FA +F6EF8ACAF85C250D6652F327B2BD3B29E1C64E6E66C788FF1D9C3AC6DD38691CDECD0F3FF4079BAD +A2BC0CBE14AA3FCC38E3F31B3298A6995C87B34A7245ABA2C968F908D8337860507569C370654635 +570615F987531551414B5CCAF7F4D0B38F701619C553E746BD90655294270560046A925A021C98F9 +3EA8FF5B5B8A0D05AD483E6DDC5257635308C6C0FE9182D0E4FB011A00981A7B95DB5BF5A82F8B1E +B68E8822F8B1B7CF01AF11302B44307F3A71D5EB3465F793CAEB1E72D2C63E3D264380A75FF1DDA5 +00B5F82B04179EA9DAC10731FDEDF5913EFDEDDF5799D2A86EF2D16C0B52D99FCEAD392E9226AA6D +3D11D29054130C8602F703CB1EBDAAA089A02C0EBD53343A7B297836CB63E4B2C33C8760ECEB15E5 +6B59D4B296B8B724244D37651C3CB862C7135D62B2692F5A27B9374C5C3C5399E5C4DCCD76572294 +F742B1F545B736BF4C82D8F4E2941CD52C0B944261DD4CCF8A968B646662F3D187557206FF165F3C +0D3D5CA1E428D61D7936E1D00C5377A047EE80E0A5612F7FDEBB8B224270ED23A031A049E8676516 +BF66EBAFCF3F9D4975B0F212FB7A914EE45640972B61AE8E60E602DC7C20758BC07A159B08862F16 +6302D8CBEF03C4B0C73BD8504EB5B14DBC64FBDDC867FE51F76099769A7BD4FA4CF4096EAAAFD55F +9A20F6D4B84D8FD139112A43204026F15F9FF5AB6729537CCDA60369C24D7EFF4B6B971EBF0BD277 +A9AD1BF1066508A0A7DD9A8D45447A39B92D6B0F7DA4BEC2689D25F35C8C490891F053F90DEE5E2D +8C9D7FD1E23D0F1C5F57921BDB13BC9B8C3CED4FC42C4DDBF0706985A0DDABCC683FF5EA8416C225 +ABD219024432E3972C3314C30A672FD21523C83D93B2AC8D1DF023EEB1BD74E825FCD19873E63A45 +F6935E6685CF5EF472191B976F9EED2A4765E1B21B46EE1C4CB90AE89DA48E52BC4EDBAC2C855A67 +CB0BE9160F3126151CD171081192D0D6CB27E4EB2D725F31AE95FB283149F53F22BD8E414354D4BB +56724057601FE4BF34A5B188C00B0E550639CD796CC66EF895AA5315BEAD49B5639EF0878CDF2CA4 +271027678693EA212D0C11A6EA52F748AD0F62A0336BEC8497EE933EEC461E461CCD2F5291B980E2 +8B7D66690B10EEBE22B092179396EEF5D00E42E6CB73BAD4485F2063AEA6B6207E136ABB925332C2 +60F12D8B60B211A9BB15F37F42F53AC2559F5A8397DDD491D314B6DB0B50E41F0AA0A42FFDD5B9F3 +FBD8EFB7E343C06F793DA6BBEE4FAAFB233C31EAA3AD701B1F1C4F2FB05A7647439D19CC856C7D98 +EB3375B3ED2255FA33D9ACB87C1937E0B7F34C9B299C8D2A7F85D41141C598F9505C72B5AC2DE9BD +E24CDAE1DEE416786B93D4EE713E437D2C4A3251A296B785C81A8232F98ADD04B3D2B41394F8BDEA +7B602A555EDBD51A088C2277D8E86B08A0D05CB6142E33E973BB3F2CE841D323ABE6FBBF83B84272 +220F569DE23264AB672C2590D4527A29510E7F289DC2193E66FF23D83703E27E9E81572E7534B1DA +510BB846576E8A39D9CF44483F297C92317ED8E46237B3D73844B3B19D37831B44EC116CBAC3F75B +B67928C4D4E741EC95E96FAD74D852220F4F1A8FDCD273E0F6E77F09EFD5723CCA1398A021FAE947 +9CAC5922DAC8E2F46704BC216C7BCC1575A455CCE2C2A080B9FDCD0758D9D921EEB6DF96C64A31D1 +C9BEA80F48461857ED7DB635A3BABB3BB6155E835E605B09E06A2AAF6BF3EA70B8C0E098CD1A818E +30B9A4AADC284EE2B87E945B717FA73AFF5FB788E51827F6FBE319ADDD059614B493ECCE718789A2 +EB0F117EC811EC38A3F4EDEACA660612BD247425A3FB2E6022CC14FDF69B6660B37FCD4359F1BA54 +D12B1F478D76CF898824762C25A026B01C94752F6F3724C31AE788CFE992D9CA152902EEBC4AD8B7 +A5F0E68A5A0408A2F0BA71CE0D94B4A905B35F2781D7E7A2712DC62E87518BFE72A5BC4A9F36A4B3 +B1494B0C4C14705203762E0CD0B28BE31234449C7655B5D6165D7CC4A16431F7A8ECA58D25711E98 +4FF2CE123C05AF9A65D478B73739715DE62A199D47BAC65785EE1DD25AF91868F91D037C0AD754BA +CE3DC4B67F1FDCA8FD9FA39796EFA9C975DBFAA99DB70624B3563408D0048E3AAC6B4F228DC0AC08 +B9C2B63657EEDB53B46D157426A3B4B4B8CC5B4F30BC24CF9BED442DB51F3C7A0656DFBEFA401E1E +0823065499C69D51C477360FD13ACA8896A8117789C4561F3D85F3A80D18E39F1D6BF76C7876922A +1038ADAFD53F2D092369B356D0CA3FE6A27D7B9BD3985C78424C21E60F6BB46408013DFD7A30D320 +EAD9AC6E5FD36655AC6706666A76F1E426640C4B0BE692E4879991EA9EDF4596C0DDF43D4E370360 +D91E8B2839D368DA2A910AA6092337E2E20DEECF43D583CF164881079ED5A492B5EFCC1CAF91512E +0FEA8140CA3E2553733D6F743728ACAC3E643394015967DAC8839D5A804503A45DBC539FB8656D75 +2F00EECF73E7EC8746CB13F438CAFD554C01150048F758573903B0B3260AEDD78BC2EE87D201E219 +486315A4C01D95DAAB54335A4F2CAFC3F43F12A9574CD2DECCBC1858406C701EE62C281A55B729DC +EBBE74FDFF275A0A7B8B22C7490187D1839F4FF271723C140095F28221C4145C9061F9A5B3EDF8D2 +9E0DA04D9A8AF6ECD42DB897DD5C9318D636FAB698554BD9EF9B0902BFD8C96CB958773A3C4A5FCE +8A334C673206C39277E45AB50DA2661F89D621AF057CF1A7ECDE344DC7658514B4C655937E7BE010 +B0694B069FF64D5582E3A4B7F6AF6C96D056ABB20CC883AB25A9BEABB18A84F0258CA3E4F33FFB77 +9841F5970DB447969FE9C6BFDB066ACBC040648D74F553EE434BADC353450A3792EEF9CFDB2FBCD6 +07153F2EF73C1BCCE3784609F26C80193BAEF766E7CC7C33A4CAB862E6E01FC1CDF11E2FBF25FE1D +308CFF9CD924893861BABF78F762F3CADD3E0BEB38F6157CD08F1B8767606427C4A631AFC9488E6D +4D1A8F4B51ED48582BCD3089BE037ECFF18DF6175EC317EA56D4FDE37288F089532C13F7B3C1EF7D +333E7FAF8B49D95F535F60889CD7245E5CB0BEBFDAE8F7A0AC1AB7DA18F2BC06267B27403F1BAD9F +DF5F13254E96C294C4568EC7154479570E514A55208D19A4538959F7C5B2A0C9CFE4B4A40300F248 +5943C6AAB753F3F0E551727B8DA8E75305A9CE623757B36FB7D34D13CB14EE561C404CDB2D88F375 +2BBFD9FDBCC92CF110D6C35629E3040D995CD25B90BED2CE79BBDC846EAA321B1BC46DFF7845380F +BF08782D6A31EC7D41F251786FDE403A39626D6052D5189CFBB3DCFF09E81C09D0CE7D571F979509 +79B26AA4F6D07F97A33522306AD692D4D960CEF1CEA3D251A841E2A23C7AE3EA44B3F31E79808F22 +B6ED20DEE4186927394624E22D37E873B660BB8DE6FFAE058DD5817A3BBD68963D055D406F253701 +D940B5E58DAB01FDFF1C626D0D35E8E7995D37057DD07F6D4D45F152A141269A7FB81433652209B2 +B23D69BB5D8D367D95D4A94B2C2924FB83F50F651458CABCB65009912F00F485A924A61A0008DB44 +D478CAFDB7D9524204947703B822755A1301FE163A8248C8AED7073362D918D097597B24A3B579DF +FE00F47776428D2B992E1F5FAD6190ADD1C789BB9A913BB1B938EDE357BB436F734477F7BF332E36 +7C380D049AED8687B4C2AB6EB813E719718C9CE62296C24F783B16E9635A7E4402840BD26D3A0DA5 +78050C49E6D8239EBE9E053719BE94CF6D8C7942AE1660F28B21B3C1E6E15459C1FEEA4FAAE64AA7 +165824F7B939E28E51009FB778E370C6001B3F546EBB76454912D730A3F8C7F6F9EC01B9F90A2E5E +58EFF9EA99BE57A0154147C2B7A7C7E493E37C9BD0ECDEAD4AA4DBFF95D7E451246C9D4C30E71F4D +76441A297A1223FD2F8E017B10C108B0F0F67385060E9C18133A6264AB2B445F6DBCE86CA803357D +749244A6FFD8FF8AD37EBAF3787F3702764C5EE2CA7547D7A9FED5AECDD2064F7C767078579DE13C +F135CB05561B15BD9803646611422353774984D386BAD640C5EED157569356A17BB6233EB298960B +8E34209562AE170A08D15F3A29967DE067F6AD183BA1EB49A99F4899031A01410D7311BB9B7A984E +BD6A303D44CF42B40F12769D44160583BCD68C68F823DDC0D73150083404B12AAA68E97206053C6D +23FF0620231D3570A089520E928E980F56A273092DF94EB3A99FBFD877B58860231B2A761DC91A41 +A427778B06F07D4F78924FF986F837C1437B44EAD5E7C56B9CE9CCFC0F6ABDBFDBDE4A32E3FFF336 +7F7194DA20D038CC44C4413B2CAC15C05B22758569D1008EA057DCDCF4A324C924021B35B10ED632 +BBE921BE2E34795951DDA394FABF3EDCEB99B3CA15D6579559F0BBECF8DF6E6DAE427DF75657AEDC +FE180A88DDA445A5A5E239B364B8884714B0ECE259F32F1742DBAC0BFA9A1052E2B14E632B56A474 +F2C9DCA9B5FD8D62A39227CA8C208DC69E5F543A745A913641950AE0DCCE02D20D374B652E2CC41B +F0417F99C2EFCE1C23204261FD1BCED5A1E8AD4736C5F23E14482D766390B1C62A320F751CA13454 +8DBA0B08E4BA0A0CA5F6DC765F9520D15D895792BE580133B92EF3691B95331DC76A551C4AE9AB10 +24D7EFC4A02B5654057224C3433A2AD6859E3E4C40F8F0B089C846002C75ABD667C68606D7300B7D +0569753AC074BE6943AD20018835A6EA28B99C983BE3BEA9B742A76F2D7A2A26B35664D24FFBF656 +EA28D525A78298C898C0BC2DDB56FA37151AF5A08B28226CE6BF09726C37F1B0BD39DB144CBB5494 +5DC48C374BA8716F6C9A4626C95B6993DB2CCD59A7430E3A3B2E6CCAB9A801760B8548C8447D129A +01EDF187435162EC13A65C943CE9EA547C3694F88F9706AF29F5357EE59500EC080E7FB844E8021D +907EE02C344DDCB78559AD7FDA31A3031D5CA80C004DBC04BE54B38752D49DFD19F1124C059ED68F +6E85E1A3A848F98707D7413ED3DEEEA823D958CCE720B85303CF208AEBB9B799653EBE4DD16186CB +F8C0301AAC6271EF9E2CF6732A6CB8548B7CAF2399287D6AEBD5ACC7C9D0DEB85BE38150072A0184 +51D3F1A8ECD784AF8149BF932E0422EDFC608B20B46D80D3EB68D746E1EF40423CD6FA218C9F569A +3442B0E70A2D464DC59CAEBC9607333D7B8FB90349676207AACEEE5ACE8E0E9F9C5896751ED4DA00 +95D68C421D8754D665D3D58C682AAB1DD72EF9050894EB680110C3E7F02C3551D73C63CDE8B45E5C +453BC9AC1FB3145CB6F0141B8E4928351FCE66F2A5AD534E5DD8BD901CEBFEB963DE2787095D7755 +81E588D3A165BD55B51F039992567B85FD3AE92C7526E33B44B8149E57BF7E57579E37611AA29DC5 +9EC94F583181201638BD4BBEEA23BB9EF067CFEC2E436D726F401EBA897480AEF7E38B9798C6CD42 +182C43B2BFCA7D8B6B696544F6B00C7B7D0D2C70D955304A4FC8D97E317C01113404129D480AF8E8 +EC0075A94859D5A79DF5F3FDC2EEF4F0BC1113D2C92DAB9859E9944DFAF557DF43AAF42B4FADE1BB +F5AD6728F884F0D4E7671787F1A3500B00E62929147C72FED37CC222EE991630EC9AF614160872D1 +BF4666DF3B682373AB1CE36FB87C42597FF1F96D3D7B07DC8755C2304AE69B955FD2547D316E16C0 +458BEEAD37B904BC27DE764240E67A68ED5FB57BA1F9C7C4C8F2BFF745F3E6FC732FD5E37CC1DED3 +6EDE20B06FD832673AC78DFB7655D7358CA03332A18241D96BB0A45D16BF2A2B80D0A68C74C8DAB3 +F18936EF73085EEACA9B24B18EB7DFFA75C707C947E17736EB7B608C4AB90ABB53A53F44D8661485 +5D60E36CA31704053CC453F2A50B878AFCE0361EC30444F5D6009ACB5D9673E5536B11A02B659252 +A64923E738F494D6264824392234FCED4D66E0342D03189778D41AEFD907272A919AAF40066A304C +6D7831F90B347CB8EACCAC6A726B40BE4671D6A0A591DC36A30ABBF91F9E780137D80EAD43BD49AF +690A3789F39D3EBFEA9CC64B82D601B88805B6FDAC3C84C61638DFF1E391DC74FE4D08A0683BC5D4 +E6634F82F4DA357742764FFB2B8D264275F82052921F7647BD8709857BB1C98C205D13EE51C14E9A +DAD1324562267D1931B5143A2ABD173C745B7272A6FECD532B5F189C8749DE0ECD3A6B1799C1834A +414554EA6972309C48DAB44A9DC41D8B28361E89CCE4DE8AD6058469D2F603E7AA62631E80C01535 +539580E124A24E9387E3E0E58A63AFB29944207BE5929455A150AA58E27EC885CCF019CABE1B8769 +0AA7FD1F4166DF820A324FA0FE3B59F8B767BFE029A7E3ECED513A6CC622AA8CE96563219EE328CE +BD649EE99E5F108FD57646926CBA30BE3AA8E00EB4CCA282AA35C0742410477E2E5771DAB74E4181 +D91DBCF25DF36BDBDFC5AB6C73A982A390416A23C8DA10655906878AF714C3478C8A0C7534F6022B +80925A069F63834539B18D9CBE67844520A195019C15F8F858E91CC50DE001EDB52C89F06035473A +022A718893BF7F6FC0B2E6CD4C1CB26E98C1A191EA5429BAE831F464971180F5EC2CC6E6F8F9EDB8 +2E2A7CA8C5656BFBDD618F7D31635A23C73F330EC76F4543C9795600F8EA45DF62BF4E071FFE3758 +2DADBF252F2A3EB399F61BEAE10BE0FEA537C272CE850026872F4BDFE5921245147B71DAFDC8EE88 +C509B6D8AC249557725FC5D14198A2DC889A4A9ED045870F767906A226826AC93FF1E09D75B4DF79 +8FD690C5146175EF2CBED8F03C9DEEBD95AABA69E98E25A98CC96820CF1C684F02E7739F525B12C2 +72613012143FC26919B800342903624AB29219E6266716E451C9D60A4FA010B8D26B56A4C91AE1C2 +ED778E35E66B49C4DE64021894C2B39E7C883518B06E04D198B7D056A24C3E65BC9E30BF2F73F2DE +21E676A37E2AFD625220831F598E93BCBE098AD73FB6EA5CBD9D17EFBE6EE35FE4EE93BD3A75A2F7 +118EACBCCB82216DF70F56C2E82F77F072093824C6ADB800C66F0F26BF7AE513A490AC3DCF648DF8 +2E54567ECB9D6FE66E908748B7D5090910EC99EB9B222E92B058F3EF34A11918D6FCDDBE2B9C27D7 +DB19AD215E7B327689E0597963E6EC907A23A0EBFCDF86ACDC349CD8983EE83144B5B8489545AE2D +ACCDC8E553FF4A1601F42CF82A90D571E36193BDF4A7893B2637DDC0C73EC0C21BDC4BE1D02BD257 +F4C240DD6AC53A321E13FD2EF4343B11B9C8152EC02EA6C4DBF7F66C9356647A948CA9E26672BD7F +9B828FE109600B1638806DBB95DA6AD0F78050FB13AA580139C7198B1673D4AF9BB610A83A709D3B +7D7B01AFFC0A740F03A5E2E3EB7AF145E3338D46D03A83FB82DD6487A52F9494A89717FB500A26AB +C949C51FE25DEE8F78E199AA53EC1DDF9D767D8FDA77FA32F19200BDC539F00A23DEF166D97F0DF6 +64489D932D115523CED7460212BB35D887FC3507C2592ECF622FEA49AE2F04299A547ACEF75EB0C8 +8ABDFA7A042D5EE4C88D716B00E79C40173A3F7044546D093B988463623DC1649FC6CD645D01E102 +1AAD11911190D0B07C0A66AE7F9F9CDCD0D841A976A8298C26A0850FF4FD42EDECC37B97A19F7240 +3413098A81025E4451850EAF180299667A392B7D2E96C4068CE110CC3CE15C6E58CBB11CE21A3E9D +FDC88ECF14A8F2D422E1CFCDDEA320DF5CAF93E6F9AFACBADCAEFBF542775D07EBF09A96F0162438 +62662AB782A464DC7A96BAC2B0F0F043E83690C3B24432F61293A1C5B3699605EEE8339AB079BA1B +A7C65ED392B6E94FF817CC25AD32E89C95A0667F124F26B11AF5B45A9AEDE4F443429ED30130D7C4 +68C940A7C538ACBDEEF77BC084F8A24FD0060BB9CC12A710DB9DF03CD381FB6E76F79D3DE40DEA4D +FEC56ECAADEAD68DF4492DBAE69EF1663E2CF90614871094BF6F0E1C9FA0EBB2D34923A19A557BE9 +54D914F35BA044FC800D822D88B5E70CAC27D6D56C66AD6CC3C7647DC679C8D3E1D39AA8282BCD27 +982428F5FAAB76EB16BCD26A1685C044E3C7B87B3A1685279DED690D76C0F1C52B76FD13C419165E +754BDD7FEA75E26DFE2B916DD0CD40301CCC945683C8E1F49A03A0DCE1974A76B754BF04D36C2693 +969FE4C6C39D60D995738F1DE0ED6A7E0B80B40BC14B440B6B8F1085E83995E224BFF4EEC6F67EAB +103B4BB6D21F9741932DFFBE85C0BA3D2AF925D670318D1157FACAE9C09B3AAB5B1FCFC889348207 +8D5A3F7787C699C420C9BF0883D3B8B3D7753E9A146175245CA9E2EE04FBE258B6E42334EF141A41 +D68ABA286864E72F0E4ADF41C1C96E60E69320E79211984A734392C870D72B8C236AD631672AB9F0 +FE48EF2611740799DF5B3339BD49697C4DFC0557C1022AAF15C24FDC54FBDEE2129EC70473A17EEF +D202EE43A1B5C7B78A299B6EC8BC7595FDA6BD0BD22E025E8FFD89115448D99FD27BAEB680A22C59 +7295E33201199E9E1E38AF099926344D1B7CA626166CFFBA2F3D1C15AD63F0C6035A5B9BC5AD644B +3D5636C2FF3B9403AFFC3AF0460B328C390D3B416C7880A0DFF10BF512BBB933081FAF4B2E06C093 +E80950F08BDEF07D56BD042433CB0A0C72E1F17692C9F5C7AA97C31AFEFA39233C62D5430F11DD31 +478E86A36A8AD3817B0AB397C8D6935960D905994ECD2AA8299D248AA274AE0FD1F1377F0443B37E +67DE63151184DB5EDDB4DEB9CCAC76896BEBE32E30E0F3C2557429FBD4B85ADE7829428C5CC95CBE +018C17BF38FE4B26B0AB736FEF35F6E3DACF0BEBB3B795D7982075B75D87324AC28D7E5B446F04F1 +0A001FF191A5FDD10B4729E57578FC42C26D473F22C70D5629AE05FC33E50A4EBA2C5D4D63B1D147 +9ED7B8FD7A0D824413D06437118C788543A21520653572608F9172CB1D1AC529280AADAEBB5A4E30 +AF99A58EDF2952BEEA29F366FB6FE7A804DFB1D116B73B45033E9E7E9767A9F41F2FAA76F97411D6 +420FB211B4BECF6C785FFEEBD90AB932E82EB0AEC7ABFA4A7AEE53E2482617576EB28BB2A3C7044E +15F0B6521F3B073021C3CE55890951E041EFA38937F2C0683BAD1AF255CF3747AF2F0B8A92BBE44D +88F7768D35B8F4EAEF0AADA3A42E26E3E3EC25E286C40808665B80C6265716DEEFAE3A86C9B54D34 +74285F3BA2946302A065B800EC7A9694B0B715BC86D3EEB649FAB8A47D170550D9213E8B8E9367CD +FC8527955263AB2AA55FB7ADB7DA9A4E727E3E69D9C7946369CC078DD7751DCEA1C0601C57F4B5E4 +48BAD7F5F8A919632178C77B7B5F95E402DD808AD59EDC020D82399DBD3A9D9F3FD089B0909C171A +940673E361F5728A89DB3E2CD0AE2009A6D64FD85ACEF62F8B42F009BBE11EA0AC14525C2ED72E03 +0DDF4F670D18B94C07C027509F281F7B59D854A5E46F4DC544BB320C427642D84B914A2E5043B1B6 +FC94802BE925FF260775F6D17A5C55C5A5D5931F88E3D7E8485D8026545CDED1DC96D1ED7E0088CA +ECBFEB99F110A5CCDF7EF3B4190F3DA6ADCD5A46DB27C0F076B558667A1B9ED967363799307E77B0 +9859909B4E86A27639DF3A8C0B193502FD22C0298AE0398E7D33C3039D7878922AA9997C93F29655 +5F5D7BF3A34D46BA5592FE2DAC5A32DD9945852962457B81DE4B6B9050660EEE1A0D95785D824A5B +DEABACAC8A8A90E7303093F0DFE57ACDF0EF2843DD7497B1B80AE7E883735D9BD689C84A61DE8127 +3E2DCA2F64B00B62F0FA3D3B5359241525434847763059927565F4D98CB8AD1842E31488E4C1DC58 +4BEEAFFE1D3E76AA2E6C81CE2DA9F00DD32841412695C8EE17EA60499E99B97D30C32DDB0B9E473C +E65C259949129A4682DDE5DEAC0611464650236934D7C57D1EF7E8B5D9E5D7458F0FCA9795853710 +F37B5C24E39D7EE92B2D4066D533A662AE2B063B741559B24AACF24DAB6FB6786F639ABD8B34C7E7 +AF20E5FC999BA74AD93CD821B545C2531C506719605A64FC06DA8907550087A4599EFA621DDFEC17 +B904B6115BF94AAFDC56F3570065D75DADA1AB177F4C333A04A0119A89BD209DB0CDBC5DA0C8B99F +EFF54B2F4FB4BF95AC573EBE6D5CC8110E6387365CCECA5630F5105C887DD5803DC1376986456634 +C3B3BBC235A72AF168CD5B350E0A8BBC303A2CFC37FF627F4697C0D19BEAE28FC3996E967CEAC4FC +8D9D309E2FA65172E899285BAD3F8B45B38C9C2BCE94C31911079850A040C08789EE377B9E652A10 +01EE4F44420757358E183D48EED67C3008E6F05C3971C732B98ABC24271527B267D8B10735CB1FBE +773E33FA51B5B472E402676E3590C7BE28BFFDE77AC34544718A20833C9891A176AA3A62D686E072 +7AB2150A1E77FAD5012D0299593B0222CA38CED2B9953B1E5893F176132F1197609D04F2F1D647B6 +F44B2EB0AD94211733F226B570E9D963AF9A6DF04FDFA26C0BDF31EDC41DA521F9D0090A9FA5DD13 +B9D977329F6412815A8C52C3290DD42EDBD312592DACBE0BFDEA209F389DE8E4B5D8ED51B46F1557 +C2B50098C2262D3DB298E12C0AC3E03B82CD2807CE04E109ADD00EB181D701E4BC3622DE8A86E977 +3D6C4AEB830F3283BCCEA663AFAB740B546C3577E9182EFE660AB380F0504590CEEC89313A608A29 +9F9DFFE22DA6296EA3E39857D7229885C78F097E7E7845E6C908A0570D4ED0AE320DFADB7AF87E5D +F85AFCD1B271B672813C0A0E2EFBAC5275807ACD3A2F09EAB95DE6F571E3F3C827FB3EA9DE89DEB5 +4B8B14770305B23EDE569571D0BB8BAF4811E12A0DD5BA4809818D2FE088DC1CD4BE72EECB36C819 +AC25B41BADFA27D5839D548CEED7DD18F5F2BF69EFCAC0ECD4FD942995E930C147E1A7AD93628180 +E62F20F3779824324C5A1C35ECEF68DE30BF5DFDB5DDEBF66CBC459B2C7FBCF0ADC0274D137BE67E +B22FA117C85CF7D52BBBB4CA5F9A6F73AFC23BF2E23B4B1EEACD33DAA3187F1D104843876EB44322 +67EDDDED02B6A507D13E3B9F012FCB8C9F0D14D951415BCFB20B3211B5B9860B0C6826BE597F2F9C +94DA2788E65107C5CC77CE1265E33DDE9378AF9019E7E68522997470A52088253FDCA09AF9F071C2 +988CEBDB46B7F7C8D08B5467A1B3FA0EFC552C5E20D4B9D565AFEF9B520FA2F544E802EB8E8E0C76 +21FF63F4A5C0C59F94E9E1731D8F3C80C772805DE264C7501E705BB21EC37A1379BEF8B8A9E50EB5 +6FE9CF9C10C9D25CBDC68124D2133B2DB6348175537EF872CCB8B1787330A5ACFEA87E2BE6615DFE +442EC74BBED30021A0437ED3E9DBA6EC49A824F0374B446271DE6E1B16AC74816F6216BAB7329725 +8CFBA83C178F5EC009C57404391898BCC3314411F129F12D68218AB6D0BCCE2E5AA9AA1D5FEE1E2F +0AFAA4BCC3D226C5512B456CA8F28DE54858F18DE2B30AB4FA02840859988BDE7ECECB4EB0002523 +C6EC40BAC2E7ABC411329F803DE2DCE1EEB354E4E6771E4328ADEE4E713AD248BF4F91108C52B169 +140F33D5C56F1EA2240E7E182C060187E29020139C373B4A6CC4D2156F7C15590D3C07C98535853F +4DF901EA9F2C66510C190D9456EE037DCDAA428D433CC2231B2B627CE2B5304B6A4630576BC48984 +66D7A8BB75D53ECA10C74D434A4E50B1A706ED6C7BA274A9CAD5D929B9BB8A631825A9C32A8F468D +578507DF2291DEAB6338ECC92CE8664D4B1C211A4CCE186679B6C71ACD5655B97ED8E552B09C1C85 +387749406C549057DEFC059CC85639203160B8FF05A48F7D5C4F452B111891846A521674C0E2734D +50B8C7E7B5D9F438C58DB139A6509DE3495388E0D7AD24F64FE73707C7BFB8CB06FA0E0C41346B98 +220E007E28515428C1874AC996819F16CB152C16F89CCDB3F9C83070AD90337F1823AC0A48B72749 +C6C29A8FDE1EC2E76B0D29FF711891EC81D0ED0B3349E9FDC413047731D70C33E57D2C4B637C8FCA +B027CADCB3E11F94F61CF3A56E4D90E8550F456BB90638DB6118229C9B74C9533508F343C0EB422F +87627EFB562C7730E1A804E3E4DC80FC0F199CE210045ECB1E3313C3364F78A500A8ACBFCAE0F7D6 +56FDC8B1BB95262A32ED7562A62EE5CF989235D1E641D726F34D215242D44A946662EE94E765A3C8 +75557732FB4DE1CC2699202802D4A5D99C621478C1C6D583FEE8CBDAF54C73C8C17BC73F1B414EEA +BD901409B83E98D62749F9E742FCA7C632C176D323E72FAB241D143950B87AFCA5B7B75936FC6638 +1FD0E537C30D744E970A08636D27AE7A277F3838BEB7D1BF821F331D483FCEE4EF9FF98F350B5B3E +CF2D6A5BBE95862FD5DEA6D3A5322DE6723B5201FF81EB1D1BC5BD0F77CC1737837655BE23E0662E +AFDAB16BC0635F40DA516BEBA8128C414D6EB5F5AF5F98B8C0129606FCF536181E7A8ECA563BBFDF +0AC65F1932F1DF20DDD6739F7B1EFEFFE282FB6DF35222E8148FB5968BC7E40A582D7B146E374270 +D3D014849E898E91997288BE44220B6B6E431B7AE3F2F7C5BF3E3444F5088F9F42B7F676EA14671B +C70A84B77BC275E82516040F7B9DDC582C0FE335852A83C31BE3B3F716F17253AE57372D14951A2B +58F69C2DF7B93052823311E4208A72C10D0625869BC5F3808D231E86CD259824D7E6C7669013CC55 +B61E4C20C0041C35BBD7F1C291EE7A3CAE016A8C90C56F22C515375252FC3E188B80940816EA5117 +88A2FC7AEEEEDAB9E0A33F2C82D886F9BE029BFA1348DAD16874751460DC149CAB5189D061E7C804 +1939D80A03BB529E3580A235F8C37EE25C503BECB9C31CB91904BFF4460837A1165A7474C83B5773 +5945BE04F3FAC3381310E4BEF8D8D4A7343B75D23BEFC58060C48BCEB52566A021C197ADCE5FA025 +1AD31CF2F90CF6A72122C38FEEACE6BE36176B1A990DBC42A33F0BC0090799C2F6C8AE6990725562 +B07725B3DD15C9011205C28700DF82AE4F00F9842DDEA3BB5C15D3A4CDCD18E692427505D7B24CEB +40CD7AE0D81A4C83A0F9ED579F924FCB19D9D017E60C6500CC64572E0161EBA57EBC11A5932F24FE +9F1AF444B3C657AD162BD39682D23D6355EF5D84440A124138CEAC85C926BDF484AD7B976D9A44AC +6015C25354DCD72A889474F31B8BD3CB7371B95A9729FF0E46EA3644477AA4C11FF5913D917B7742 +065F0593482B6E6EEC9EE63633A6A963819F3E6A2920136B17C982353F1F071B3D585DD24B22DE9E +EFB2B34F776DA85F85E9E107D16960AD67046476ADEC509FCFC64E8AAA6427935FC231C817A21C71 +6DCCE088EA4186DFF0A3A7A1C367615B8E7462DA176C9F9EA893DD04E29DFBF88679991AAB79A172 +48C06E2BCF3B91C108E09331FB57D94BE85EDCC66DA8225FF4B38E12F8563398E62622EBD2EAB098 +691EDED4D5D7AFC360AD6F263C043DAF483DA05CF70DD8BA8F96D2A5A87043DFACFBBBB3F8A378E2 +A896897DD48D8B13888A023AE6AD778DE5FA991907E731E5C5B0A3DABDC360D77CC71C59C34506C6 +39C21BA9FF7A9CF322C21B457D62F20448BA19CB765C4C0AE7CAD9A972F60ED60A21C92AB665537F +EAC8738AF8A27D3946F759162731F62C0A657CED40C66B8A9941EC2559074279CE0F6E10BE3F44C1 +D517E10D85EDA6BB6D097F4DF55A3DB7D50679675A781FA1FFFD6F1A8349B2870C8114A05F5F7645 +3B38446D57ED63FF8731661F0FEA79033E4C8B5CFA29CEC43355780C5E2EE86CCD449577EBDC0140 +47AA5CEC980CEA8200867212DDDAC234BEFC9FDFFDB43DD32F44883EC6F2963D4D28171E19E144FE +1BE2B8FCE99016691A00E4EA594F94E973E899D14DC44486BA6B4278DCB292FA5C7E6D73068A3BA5 +1557C3F072547E7F2F869D4E9AC03514276EBDB0920FA04E67E2934A250B1A502A8D06A25037CE59 +920D0E136C02D5DDEE2EDBE31A38BA32C4122AF89F295ADDF579FBFC72391283DC1914E9322A63E9 +44A280ABE7CEFA54ED2EF42B79FA97EF21EE83EF20CE34850FB66C378EC7C08B2BAB924F58FB8123 +FEFB43A385BC1EB922AD8360750FC1B0D8A303AD19286308B7A39A5086A50A8FDF7D60188342A9F6 +42286540945790524174800F8C44ED71306ACC3437FB49D8FC43FBF8E88103A76B4A92D95DB9B45A +FA067E31EAEFE6BC818D11D7CB8566BAB418B596A7494FC7326AB3CC029D010917B305CE585B194B +C5415088BC7AC7852A6D52EABD223E2634DCB29080217F6755B023C591F08C7E5D72267664136639 +9766EDF511FA744675D473AB37BED0ABD92E04049AA9008014C5EEC1B0443C6C86302CF6A3C38BDC +B8D9E0538E349BD930707DFB002700B5EB427192AD6105E01B8C2FE488CF617E9EACE73EFE3BAFD0 +F87CB57E02AD31627E6439188006655D6B992B393F4858254C2106FE9A3F0EF7347347C66C94A999 +D49527A2A177EE20A08BF594FF1E08CB091D22A8C1569320EB5145FE4A2674151750620B5EEC822F +A48C5F8B565DE0E18AD05F99827FF24D3BE03B007774F25284844D0F1F8F95643646960F303A831D +4C2C4ED9E0664C1D705137C157671D62179B47FA4A6441DAE99C2F7C8765C4C931EF345CD8F92D11 +E290B2FDEB1B0FAB4BA661A4511F75768808AC1DF2FE79BC285B976D364ED25C64EDEE62E8E035A4 +B79344D55B1E7E2B43AF1CF94ABFC8D0CA5E89240EEA231464449B831F1B3F9EF01AD07B38F0B402 +712A0346892A1DBBBCDBCC827220CE7F492CD7471FAD35B43E71CA14F1F1A9CEA740C4E1A98337B1 +5EB97B10AD57DAA9E9ED134CBB4614321D548C6A71D8ED95900E7947A7E4331D7DF3EF367F6DE8F3 +113A7DDBEA0F741F9C189B8B586B83671475A492AAF9994D884FEF3A646ED4F272668DDDB05EA230 +399223AE63088D636AE6AC7CE2DA06CA6AADE9272FECE86D0EEA8290B927B17450DA6F34A3D566E2 +096300CD8D5A34139489228ECFEB104714FF907A6E1D3DE35BC0FCCF45A2781AFC5562CCDB627E06 +F23DDBEBD4212F36C332C4A5A9498032213DA7C3FD03FC4832D1F2AC9EFEE3B840BF8356A16E14CE +989C37E6234CAC7A215DAE7C4DE2E2B6D9A876F709422113B503556A4BBD0ADC107B6B639F1BAC9C +6FAC7F4092D23C04EB8684C9D0A5F184160CB660CF6E8672ABE1AFA596EA86890DD22D0A406B2118 +9BB626943F378227132475A25710B75E5B3BB2ED7DC0412A0A079E2AC311ED55AD8E7B2A1A55FC84 +E62D61398511B70877F3DDDFEE5D033A9ACB66899021951378EAFB9E9A799AC9686FBC2E9E9B3A51 +6DA9DECFDE87FA4BC042A5D26C2117E29AEE8840B18361B7B38B21493401E931B431EAB1A3371628 +EF13BE5A0C64A3B9F8B6A29D209884706D2A9AC85B86E3839706269366EC7E53F31BA4219F741E55 +21E42F0A10B7B29E839B924AD90861FDFE3A3D446D1E32C06E66A5D7187A63E590000D1356718648 +7C99BC31088356C9ED29BD80C61E442B81E15657AB191E90EE77A6804C762C45D3C1937EA17454CB +D76D781A0C96A0914B9DE3D3984BAF6075D3A25AC69BEB34CC413D26C39824B83F853DF864C269F6 +8443970933FEF93208BAE4DB3BBE90DD3CABF6EAAD2F6CE664CFA05999FB1CC406A3502DAA6C8145 +3C69CD218B18BF0B9654FC3637EBAB8007A2EEA6ABDD11FB338E89FEC84B344857804D028F849E44 +5982E294384A442390AD36D7C4182EDD7A05BE6744FCBD3BBE6FAF4796063CF42499F2DEC302AEA7 +B64FEF6DD74F278FB5C2897CFBB87FE03538B2739E25EDEB806142E0030F7413018EC1833840BF37 +269E21DDFF67B8059BDC83DE9C6461F3EC2BC2224D2585AFA2AABEFE7D3B2A899C3E08F00AF2A707 +55C145C6BFFAC8F96B54EE682C3C8CE01BB44EB574E1C721974F236E8A2AA28DF0A4AD285EC4DE58 +DBD3A2FAC8A20173AE84CBF877559ECCEC64D9448F8CA0FE5272BEA6543738D5EDD54B73AED3C4CD +172F91C4F70616B36D37B1252D355820DA88536C829B1542C1EF4F76375360E7123525744F55001E +71F5AB1E5B39A5248CCC6789647F1FAE5E989A8A8ECAA2A9116ED69DC9B9AC25A743F70AC4B62D72 +5F120D94431528EF8C74611F7529BE325AF84663989C219B70274584D1EC4485E11AEFFE4A29F534 +6912577C010FC753DB47204BFD9E504027B335EEBD952FD299E5562407234C5DBF0B839D1010AE10 +5D56766F87910E44AC7968842833A302882AB481FE0AD444911F7EB8555DBB9F3D062E9E9FF3B529 +4E9A3B3AED4AECE34C816BE7FBCDAEBA33FDC491C725F13801A6BE162F7F8E99491E8BF77F2D2010 +D9FDD78BA2606A28810CB5D88CE00218D49609C62E96BBD36110DD611C3573EA341FD2AF6FE0C932 +58565D4A6C88CDA3951E287168A478F5EE074BAA9676EF5911D8EDF8D66A7E8F3BED1BB786F480F4 +423C3D3CDED2E80AB76CF5CE5A53B092A44FE52806D8EA72033BD3E7BCF1DA9C870108F32D8987FC +25E7D873985C3C4882A5CDFE7A8B51F3754842FD6F1FF6235DBB88F04989BB2F2816A476B513C10F +AB6F81CF115EF5044D3366A4E192EF68D385B6B6F4BDA7D14D9627F40843035CA80DE49F9DB52CD8 +714F769DF97F73E3152E8DE34D8C37163D485750F8F4E37AE8A3C3BF54D97BC3B2978985B557F9E2 +9F0537CB743EBFA0B7864BBEA9C126F13C02CECFDA50A8019F90900F409B6D700CBB9EEE18FD1952 +D496DBFDBAC800FB83B37C705367F182D91B21C7F6C242D8735A75343C84DBFFE583337DE2A95890 +660584B513C5BEB7A0926BCA7B7DC3ED4D080CC3F1264A4215ACD35DCC62D896B4354F2F7254444A +7235E0E3D53D02583710DBFF2CD55AE5E61D25ED1B3C3B6708E5BB308A3D658F043C26B881C949C1 +0940AF6BDFD2DB0D544DE119BC7F7B03451E61FE18845000D350AD6D04A09D8E3E999E6DAC6AB73F +818A11EAD345EAED03BA083A6EEE7E9CA8CFB760FCBC88B8DBE0887F79AB430913604F15272F9C73 +DAD19D591B40CB7863414A8FAB21C41F80A4BB0A3AFD9D4B1322487429149470DE62F305906F1244 +2AE20521BE034F159A7E7EA211A2FC6193AF59CBDD4B43207BCDE8697DD515459F80F8EDB982C97C +05ED3996E03891DD7EEAD505F6A71A924CC1CEF29053ABC8F0B5F56D0DA1249F317406822C225863 +ECEED46BE0072EDDFDF5F63DC8E94FC119087A66E394A653D5AC774407B006B35C406E7EE4385565 +78290F8CB8B131B88BD78CB87A11CDA44C5A199BC71388A81F2F30E2C003094E793969673E8D0906 +1F4A3FAB9B14C52EC89BACA1C52703F000A967EEC445E5423D3BCB9253D91AA64BB26727C8461FA7 +FF61022B4C6A9E793901D3407487B4962A16B564CAC93D7AA28A22C28318F69770E12DF9D6CFFF17 +09EE0604890191217696AA52630231FE11153761310A72D60E6925AD6B9D63A66047F32B9425C91E +57505CFFE42A90185451297C2CAD408B0CA4F8E923EC26A3D5D66448550AFA3CE3BE9296C8149878 +F853F2A7C3B2E98899C8F9B47B1405F96D2B22E9F1CD62A2945AB62F67AB0297982809A829826BC0 +F24A7777508EE0A71BA7588663D8118E3BAC936A61FF4A628EE96C0B9AE05072A5A4307E68EF2C3E +97A46EB31A2A7D4A33CE9C44E1111D73D9D3C6A0F22F50D8C22153C2CBB5BE187C0F2F37708237D6 +FEBC445843CF88F4C3A249B39DC971CA003A78028F8A2CB3BFBD2C26CFE457A9561350AF93E60295 +D21E1C2024312B1F2F76F2EAEB822CA72412F860F5A87DF705C2F82E681AB9AF45A19023E02538B6 +9E0F273BB4811F07D153029BF988D3EB66358D987895B96B5EC4C24F3409C1EE1B5978B1EC8F3E2E +75FDF2BEDB0AF0CC66481FA98ADBA8DF4D8C8EB800F88C7776BACEE6F61095FC2CA891574F309E1C +0C27E37D8B03472DB3FB8D6668E286501118F7DADBF1106687AEC6DC4C162D22E3BD0BD42DBED782 +BA4707ED5181DAA9ACED58FB358DA0AA9B63AB4394D42360C3F5082A6F168CF41D381073A1D99CA3 +3BD97F62446D62059BF4A8616A8809147B739400362748EDCF39FFD2BF2C16D4632C06A5D43B48D5 +0D0B98E4449B4F1D39659DCCE8AC72A2E8F32487FF8686E550299F37A6353A8E558D4A8B074C2F5B +864DBC8FA3391E3B5135FC738E4C13868D67234489B6AE382792FD5214D5F9E5249AD65C433C22A4 +D63FA7A36A48A339F443AB39C23D2025741186C8B18AAE9952D41E25E930D6905B29F85A4589E9D3 +95FF040E3F72FBF29650D4DA2B6FA27F60DAC4169DC9764021F4E7E094FA25F6B5AF25602C593266 +ADED528EAE6C967E66F0BA05F258E34CF6AA5488D3F7406C2AA9D9FD9C533E827F26862D348B5223 +A690E40953F337352C8245F1861A19E5326490F2A9742917B5546884E8036DE0874363F812CF6FF9 +574DFD5BA77A1DD7C5778B2B9A2E22F2482744E2CE9323E590C60FF02412CC7AB0EC30D0EC857D27 +A4E3D8B35FD69FE28287B2160AC0BCF645A0654403D26B05041654B17D82928044BBAC40871BC3FD +D8EFC3207928BDE5D66926ED199017B223AAEBA563F2723AFFAB737F6482DD269F44ECCA3B32FA03 +FFCE3ED882B449BEE196F59E6616EF1A2F08B42B1A184727D5BD96DF83972BE1B5F8CF098F61B84A +B5BBCBF231E099CFC07D4748D43F129D123FD8051628564931E43A70BC09BF20AE2C0ABB009014AF +89753F91ACB574C5A218A47A02DA3AE44D3F688F9D96076AED9EDE7388B2935C01FD400BA7EC9574 +96E317C6931E3ED7078A53CB4CEE4E56311F4D8A368AE981606AD7E9DB0EA2A10E079476D9881596 +8C9675D9CF15E29B9328600FECCC02B484D8749B4154D69CF90997AD650D881735AA71289AE93FB5 +8C56686C1F9FB0E696CC0285A1A870373E5DCAC285A1B4FE903CC30B8ACAD2E4613873ED77D813D2 +9FA4984A7530F5B046A71011D97CDE4BAE9648AD54537A1D87A5AF1B92560DD7064F3EDD3CCFA7A4 +FBD6166D067945F103C13B3019540C3FE706804A7A00E1D28A0C26A40AC5A8845E39631A00099CE9 +88C3F12C8954EA84BF268D99E2E13726BF9C63474A5FC874CBF723BD48A5461177789E11B4F1CC63 +0FF4D60DE4F01422C1029E664782232A8BEFC38CD058135C79E015A55BA933AD8D446A81051D956E +429779863DBA60F4258DFC62D1DC3CC3862C5D310579417FEF4D7642ACBA8BA5284DC5833F581150 +ACF91709D6B3A41395960197AB43E63B8C6C2F745DEB4647E298341F3E3023D7DA22509340E28140 +97A0C193493711E60C5DA99C464D8DFC83A61291BD0E13AB6A42C81253C0F1B37E71E7706BF2D662 +76C60A3A7DECCC58E56524A200F8C6C0512FAD2FEB51F5C24109E284FADFFD8484A7A053E8EA544F +AFC1C7BA21E4824866090E542B8BB0BE603060A36D8A8CD64EC92D6037B438EA6A7F3A23E9608F29 +02E8DB10F2854D89E2CE6F093E6804299305BFF3D1601C2830F793B92CC2542A6CB0E9DA602E00FF +DEF234702D0DEFCD270F2984642F13B818F65BD407B61227A94AB11E7ACE8D40849808AB3A7E6EFE +B2D4F3B9FCD233B8497B35299DF28F651B0B2960B4545BF2C05952229EB1CF676DA761995413051E +885A529957538A8B2C6BBDCEDB2A3F4104004B880624C2F55B544EEE8DD29231386492598C2BE995 +6E7BA78FA75FAEBED43807FAF072839BAA02333D38BF4A59B1F3ACEA5C7BC188C0BE8A8BCF2069BB +E36BDB2203EAC19D249C4ACFBF8F9717111B7F4637D631D058EAE0CD4E4E8A9F0ACE1BE19B3A241C +9C6DCE2513690473D8F4A59E1A7FCC926885CC324086981FE0C6AADB8F48116822C59B1E93F53829 +77CC88B82FEEDD2B4CC6095691FFA1419D5A6850D3576C4E705C676850D0BCAB84D52791355DCC7D +70A7F5CF603B4D0D7F0CF4F25AEF44F21A2D627D45D306DE0BE1C794AB90DD1EECAB2D61A115D3D4 +AFAD1914E808B16BC868FD48208E1F915B0E8ECFD59AA5895CD7ACF95E0DCE87DD8B12F683C8EFD9 +625CD388337C262013626A06ED60556FA70A8D425D36B48E430758AB4CAD34A656F457A60E4DF933 +07C4D5B2698501E6D1F270CFF70E47DE38A5FCA2B04D7BEC3A2945EB93C49F1D366835581B42F43C +C99D71F13F45A9FF8D12E26C43E1B994BE5AB44E5309B0F936D78C93169D666DDE6D18E33A5E016F +32278897FD7E76BAFE39498C6A849F4A6D882D109C40B42488059554CC95530FBBAB2591DCFADED7 +3EE5F2F2BBF17F4A131B8126F9E0AEB4B379CEE4EAD92A1BB29E3789EC19671E77558B4DE961629F +28B49DB4D8FAAF541D23205844EAA801FEA468D26F32BF9CAC30BCA244246A55F600BFBB61C5E8C4 +10CA07319DD094770DFFC1CB700DF67097F61C46036353C8AB3A5E5198445A194BF189E20490E970 +7D2C03C1A003BC782A66841AE5DDEED2297BB6DD019C98A66A8F279748DA39C85CE2082D09210EAF +CA995591A1A3DCA52EFC9A752DAE0CFB125127DA2932AAAAD7E9850AD78D48304260B4C270EFF12B +160DEF2C2B8E30E8C137975AD20046F37A72F1355F31D878334D96313D330C24EE7350D4042AAE2F +A345EDCEB133121D4B39645B1D6114D0597C3301B4CA56DA2B4A457D7A51BD13B7AC61FE6E1CD451 +6253F606FD4B57E9F4895CAC93691EEBC2AC992CC5D122DD3FC6A9EC8FD337CD402F03F901CE7986 +2F4F4E4500617DA0F913E357BED3ED04F49FD61FD1C66606CB231C3B7A5042C7C07EAB2E02BE8CF6 +AEE5E16B4AB725B5FB5D01EAD4887E365BC2ACA579BD80E0AA686E4A08DFFC70F99132353E3D5898 +5205807271753BA3DB7264C4567DF5FE999514F28E1DD6D3E966D8810978B140F8DD9BB259078A47 +013BCF247C37F543C0899B532F34843CA56F18F688B42A12DDE2A90CA457860540B6FA138F753DAB +E7331188ED6F535480FBFE934F68EFB1C9C16D4F11EDB35F944EAE63751101928EDD0E7AFE64D7C8 +5E9CAFCBA88450DEF9122A245FC1ECCF9EF8DD94EE6A70CF16ECAB39B52597AB1E8C47B6DA4FF0DE +C7D0FABC84DDCC8C652DD7C941DB3FDCC5F0542A8F433AF9FDF4D393E123884E1DD5D359D46DAC61 +0694020BAE84B3BD4C068E3BC871BE21DA13571BB61387E207926769236776B5B31A4A462902966C +DC3D92BE171F10EF8395D2402F0C492A3FE55979CB903CFF2CD2319CE4B46481489E798A131635CB +2E70147193FE3C8F4570FD01BE10E004B17341C4DB8B029BCBBC45D31227A684E5F38F5D6F0821C3 +ED13D31DBCFF51BD759C84A98145FC86D82F871D2D83F43C3DD7FE9A064120338A7BC63A2C60667E +25B50EA1C267174B334F4437856295A6B826F54C3EA9ED39CA6909A0F6D9669F1E75A7A05CFBE7B4 +2C330668E311872177F0BE3A9B3EAE611EB48721AAC2F10C3CCD897CEA8A136E5E10C2337BC5EEB2 +FBC1A3646A6B6792CA3946BA5D4D135D929547042A2F0D0A202A4D86E3F7098C823E7AE4331CEC6B +607F6AA434180B4153F6B10DCC914A92D6D0934551BC9DDB3C78065B2177B264216AF5524D798AE6 +2A90D00A70CB36C5B9950499163C2C1B04339FA76D28E03A4D0C80FCF7BF29B8A188C67EAB6A4BE7 +8C07713C7EE09043301B5BFD60222DD0D0943180AFB286D2953A8A12661986A4812E2C0DE5B3E703 +DAB34AF0E9306A5711D286AD09A3C6AB80841491BD0E5A1D1ABE1D600CB494BC17EC4F74B45870F6 +AC41EFE16BB6C87F382DFA4B2B8DDC9C2E912FD139A2FFD5C92D836F3D749EEB985C62A376849751 +A6AC56B1F679A85FDAD9448DC7A4CFE323AFB540408547C53297DB6FE9A0B08901BC285997934A2A +1772090FBA175CF4660764B87A21A738519D5B619840D15F45DF3589E8B80A6DDF1F395B65345869 +58C7060DC131700DFF6E25962494583085E6F8BAEA557A5666E6634E4704D0C07BB0A2EB228A7BEF +EB890B4EEC638303B8005BCCE922CF3A7AE627206F2946A142B0095FF960BB8B8F9C975B6FF07479 +2D5C3DFA125B7BE7A8356D8B44E264AF6AA582DB84BA09D2FFDC2213903FE8FE16DE5EF61E518DE0 +6A29D98E217038A4CA4D219E4F114858CA493AEF0CD6495A7C5EF1ADA06AB543051B1A5213952D46 +648BE06D15B1728768BB853CE32943AB0988D172227780CE82D1A1D297D0D6ECC51B290E156645B6 +9BD54699940AB17EBE10EDF258BBA6BFEA39F4F0B066FF6B3FA16C7C72F2565CC028F249BAA4B488 +B48A2513DCC5D1E205FB874BCFE45612DF4EDAF815CDD53CBB80842B429B1AFA32D35EA58E17F4E7 +252C2C9A737AAECFC25FD8CA5520D3EA38AF71C61B88F31FB53A7F5369305D63495ADABA455C3C4C +35D9FE423A00CFBB278CD482D3FD33BEEAC1F359AD9B6AA17D60CC46FAB670ABFB3B2B4161B9EB9D +949A06CC3B734F63DE821FE0B8EA065B6F79C0601E46E8CEB6E2DE0C052771E0EC7012063F1AC46C +3FC454767469225AA266784DD77256C6FDE25D7D857FCEF2562AEBE38A9A47063AEF91449723E680 +B36D824BB95BE95C9802F7AD4DF1AF37B4777A1E116A7EEF770F0A499BC9F9675A775503091D4EBD +F55118782E3E54AEBC67A0545E8D75BA5A482C6CEC50595D45AF041664689AA4338C1428870CDC15 +BFEEF788C1D1E87B3389103225E2619120B129FDF048DF3F5DDAFF1CE87428E6BFA591B91D82720C +E4A72FBB952403435248657ECD5456CA814FA0ECBE70BC3D391AEA0A195AE8FBE92D054AD0F3E549 +841ACDCE59DCAA9DAC20348A1D05DF7B4127C75F4C6607EF8501F741CBC96AD8D429A4CA8034CA52 +9AB673EE706F1FF51DD93F45802031EEBF8F3AD29D2A74888B1CB344F88ACEB9B4DEA13ECAE0A3B7 +1FF5CAA38D0DC484B96E90251D56732B85DB8A8DBF10E0A5921121CFDFDB6EA9CA45B98584401C3F +346BD026DA2A17E75CEEB74E48667F9BF5C538623E88021F0C24EC7D082C3E1FFDE5A6F68F0F3F6D +2B61547C4C14614118F7942851B090D6C8D80BA06D9139CB5AEB0EF77DEF2376C0BF602FB1083178 +27E86DDCC00622438FD7A268C0252CA2E20DA887A9D5ED251FD5CCE811E514D702206E413DEE7528 +3A6AEF793F5E3E3B0B10E59226102DF2A7347A95B96E39C6B6ABD1FB4B7CDC0813D7390E2819EE50 +7E5458E06D43CC3BF16E40CEC4E64909615CBFCD233B5D316BC8B8B46243CDB7F2FBECA2F208D0A7 +985048E88A5E685A9EE8C3A351FFCFB522C1EE41A8E4EF0A080C0D110D2A0D8A980AF1F604D1CD52 +3CF41D08A45EB809AF11F530C138F256FFD49BC20F77005D004AC85FD563E8E8BA5F79CC60FDE714 +1416F4C14FFF281ECE2BE1D167B7B1ECE17790454F795B056CE4DF1D08F25F0E5E2A16CFA066AF97 +F3A80015068FA2A1D65F329A5CF7856114A77EE7295C7929CA27796D8C51B31347CBC7C4EEF1E75D +12237C558BE5B65DCAA4CDDCB419E543B1785DF8B225C2E9778839123E6F85E53D925C6D2FC0327A +EF9CA13D1F2F9B3E510A1A4190D3057227621788AFB6567132238BE86559BCA006B85AFBB8AFE5D7 +A8CE58AF731F2550108C6B8CD63632184B1E218F01881CB94CEBFFC310F263565A39C83C874AE474 +AA213F7DD9CB3B8446A3E47E81BACF7B16E4A80899BF2D30C28F9DE0678DF364589DA6A454398E30 +21A92AD1D8B0DC494D96A06C7568E34547F0E68829E5D15F6500ECD7403B40B5CC2A479D0F8CE3E8 +709861608AED046339BE055BAED1A6BD2311CF918E74378D893710D42A769671326E947F108A38C2 +3F92EF5C6A50EFE36E858671F17151C719B9C7D60E8E429F088616D616080DC676760BE83A3F5229 +6C4B13CA3818E3FEB56D51DB5979F28063F7D537C15493D57CAA1E55438C01C794336D9F21520484 +FE5B5BAEAD7121E3CCC5086E2F2191DBEE9527CA61CE85CC0A4D99EA29169B17A10B3D4372EA493D +48CF572E570CF48B3B7512268A9AC4CC161F97695D19412C36F1D67522E272B1C18B6122A355B43C +37636E71462503C43C1AEF5ACE34A6442075B59892BA54E9B5A99B9733BAF64161450F8ECF4FB2B2 +DA6F9AAEBC827CD9AEF94BB463935C26E1DD5DDCA5BA87E0EFB62D9D3226DC28427EC097B68C9EE1 +E47D125F884F8656C8AD55D625A8F7F1A306383A5036A12E63B6FAC4A215AC88725611EBA74B6770 +B20F45356879DB807E9C9105D14C83104B2DCB4F274A2F37152425935B3F2D1633E4444540F7411B +24C17055850C9528D912F9DD40984CA1B3BE277F82198BC99B80233B8AAB674A44B42B9123E886B7 +B3161D48B4AA24970BB75A983569690BFD4B92E6ECB410DDCA14A3BF59057397FF1AF159BE230216 +FF2F7009C3A3DDD81F2BD1F5C10C2C2F30A8095D7371427AC9D527AC6794CF8593303F786F21CCBE +36DFBE01B0ADD78DB3C9483D9DB09A9E2577A3A4296E7BD09C41F5C91B29C9D7E69EF06B5BDB66E0 +853C3DB133769BC8858121A350CADC1D61C77C1A313833D077938AB2E10EF970590256479B1549B0 +401A9C5E97F12A269A305B372F0EFC035B06AE3E2187D6E3A62DE3B4E8075F0ECF5BE32A5B97A4AC +23A7335C4AB29917129C08250DBD1D5BC123C92E821BBB1A7598E32DDDF1876C6FBB7B858FD317D7 +6022955595338AAD2BFB3B17DA11380E7473A5F566C6329E0F38D9A67E68FD4B8CFCC82FC0BD7BA3 +9A25D5BD0952CC71CF0BD7B5EE184A8E62FBB366D95AB051BC3A80E9F6C27EC100E00C4A002291B0 +4D758A9683657FCB07A2AA8B07DF585CD4205B4DC55330E8A2A367CBE966E84618ACFCAAF81C8A9D +3A3165FE5A623720A0C11CBDD5CA200100DD2C970A09B124D98987D6A2CA7CBB40DA5B585B86FCB3 +8E96F54C64A921D7A5D2F805C67339DC4FC026FB2EE533BB9700F66F4AD58949B3DB75D770C4D7C5 +C23DC720D78124A7ABF30CDFF10C708219903431C7B1A19317341CEA22AA11A588F8AF9BFA4AAC40 +31EF22F13BA597BD1E5958CEA741274197113CB0B7975424C17F73733ED30BDC0170CD0084BB7DC1 +8F031BEF2A6C44D0EA0EBB84E31D5B4C50B63DF0AF96AAF33E3C986D4B82D161E2F9049DE9710EF8 +A11BA0E08266A3547F20567E582EDB47633772D37FCCD119AF5B11F338098F185D5B055975F56092 +CC6979FF49487D69E08A4E8754FD51D29410F56E0F3EFB9C3B86253B3B0A39E9D217604F664CC696 +634D216524F976D066034DDE8EDF5E8F9644337C50C7D8E2230B38E0EE88C53DEEDB3363477E32CB +88B3ACB6931B9717FC93F8D1A9DDDA3A7D8A56783F2B651A1DE216493C34DCD17A7635D21CEC0770 +751424B599B00CE950E60D8A8B3444F1629220DF073137E2ADE1B6243C1C69BD5E10450B948BFCBA +46A841B98BCE7C86E120789C73C87E581868021E68F78CB903AA6D494331174CBF944221DEA9E567 +A2542BD399C5E471D972FAB7E8950AEFF3D91A1296E685B0BFF91367C2703DCAB10AB236DE603E3E +993C4C9020EEBFE89726AB9FB92AF9D371D7188448CAB9FD2704506C39F0842E36EB8B9AAFB1F26F +CD366E66725AD28B19FA57537757EC71B04B451F056C3AEBFA04D1C0CABC9492B6B8D239EEC36776 +D6B515ED43A66AB4342BE4C8B2027F2D008EA231A24B472DA8352A05DDEE31CBC3577E5814DD2D4E +C17A216A7C8FFB27012346C9ED12F7819B96A5B135B6196E888C9AC73D7D4B7DC370E2FBFA17FD74 +0BF516CFF69294493900BFD63721A537BEFB31C5262567103F9CEA4E8DB02964B983E1AF190ECCEF +41F552C45E9B94E29AE3F129687EC35719F591C1987A08DCED3B822344DF81A70AE78E14A81EF1A1 +A77FD5D19F7D35B7C12A473EE11C655E15DC5D3C94F226CBDA85377338BFCEA18359176CA7EED622 +84F9015F2D592E27F1037C95570CFDCA3B9B90D35DB8C341434BBF04C0E692D4C2C3F59EF386CD1C +5A8C783198EBE42C89E3B64C662F7875325982E9299C18DBD1FD2257DD964F9F9DDEEBFE56E4AE9E +8163E8C58581BFD5818BF396CCDE3A66F58E17BDD262D63E5D9AE0D0E43C304CCAC4776B87D40F6B +5AD7E625B2065FAABA81AAF2E2D32E0ACAB12DB2A9FE9C6160FBA7DE5AF019810AD9C64B2E6567B0 +F0108E8EDEC635F4BA88BDEBD3561CDBBB9063B4D19C493E0CA4F551255A7DF6BCE96C17A5DD4877 +7F654F4F114CF29ECDB9779F63EECDC04E1FA06E48701547A8F96483B4195C0DE90B0FD1B95B5F12 +5C43BB973D0B6414A994595359289B5FFEE4A08D684C2E7E3F417585581318D54DF2EEAE7CF36E2C +A1A70EA76154B88C1E4FFDF5EBC28ABE264B40583F2DE1F761DC25DF242396AE502CC63EFD6EE284 +DB37B0DF36A8833ED3EA5A1DB372C8E1F3203E6E945A53D83B87250CD72C851F68A602E7BCF92DDC +E600D518069F8900F3BCA434D21694229572D55F307E0FAFA3DAEA864BAA19F227097963CE83882D +22FB7F1AA3FE558A3669BB7FA37C0A591B4BF3E2A2FF85BFEDE5E424E0AE337EABDFD67BBF38E160 +09A687D2E7C1882DB79304D722EACA233EFA489B10E8D4D404D12A5A4683FDE377A9A952CCAB257E +ABB408D75FF130206DC3447F26E74D1D00354313E340878917177DBD2AA5A2A70E2087D551DA5181 +6FAD07F6E826DB0BF21560BB26906D3100E12AA3801D4A8BDCBF1A0A8D58F1054EE2A0DB04ADDF3F +EAF1FD6E322BBEE274A4995EFFEAAD0F24D9FB45730F6BA0F42D88BE4AEBE9777F8AF508DED61024 +EF955B26ACEC51AF5C21BAD7AD93CC0C9DBE03C0FE9637A3099E5EF329051C87FFF70042D788F21F +4DE645FEBF58374F7E38A9AFE3DE4D2888DD807A09169DA8516DFE37591C122A4798343C1A8121C7 +430F244EE5B19A21897A4423E21CE0492E75C9320E37BD65F1EACA7FC6FE032842E4D985E666E633 +AE5CC62B3449404672B284EA5C6A01E927787104ECCE1354D1C0E5AA2452B7B12937B946B74EFA98 +8C5C79EEE5ACDCFC994CAA853AFA08EB9E180C5E898FDDF8903EB0863E98A4FA537207C154CE5B98 +EF4E97FB6A1CBB07C45D34221EED26998EC864475D231FF76E830A7C0900C4B5FCB980E3F67F4EAE +3DD8086CFA1874E65C424646C7A8F84272DAB04A5984FCE3F4B89380B3A5C6F04EA3B683CC224913 +9209136336AC9349AA89690B5DE0D93E92F996BA7E346B043EBD45A35FA297DB70E9A6250B138674 +FDDFA609BE11EBA8106EEE3923B4E2657CF7A82443C25022A5DED862E8E3A5BFA7B8AC1C1B804B23 +C08A7D8FE594140E8432C1A5E183FE361B6B8C763054458798B756A6CD2535FF4667C634DF78AFBC +C87D40DCD56FFFA12CFC6EDC6A8AF41AF657E0137320624F1895D9AA84AABAC5F3653E977B054C52 +92B06FEAAAC0E5C7FD45B6654D0D9CCDF292A8489E1E6CD929F0AF25B9604CE33BD25662133CEE99 +CDBBD00F865431193767480700CA019D694B38D695CC29F732B5B39B6CE654F1AA18AC3223B60E55 +3FC3C3E114E6A2E59617BFD58AD8E429200B59E035DB7D67C8DC6C72001D005924670ECF7A06FF5F +7B3DD5556DDDE1BE5163F7CD6FC969033EBBAD85856522ECA91F2A200A75BDDA69070240D86C2D7D +959F1DBE45D488A96F308269E8262327A410D057446F596418DF4A12267A7A3FF5ADEA0846896D3A +EFE3D5C72387F7EEEE809BCCD23D1126B86C45A0F66404FAF617D03379A2E44865051E92B4C835F7 +21492147DE3DCCE47EAD192D3F10A5DD459634D6C253F4D09DD98E7DDC836EED8DE08A78A6F6FB93 +84565CF6DA8D4C948144272D5F8787643B9B550F96E42A8CFC0F1C1F2B14D83201199E9E1F5BC0E6 +51E39E16A744930F4409C61CDF9F456C7EAC5C62BD22CE0DDCCDF3755DC670FDC8FEBF09F241BF95 +1A1694BD70B9695A15653F719DF4374AFC6C27F58DD144253BDF86FC1BB3D4FEAEEB9196BCEC7168 +AF1488AB7072751FA24C6642B87DCADF4B2427631E7D7FA39D126410189CD8E24CF0A490FDC7045A +A9A83D02CAB57B9C27211FE1805080FBCEF23B86CDB19885A574C589BDB15C4CB3651FA3D2D6B619 +4449B16D927047E376A84F9423562CD548D192AD0A57E0D5B41010D1C7D87929DB6F456B0A073DF9 +A8220BEB3D4DA01518B5C85C82ABCC821BEEC1965283DABF6AF3AC55ABDDAEF1B53E6119DBB4A114 +44FB1769F9891B5DC931ADFEC8ECEFFA179427FCB08C1B149EDA70717C5E9322F3CE9405438481F3 +90968A38061EDAA2223147D6143877E4C4E645EC0A9803A933116D610491FDC639DF69B418156772 +E14A428864982D3230EFFAA1D2D3388B0F70D8FE202CDD6CB0E97B3290ECCF4A1A69D93C11D3D735 +FF0B5A9A161A86CA4F16B7C231B8BF71D5083697B2F24C0BDAA6E83E5DD2222DA248D4ED3B5509CC +7CA40D8D5A2507A1D40FC1AE9486343CA2B2C65B6CED9B1F4C0535ADF7BECCE45E1DB74C7417B06D +897C6ECDD05F068857D931B3D960103628675BE2FB82DA774ED3786670D8C2BA4ECC36C203C7F7E0 +9C97A09E3B2E2C3EC30B53A8825519B664D09F149AB73F183A4A22C8F20E2052F76649AB881BA28C +6C3C3950EA9B3961F87F6AEFF24DD7B4E10DE28C15DCB27F593F98CBC9CDE19360C5531B2A0C6DC1 +8FAE2832AF49BF9522C068F3036516F711AADEE496EE4E3EFA2F55EA4891DEC6D8A868E0B0017076 +3040CA4D42A73A71057DFE315FF8625BBD74F41E5CA77B7DF096C2F4F0D51B1184CA9F2C8517FCB3 +956C44873D630E94EAA1D2D1451B332770172B2D7A21ECAC864C612235C0B39ADF55EF074F2073FA +D3B54A66B07AEA1FB495A9F7269BD1C07692B0B762DA6881EE6B265ACED0BF0794C0397F8D8B13FF +275BE77561067E2C1FA536131184682BFD32384C6488012D29E8E55838E5A5AA7C40C20AB6B03BFD +BF4EFD2001A612100D585BAC1C77C1CB1ADAF2B000E09686091AB7D1AB6ADE395A03580BD78E961D +14D052E7D4A61227534B55FACAFC4E8F9326DE35BA53A463F7D94B705698300771185DB19E78FABC +D0AE4FCE005B79C795B692F2D9C00B6A61D4B343C35E417BBA169EE82A2E4AE204A126B08A94191C +6E5B5E8328A147BD6ED5A5AD5C9143A50C47789DCFA699720336AFFBD6B1646D8C35139B0DC34340 +C76C7E4FD72DABF80BF64BA04742D07B380E20A678EBCB19057F2346FEC653F1302992279DCCD2E8 +902B2B8F78435AF3400DAE319E94E3148BA88C056701D524DD67FF368C85EC6366F31689A62FFC03 +4BE436588ECBB8B41E8C43112F3B65F50D20A5EC51A26FE899E74731B01C7771F75B76070DC0B223 +1845BE9C09670E65C4DF54C0FB36511B735251AEEBD13FDC0FCFC3134D8DA826E4100521CFEAB202 +B83267EB3F69AEEA25FDE1E9C86407E38AB4CA2D1B91607EFC96DDC5BB10FCD46DEAA5FFED196959 +12302ED55DFACFDC6F22C1528AE3A9902B75F46A489352C87B0F5056FD149DDE67219A3C3D1DD875 +BAD81AD443E0F90DC79D979E10B4262DD20B255B04BBBE5C988B23667135CC61D988928F5F07753A +C76807203942F6B62A178A1E5CBBFBBAEBE735D09B747BF00C360F2E2B6C9F2261F7DB018EDF40B1 +3DB4B7962D4D2F858E1FA6376F21ED9CEA4BEE07FFE27F136BF60352AFB71CDBFF7E65F6B4624E16 +C6E5AB7FF60B4711A178FEE691DD8D362B14175A69070FBC5B9EB15248E4DFDA3C9453335D0E1EB2 +7714E4624C7215A73B53402B02CF7AA05A14CE1E5AE161B75F218BF0EE1215D3CD925D5ACB7DBD2E +BD8E4405433F432C491999794C814D5B384B7A1C0AB28A11CBD0A6EA12678508CF86232309B0BBF7 +EEA929FB7951F5A6B367C5AC57DD7D3B3C1E2188DD4664D14FBC32B1A1EB53D7EE7F55F4D5C2D014 +528EBB7F0E595F7618721451EE6B83EB240A6C4D33377893D4EF542F47EB2845A09759D5554C74DC +5E9109FFC8C929CF1AC446D8149957720EE4FB670D3CA61378549DB992126B23618CF49361D6D4B1 +C73C3D37E4A4465ABB349CFA34E9351D1192E366267EDF02DE432ABC80792B0CFD41FFD0AAA42E63 +3F5B2177A351D33477C636CA573CB02C07F7F7A41C9F1BC4C112BD6459DD130757D2BD6F47495C3F +92E99522871DAC2865 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark{restore}if + +%%EndFont +%%EndResource +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(2) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -118.485 -136.705 ] concat +1 w +[ ] 0 setdash +P +P +p +np 181 96 m +181 112 L +219 112 L +219 96 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 181.505 96.904 ] concat +1 w +[ ] 0 setdash +p +np -1.505 -1.904 m +-1.505 16.096 L +38.495 16.096 L +38.495 -1.904 L +cp +clip np +p +np 0.495 0.0956 m +0.495 13.096 L +37.495 13.096 L +37.495 0.0956 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.505 -0.904 m +-1.505 15.096 L +38.495 15.096 L +38.495 -0.904 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(5) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -181.505 -96.904 ] concat +1 w +[ ] 0 setdash +P +P +p +np 267 3 m +267 19 L +305 19 L +305 3 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 267.864 4.035 ] concat +1 w +[ ] 0 setdash +p +np -1.864 -2.035 m +-1.864 15.965 L +38.136 15.965 L +38.136 -2.035 L +cp +clip np +p +np 0.136 -0.0353 m +0.136 12.965 L +36.136 12.965 L +36.136 -0.0353 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.864 -1.035 m +-1.864 14.965 L +38.136 14.965 L +38.136 -1.035 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(7) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -267.864 -4.035 ] concat +1 w +[ ] 0 setdash +P +P +p +np 295 86 m +295 102 L +328 102 L +328 86 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 295.617 86.954 ] concat +1 w +[ ] 0 setdash +p +np -1.617 -1.954 m +-1.617 16.046 L +33.383 16.046 L +33.383 -1.954 L +cp +clip np +p +np 0.383 0.0458 m +0.383 13.046 L +32.383 13.046 L +32.383 0.0458 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.617 -0.954 m +-1.617 15.046 L +33.383 15.046 L +33.383 -0.954 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(1) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -295.617 -86.954 ] concat +1 w +[ ] 0 setdash +P +P +P +P +P +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +%Trailer +%EOF diff --git a/services/clsi/test/acceptance/fixtures/examples/epstopdf/main.tex b/services/clsi/test/acceptance/fixtures/examples/epstopdf/main.tex new file mode 100644 index 0000000000..c35a378422 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/epstopdf/main.tex @@ -0,0 +1,10 @@ +\documentclass{article} + +\usepackage{graphicx} +\usepackage{epstopdf} + +\begin{document} + +\includegraphics[width=\textwidth]{image} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/epstopdf/output.pdf b/services/clsi/test/acceptance/fixtures/examples/epstopdf/output.pdf new file mode 100644 index 0000000000..199333ddf0 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/epstopdf/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmf/main.tex b/services/clsi/test/acceptance/fixtures/examples/feynmf/main.tex new file mode 100644 index 0000000000..c36ed70919 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/feynmf/main.tex @@ -0,0 +1,28 @@ +\documentclass[a4paper]{article} +\usepackage{feynmf} + +\begin{document} + +\setlength{\unitlength}{1mm} + +\begin{fmffile}{diagram} + +\begin{center} +\begin{fmfgraph*}(41,17) +\fmfleftn{i}{2} +\fmfrightn{o}{2} +\fmflabel{$g_2$}{i1} +\fmflabel{$g_1$}{i2} +\fmflabel{$p_2$}{o1} +\fmflabel{$p_1$}{o2} +\fmf{quark}{i1,v1} +\fmf{quark}{i2,v1} +\fmfblob{.35w}{v1} +\fmf{quark}{v1,o1} +\fmf{quark}{v1,o2} +\end{fmfgraph*} +\end{center} + +\end{fmffile} + +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmf/output.pdf b/services/clsi/test/acceptance/fixtures/examples/feynmf/output.pdf new file mode 100644 index 0000000000..bb7fe9099c Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/feynmf/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmp/main.tex b/services/clsi/test/acceptance/fixtures/examples/feynmp/main.tex new file mode 100644 index 0000000000..6027bf904f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/feynmp/main.tex @@ -0,0 +1,28 @@ +\documentclass[a4paper]{article} +\usepackage{feynmp} + +\begin{document} + +\setlength{\unitlength}{1mm} + +\begin{fmffile}{diagram} + +\begin{center} +\begin{fmfgraph*}(41,17) +\fmfleftn{i}{2} +\fmfrightn{o}{2} +\fmflabel{$g_2$}{i1} +\fmflabel{$g_1$}{i2} +\fmflabel{$p_2$}{o1} +\fmflabel{$p_1$}{o2} +\fmf{quark}{i1,v1} +\fmf{quark}{i2,v1} +\fmfblob{.35w}{v1} +\fmf{quark}{v1,o1} +\fmf{quark}{v1,o2} +\end{fmfgraph*} +\end{center} + +\end{fmffile} + +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmp/options.json b/services/clsi/test/acceptance/fixtures/examples/feynmp/options.json new file mode 100644 index 0000000000..a280541cfe --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/feynmp/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "latex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/feynmp/output.pdf b/services/clsi/test/acceptance/fixtures/examples/feynmp/output.pdf new file mode 100644 index 0000000000..66268b2b7e Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/feynmp/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome/main.tex b/services/clsi/test/acceptance/fixtures/examples/fontawesome/main.tex new file mode 100644 index 0000000000..42bfa8e55c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/fontawesome/main.tex @@ -0,0 +1,12 @@ +\documentclass{article} +\usepackage{fontawesome} + +\begin{document} +Cloud \faCloud + +Cog \faCog + +Database \faDatabase + +Leaf \faLeaf +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome/output.pdf b/services/clsi/test/acceptance/fixtures/examples/fontawesome/output.pdf new file mode 100644 index 0000000000..327d317b29 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/fontawesome/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex new file mode 100644 index 0000000000..5158b672f4 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/main.tex @@ -0,0 +1,16 @@ +\documentclass{article} +\usepackage{fontspec} +\defaultfontfeatures{Extension = .otf} % this is needed because + % fontawesome package loads by + % font name only +\usepackage{fontawesome} + +\begin{document} +Cloud \faCloud + +Cog \faCog + +Database \faDatabase + +Leaf \faLeaf +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/options.json b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/options.json new file mode 100644 index 0000000000..a2e0c09897 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "xelatex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/output.pdf b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/output.pdf new file mode 100644 index 0000000000..b5a12e39a6 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/fontawesome_xelatex/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/main.tex b/services/clsi/test/acceptance/fixtures/examples/glossaries/main.tex new file mode 100644 index 0000000000..336f70868c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/main.tex @@ -0,0 +1,17 @@ +\documentclass{article} + +\usepackage{glossaries} +\makeglossaries + +\newglossaryentry{Physics}{ + name=Physics, + description={is the study of stuff} +} + +\begin{document} + +To solve various problems in \Gls{Physics} it can useful to express any arbitrary piecewise-smooth function as a Fourier Series composed of multiple sine and cosine funcions. + +\printglossaries + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glg b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glg new file mode 100644 index 0000000000..6bae571d8f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glg @@ -0,0 +1,7 @@ +This is makeindex, version 2.15 [TeX Live 2011] (kpathsea + Thai support). +Scanning style file ./output.ist...........................done (27 attributes redefined, 0 ignored). +Scanning input file output.glo....done (1 entries accepted, 0 rejected). +Sorting entries...done (0 comparisons). +Generating output file output.gls....done (6 lines written, 0 warnings). +Output written in output.gls. +Transcript written in output.glg. diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glo b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glo new file mode 100644 index 0000000000..0b6f71e75f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.glo @@ -0,0 +1 @@ +\glossaryentry{Physics?\glossaryentryfield{Physics}{\glsnamefont{Physics}}{is the study of stuff}{\relax }|setentrycounter[]{page}\glsnumberformat}{1} diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.gls b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.gls new file mode 100644 index 0000000000..128261a191 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.gls @@ -0,0 +1,6 @@ +\glossarysection[\glossarytoctitle]{\glossarytitle}\glossarypreamble +\begin{theglossary}\glossaryheader +\glsgroupheading{P}\relax \glsresetentrylist % +\glossaryentryfield{Physics}{\glsnamefont{Physics}}{is the study of stuff}{\relax }{\glossaryentrynumbers{\relax + \setentrycounter[]{page}\glsnumberformat{1}}}% +\end{theglossary}\glossarypostamble diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.ist b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.ist new file mode 100644 index 0000000000..1861f247c7 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.ist @@ -0,0 +1,29 @@ +% makeindex style file created by the glossaries package +% for document 'output' on 2013-7-28 +actual '?' +encap '|' +level '!' +quote '"' +keyword "\\glossaryentry" +preamble "\\glossarysection[\\glossarytoctitle]{\\glossarytitle}\\glossarypreamble\n\\begin{theglossary}\\glossaryheader\n" +postamble "\%\n\\end{theglossary}\\glossarypostamble\n" +group_skip "\\glsgroupskip\n" +item_0 "\%\n" +item_1 "\%\n" +item_2 "\%\n" +item_01 "\%\n" +item_x1 "\\relax \\glsresetentrylist\n" +item_12 "\%\n" +item_x2 "\\relax \\glsresetentrylist\n" +delim_0 "\{\\glossaryentrynumbers\{\\relax " +delim_1 "\{\\glossaryentrynumbers\{\\relax " +delim_2 "\{\\glossaryentrynumbers\{\\relax " +delim_t "\}\}" +delim_n "\\delimN " +delim_r "\\delimR " +headings_flag 1 +heading_prefix "\\glsgroupheading\{" +heading_suffix "\}\\relax \\glsresetentrylist " +symhead_positive "glssymbols" +numhead_positive "glsnumbers" +page_compositor "." diff --git a/services/clsi/test/acceptance/fixtures/examples/glossaries/output.pdf b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.pdf new file mode 100644 index 0000000000..297385b8d1 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/glossaries/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/gnuplot/main.tex b/services/clsi/test/acceptance/fixtures/examples/gnuplot/main.tex new file mode 100644 index 0000000000..09077a5724 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/gnuplot/main.tex @@ -0,0 +1,26 @@ +\documentclass{article} +\usepackage{pgfplots} +\usepackage{nopageno} + +\pgfplotsset{compat=newest} + +\begin{document} + +\begin{tikzpicture} + \begin{axis} + \addplot +[no markers, + raw gnuplot, + thick, + empty line = jump + ] gnuplot { + set contour base; + set cntrparam levels discrete 0.003; + unset surface; + set view map; + set isosamples 500; + splot x**3-3*x+3-y**2; + }; + \end{axis} +\end{tikzpicture} + +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/gnuplot/output.pdf b/services/clsi/test/acceptance/fixtures/examples/gnuplot/output.pdf new file mode 100644 index 0000000000..84efdc5942 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/gnuplot/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/hebrew/main.tex b/services/clsi/test/acceptance/fixtures/examples/hebrew/main.tex new file mode 100644 index 0000000000..0eb48d9116 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/hebrew/main.tex @@ -0,0 +1,14 @@ +\documentclass{article} +\usepackage[utf8x]{inputenc} +\usepackage[hebrew,english]{babel} + +\begin{document} +\selectlanguage{hebrew} + + כדי לכתוב משהו באנגלית חייבים להשתמש במקרו הבא וכאן + + ממשיכים לכתוב בעברית. טקסט נוסחאות תמיד יהיה בכיוון שמאל-לימין + +\selectlanguage{english} +This is a test. +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/hebrew/output.pdf b/services/clsi/test/acceptance/fixtures/examples/hebrew/output.pdf new file mode 100644 index 0000000000..09767bd88d Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/hebrew/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr/main.Rtex b/services/clsi/test/acceptance/fixtures/examples/knitr/main.Rtex new file mode 100644 index 0000000000..add779afce --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/knitr/main.Rtex @@ -0,0 +1,13 @@ +\documentclass{article} +\begin{document} + +Hello world $x^2 = 0$. + +%% chunk options: cache this chunk +%% begin.rcode my-cache, cache=TRUE +% set.seed(123) +% x = runif(10) +% sd(x) # standard deviation +%% end.rcode + +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf new file mode 100644 index 0000000000..7a839f7b13 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/knitr/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/main.Rtex b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/main.Rtex new file mode 100644 index 0000000000..29d575e949 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/main.Rtex @@ -0,0 +1,35 @@ +\documentclass{article} +\usepackage[utf8]{inputenc} +\usepackage[spanish]{babel} + +\begin{document} + +\tableofcontents + +\vspace{2cm} %Add a 2cm space + +\begin{abstract} +Este es un breve resumen del contenido del +documento escrito en español. +\end{abstract} + +\section{Sección Introductoria} +Esta es la primera sección, podemos agregar +algunos elementos adicionales y todo será +escrito correctamente. Más aún, si una palabra +es demaciado larga y tiene que ser truncada, +babel tratará de truncarla correctamente +dependiendo del idioma. + +\section{Sección con teoremas} +Esta sección es para ver que pasa con los comandos +que definen texto + +%% chunk options: cache this chunk +%% begin.rcode my-cache, cache=TRUE +% set.seed(123) +% x = runif(10) +% sd(x) # standard deviation +%% end.rcode + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf new file mode 100644 index 0000000000..2d63d1af08 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/knitr_utf8/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/image.eps b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/image.eps new file mode 100644 index 0000000000..fb131b9c36 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/image.eps @@ -0,0 +1,6673 @@ +%!PS-Adobe-3.0 EPSF-1.2 +%%BoundingBox: 0 0 432 268 +%%HiResBoundingBox: 0 0 432 268 +%%Creator: (Wolfram Mathematica 8.0 for Linux x86 (64-bit) (February 23, 2011)) +%%CreationDate: (Monday, October 8, 2012)(15:03:46) +%%Title: Clipboard +%%DocumentNeededResources: font Times-Roman +%%DocumentSuppliedResources: font Times-Roman-MISO +%%+ font Mathematica2 +%%+ font Mathematica1 +%%DocumentNeededFonts: Times-Roman +%%DocumentSuppliedFonts: Times-Roman-MISO +%%+ Mathematica2 +%%+ Mathematica1 +%%DocumentFonts: Times-Roman +%%+ Times-Roman-MISO +%%+ Mathematica2 +%%+ Mathematica1 +%%EndComments +/p{gsave}bind def +/P{grestore}bind def +/g{setgray}bind def +/r{setrgbcolor}bind def +/k{setcmykcolor}bind def +/w{setlinewidth}bind def +/np{newpath}bind def +/m{moveto}bind def +/Mr{rmoveto}bind def +/Mx{currentpoint exch pop moveto}bind def +/My{currentpoint pop exch moveto}bind def +/X{0 rmoveto}bind def +/Y{0 exch rmoveto}bind def +/N{currentpoint 3 -1 roll show moveto}bind def +/L{lineto}bind def +/rL{rlineto}bind def +/C{curveto}bind def +/cp{closepath}bind def +/F{eofill}bind def +/f{fill}bind def +/s{stroke}bind def +/S{show}bind def +/tri{p 9 6 roll r 6 4 roll m 4 2 roll L L cp F P}bind def +/Msf{findfont exch scalefont[1 0 0 -1 0 0]makefont setfont}bind def +1 -1 scale 0 -267.698 translate +-35.28 -2.88 translate +[1 0 0 1 0 0 ] concat +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit + +%%MathematicaCell +%Cell[BoxData[ +% GraphicsBox[{{}, {}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113dcTf8fB/B7697q7tW0KSoJWRnp/ba3rCR7kxmSzKy+KFlFvqSMjB++ +%ESqKrlVEKJoy2nvv3S/O+5x/7uPce8/n83m/nu+zeq7cOmeNBofDqeVyOH8+ +%2a0l/9EWydgK9V7d4EmZe47Cm8LwOycM2X1fGDzdYkfqz3L1024exbOfBoC3 +%r26ui2c5/R4EpUfSFFnDytWX/v2z/Q9yO+VvM84uo/8Hg9/5EpPBp8vUJbP/ +%fBMCXy8fN5GPKqPjn4DGhdnXo/JK1cKUG86jheEwxHn2wDHnSmm8Z7DcMeyV +%7+hStfni069rzZ7D73FreM8KSmj8KKiZGhB1z6dEHdRxdMqNl+C31zh+u20J +%zfcK1nC8d2oVFqsH/93egLor7NzkU0zzvwWPY/98uDG6WP2m1qxjhmhwtBjk +%dye/iNbzDkRHH6e5ny1Sz/k74XvY7J513WxUEa0vFhJ+vlgTkFOoztzzZ8AP +%YDckMqHAu5DWGwf9rDutaBhWqN72Z3nOn2DLwfSeGb8LaP2fobqXp8v54wVq +%RiMedJ1rPhpaFVA98XBvmqp6Y1q+evLfCRIgKtrukO+hfKovAa4/0ex0wDxf +%/We2xae/wuWhSVqYkEf1foM7E+x2fnPLU/8tJ+Ub9MHHQYN75FH9iZCe2ui9 +%4F2uuvuf5XRLghFBj47Yb8mlPJLAyiiZ11kvV71u7Z8tGcxC4sIeROZQPikQ +%nNGyS29ljvrPaLrBKXB/zgzOGJ0cyisVnGs38MYEZ6vr/gxXmwpxOW67tedl +%U35pMMcjofO/jVlq278DfoegKVuPNQVkUZ7f4RpHXT5wfJb67/I80mF2ZJu1 +%cWEm5fsDHKa6JZd5Z6o/xf3ZfoDBO+2sPYMyKe+fYGHW3z0pOUOt93eBv+DB +%/IdHm/ZkUP6/4Mi8AfcqumWo/w63+DfofvIPWuzxmzwy4HDdvBtr4n5RPRng +%XyQ/26D8RT4ZMPzRQcuhjj+pvgxocL11Ky/gB3llQtVuy7W2OelUbyYkKzUb +%upink18mhO8U4sXN36n+LKgLm1j3X0gaeWaB1zML89G1qZRHFmim+1ksHZ5K +%vtlgedzAUmNvCuWTDQ3HXh8c/iKZvLPBRX+wU01bEuWVA6+K352eiUnknwMT +%axKOmR9KpPxywHbtC6ejr75RP+RCoPeYCnfuN8ozF3oOWGKVjV+pP3LBe7L5 +%piz3BMo3Dxx73Vu6Niqe+iUPxjkazH/X8IXyzoM+e25rp+74TP2TD+LzNpol +%pXGUfz4kRp4W8dZ9pH7KhytWwuBOv2PJowD+O3xdbDv/PXkUQOigqX62cTHk +%UQAmpw6MmzEmmjwKID7AsmxD6BvyKAQn433ymWavyaMQ9Dj2C8ZeekkehfD2 +%tIXdFv0o8iiCLxaLbU3qItRn/noUgeSth6vXt3DyKAKT2d+7fgx+Qh7FUOYz +%5qzF8RDyKAazMPW0oKj75FEMtptcBlWtu0UeJTCxx4K8oZKr5FEChh625dcX +%+ZBHCdxdculc2bQ95FEK/jDse0aTGzAepXDat36zh6EPMB6lEOMyLXttSyAw +%HmXw0vl9VIrLLWA8yiDsQt/zKyvuA+NRBjv2/d43+GkIMB7lUHT/5ubmpifA +%eJTD2mtGW4vgKTAe5fB4Z5evQccigfGoAOVTaPVLiAKmngqY/6hX1yGyV8D4 +%VEBui2yWT9hrYOqrAGPprh6ey94C41UBT9Mc+z4XxABTbwVYR3y/ZB36Dhi/ +%Crh4w3aAYmUsMPVXgmNgbMYq+UdgPCvBzrn8foY6jvKohF1ulbIi58/A+FbC +%qfuxDgGKeMqnEmpM34d7FscD410J2DMwyigmgfKqgsnmb18fvPYVGP8qyP+i +%eXn3/m+UXxVk1g8+OnVhIjDnZxWU1nmvz7FOojyroIeBPsdSPxmY/qiCWTF9 +%JVibTPlWwzDfRG2DpBRg+qUaTjQei30Zmkp5V8O1LS5aQ/zSgOmfanAvvXnN +%fvd3yr8a7G59+7JjcTow/VQNMr7DyVn4gzxq4Oo/D/X5vX+SRw30CbVN8hT+ +%Io8aWFESNqam4hd51MCExLtuY1J/k0cN3LLSbRmxPIM8asBzeCet0MQM8qiB +%xasjHkZMySSPWoiyEMg7qTPJoxby2zPkrUOyyKMWLhklHVp0L4s8aiFlYNjQ +%vr2yyaMWTljpGO/5N5s8asG5x7V9qMghjzp4963x2KkTOeRRB5EuJi12Grnk +%UQeF/fc1B+3NJY+O/4+32L6lNpc86mCRzYR5UVvzyKMO9DZbrbtYlEce9WA1 +%w8COvzafPOoh3SfIODcznzzqwe12cr91SwvIox5SRvB2uKYXkEc98DhqF6lj +%IXnUw9beZyJtUgrJowFsrQ6d0ZpfRB4NYHrH/+TapCLyaIAuOL1mtn0xeTTA +%GuPw70VJxeTRABNkzUUqhxLyaACvueemlaeWkEcDOFnuMly9qJQ8GsFlgFfM +%gV+l5NEI+U+NetquLCOPRngeGBIYmFdGHo2wcvfdxR82lpNHI5zRh4MnK8vJ +%oxG4C3e46NhUkEcTNMd1Tw5wryCPJpi5wunA1TcV5NEEPv2vtpRoVwJzvWyC +%hNniq7emV5JHE+j1eBv69GwleTTBucifASNTKsmjGQ6uu2yt37WKPJph027d +%liWrqsijGZZWO1vp3K0ij2ZIWrG+uVtlFXk0w5uBsu6Bw6vJoxnEvr7uFw5W +%k0cLZAa39qx4X00eLdCw/9iod4oa8miBzg+mjuIvqiGPFni9sK/u26Aa8miB +%Al/97Y1lNeTRAv945ufdGFFLHi3Qpbvry89Ha8mjFbJ6eHq7xdeSRyu4t5vZ +%X+9SRx6t8HFCVbC9Ux15tMJJ19DlHmF15NEKBUP6rQJePXm0wrlbvnoec+rJ +%ow2GN6beXXKtnjzaIEQ7Qvmhop482mD5z+vvorGBPNrApmT4yTlnG8ijDfQq +%YhoOZjWQRxsIRxUOnz2kkTzaoelrk27YP43k0Q6orNB8m9ZIHu2w1HNA0X7L +%JvJoB0Mb7tisQ03k0Q4nnwiKeSlN5NEOrolmkc/6NZMHB0+MWM0dc6SZ6uFg +%7LMxG3Z/byYfDm6x7mLnOqiF6uOg79Tpd829WsiLgzNttkwMz2mBv+Wu5eC3 +%2QNna0Mr+XFw67ivTf0vtdL1gYPJni0OpnWt5MlBz/itIby5bZQHB1sbMyRR +%D9vIl4Mng7Y/Wydrp3w4+GOPlMvf2k7eHDwaGWJx6ks75cXF5DI/5+OGHGT8 +%ueh/JmX++ykcZPLj4nHJw1On9nKQ6QcujnPtZ5PxHweZPLmYMH6oV0QGB5n+ +%4OL7L1PajHW5yOTLRRvp2tM9J3OR6RcuvjN3mfxkHxeZvLnIyX8mKwzhItM/ +%XLR7Glv1NJ+LTP5c3Ou/b/LgbhrI9BMXh4UZzZ9nr4EMhwbKe4SeMfXWQMZD +%A9/FH4OwaA1kPDRwxL86ri1tGsh4aGBk9AfkjtBExkMDd7rFhH/boYlM/2ng +%4ATDJ1seaCLj0THvBndRdrEmMh4a2Du8LXiYOQ8ZDw18dd4yfPs6HjIeGmia +%1DnC7xYPGQ8NtN/c//GDPB4yHho4LPTEwxhTPjIeGhj4wmluhRMfGQ9NLJ7n +%mGv8H588NHHZ7mzu/ko+eWjij+WDrPjWWuShiUM9n54O3a9FHproO9Y5/0m0 +%FnloomCt/IGmTJs8NHG00ZFz9x21yUMTM0cbX4i9qU0empi9YeGrzdXa5KGJ +%Xvo9TS+P1SEPTaxVVV3c76NDHprYMNZ2uixPhzx4+NEnJXbXSAF58DBb97z7 +%xzMC8uBh2zLR5oGFAvLg4QHhp+f3xgnJg4cRws6zVl8VkgcPraICnnu0C8mD +%h14jQjcPXyEiDx7GpEws8owWkQcPV1zemBzdT0wePFzjsF5Hx09MHjzUuXtS +%5ceTkAcPJzs6TQ/YKSEPHgZO/xLhXCQhDz4mdptgumiVlDz4uPCrVpRPhpQ8 +%+HjQZoT/uZUy8uBjr415hx8UysiDj6tc5vYeOUpOHny89DlzY8RROXnw8YP4 +%2ODrCXLy6Dh+bFe1XS8FefCx3n/ZZ4tdCvLg45ul2VYv4xXkwccGvW3hdwco +%yYOPe54UTHjkqyQPLbzbdNf7AkdFHlrIUWPfEBcVeXT0kUty5/FVKvLQQr7R +%zaMz9uqSR0dfRW7eoKfUIw8tdO/WLXFihB55aGGd1ZKK9W765KGF41+08r7P +%NSAPLTzbeGeVaIEheWihmb5sW85FI/LQwtHDQspjbDuThxbqTQ8JHBXclTy0 +%8NGVKzfH2fUkD208suB8pPknQ/LQxrkbyiR29/TIQxtTp7aeKF+rSx7aOOrC +%4WEXL6nIQxsrrU5OsNVSkYc2Opd6f15yTkke2rhlkNmQe4OV5NFxXpgZWaZm +%sx7a+CT3WZoikPXQxvCAL9Y/VrIe2vhc5jRvuaWCPDr2HbF5eaucPHQwNt7k +%dUGHN+Ohg8szu38bf1dOHjroOoNTnO0hJw8dNLh9xX/Wajl56ODH7Zn+AyfI +%yUMHHS8WbhlqJicPHayrWcPvLpEjc//UwWNdgnq45cvIQwc1LfudCQmXkYcO +%9p/le+K+p4w8dNBUEuTfbbmMPHTQwS6g5JO1jDx00LKqzOSEQkYeAlyvv2CP +%WSl7fgjwXtXzlkMf2PNDgJ2Gvm1w/p+UPAQIid39v5+QkocAz37gLb2+UUoe +%AnQ03/Xp3kwpeQhwEH93Yf4gKXkIULVkY9dRhlLyEODI1Z+X+LdJyEOAe0VR +%B6pyJeQhwJeuv+v6f5aQhwBX5R4QTA2XkIcQ+00ct2nwNQl5CHFSXO2vn14S +%8hCi/y/dk2N3SchDiI0ub9bbr5KQhxCD7zSaSmdJyEOIptmFtStGS8hDiP8z +%vvlmmoWEPIRosnSx6K2RhDyEuDVm966XOhLyEGL/5o2FoxvY65UQy276/BhW +%ICYPIVb0vOMXlComDyH+1+p95UismDxE+LzfgvNxEWLyEKEgPUCx976YPEQY +%O2uA8mSAmDxEGCbuv6X5jJg8RLguUKV8e0RMHiKcK3jUNcdVTB4i9JiQmDp/ +%g5g8Ova33QgwXComDxFW/HvZ1XyOmDxE+H2cfOGRiWLyEOHPuV0/m4wSk4cI +%Rw4yGS4cKCYPMb7rbJ08qLeYPMQoWBQ86XInMXmI0eHZiU0oF5OHGL3HDbja +%U0tMHmI06GPUbNMiIg8xrkz1MThdJSIPMYYeLQs1KBSRhxiN6nPOJv5m7x9i +%PHTWI/p1sog8xHhsAudK1icReYjx48pae6uO+w3jIcbS10cy7j8XkYcY7beN +%z5j/REQeErzdq+Rh3/si8pDgogMWm0yDROQhQcPN9tEz/EXkIcFrpVMXXfYV +%kYcE3Ve7L5Z6i8hDgvsWHku//cfhr4cET9WPX7n6gIg8JPhTc/GC8btE5CFB +%jwdPJkzscGQ8JDgqLkTo5CQiDwka3NnW9f5KEXlIMLXx8TtpRx8wHlI0Gfrg +%/ml7EXlIMfRmulV/OxF5SHH1ovT9RZNF5CHFSdue178eKyIPKb5clnwkzIb1 +%6LhObBxhGT2M9ZCiw+ry96UDWQ8pfnXPnzDQgvWQ4qgVQi/P3qyHFH3vnX/R +%2p31kOJQ3dTo451YDykG8swCzfVYDynumvhQniVjPWR4e2LdqYdC1kOGd/wm +%p/ryWQ8Z1h+OifHmsB4yDGyy5V5uFpKHDAsXqVIj64TkIcOf+d2tKyuF5CHD +%AylXDEeUCslDhpv2nfjtWyAkDxmeOjjzCDdHSB4yPGuVevvgbyF5yHDv1AVN +%inQhecjwtYk793GykDzk2NX8Ysuar0KqR46iVP5Q889C8pFj3kuDSS2xQqpP +%joYDWuf/ihaSlxxf+PUoi38lpHrleOOkdvjXF0Lyk6Pa521i1jMh1S/Hx+1G +%CZww9vlMjm2Lp6RZPmLzkGNBj/bY9cFCZN5nOu476/YWP7zL5iNHq7DgG1q3 +%2ec3OVaevW3qdIPNS47lccqA1EAhPT/IMZ0/ycTen81PjsZDV+/4eVFI/SDH +%6c5vmredZ/OU48iN7p7Kc0LqDzl6XXU2Up9i85Xjhv5Nuq5eQuoXOQb5BPUb +%cZzNW47TJA8rtDyE1D9yjPfwP/T7EJu/HOf17rv69QEh9ZMcz7Xoj324l/VQ +%YJ5Gb7M7bqyHAuHBsCF3d7IeCkx4fu126HbWQ4G2ZxsHfdzKeijQftr1psJN +%rIcCM8y/LFFuYD0UaB0SrTt+HeuhwHqfrF0HVrMeCtTvMz3z1QrWQ4FCboNS +%uox9flZgnx4tUSsXsx4KfHuv2kvtyHoocMWXDVEmDqyHAo2CTZeem8d6KNBj +%qnO99hzWQ4EH7B9P9LBjPRToGHFHXziD9VBgb4d9tRemsh4KvHz1XTeLyayH +%Ant8CV8XM4H1UOCMkVYjncaxHgpcN6abg2oM69FR75Tvs97ash4KxM3JbXtt +%WA8Fpr25fWXkSNZDidf1lpa3W7MeSuzbuVfEx6GshxI3Ogi0AwazHko85/Xo +%k6sV66FE25/LdtoPYD2U6PZsQdJIS9ZDiQOv6Bv0sWA9lCi4yNtoYM56KHFl +%U6yt1JT1UOJUt4hRot6shxI3KOSPJMashxLN93vl6PVkPZTY/L6+xLg766HE +%IapVUuuurEfHfHe+JNh1Zj2UOCWiJGyzEeuhxBXVRulnDFgPJf7PetaiZ3qs +%R8f+4bSwPBXroURV1to4QyXrocQj/V2WzJKzHkq8O2DNvpNS1kOJfcIUNz+J +%WQ8lFojPfFWKWA8lBqgn9VoiYD1UKHTg6t/XZj1UOPfEleh2Puuhwp+xr7Y7 +%8FgPFcZ3VWs+0WA9On4/cmO2Lpf1UGFfYd85u9vZ90cV3ihKm5/VKiAPFdrc +%mxNm1yIgDxXqn71l/KpJQB4qPFUxy2FYo4A8VDhoZoDmw3oBeagwwSZpgGWd +%gDxU6HqWj8E1AvJQ4dV1Rw8PrhaQhwp/VxQufV4pII+O96i4M7mTKwTkoUK/ +%DTv6p5UJyEOFlY+G/LOpVEAeKtxxcYAbr0RAHh3vJZHL1gQWCchDhbc1upWP +%7nhfZjxUqO57Lfp3voA8VLjxznOlR56APFR474X0i2WugDxUuDpzSmJatoA8 +%dPHMBqeU41kC/D+QSguY +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113dczfsfB/BzTvPMzoispGGEzJtrv98qZFzRtSJZyYpCXDKvkK3749rE +%jcxKSEY5lAZpUlFJpb3XaY9fnPfn+0+P+n7P5/N5v57v8/30MV7lZr+Gx+Fw +%VFwO5+dPdg3+Y5N4+KVq5W79wOm5nofA0/+3/IGu7PdzEN594CYdqFY+73u4 +%bN7z6/Cs6XVygozdvwX6g/qU18VWKS9f+nndg0fWhkbCzVX0fCCk7Bv4qFZe +%pSyf9/MvwdBjiWr0ndBK+vxT6BD6xJg4VioF6X7ukwShMKbG1HA1p5LGewHZ +%++0H7btVoTR3PBOhGhQGO2rTps+3raDxX4P+DdstzWXlyltdn073ewOhltbC +%ZWfKab63kL88dvbVUeXK0b+uSEiV5ReeTy2j+d/B1ee6G5bvLFNGqgZ1zRAF +%O5xbi+t7ldF6YuBqovuk2eGlSvtfE8ZCg8fgHNflpbS+9xAbPnuZE7dUmev5 +%c8APwLkqCRD6ldB6P0LyuaUuJ2xKlFt+Ls89Hqwnx71NKCim9SdA2Lp9JzKP +%FCvVGkmg52Y18uHAYqonCYKa3nNmxBYpbX9NkAzXJAc2PlhXRPUlwy3jzsI4 +%3SLlz9kcz6RA5tkn7hF3C6neT2CYE3l5l22h8lc56Z8gZmfeAlVRAdX/GdxP +%PHIY712gNPq5nL6pUGIc6jhlQAHlkQq/dwwM1YvKV651+XmlQdEq8YQbq/Mp +%n3TQ+K3kdQM3X/lzNP3AdEjs7GfW7cYPyusLRINQ0jTph7Lh53CqL3Aq4suQ +%S5l5lN9XUOVmFPJ25Skn/xowA/zPXTQf2j2P8syA50NHfen+JFf5a3mHM6Fk +%pgKi7HIp3yxQZedNtizPUcZ//HllQYjzNff1R3Mo728wKLf4nINZjrLbrwVm +%w8fBR5b3P/Cd8s+GaTsvhotCs5W/hnP8Dk5X27ttrPhGHjkw0WnO10mm36ie +%HFgd86Jn4OIs8smBsOJpIT6nMqm+HDjyg38t+20GeeXCbW2vRc9UX6neXPC2 +%2u/FM/9KfrnA3eSv/W7pF6o/Dza+Oe7deiqdPPOg7MDX9TeUaZRHHvydM2hj +%YnUq+f6AKzGlzm7GqZTPD5iXYM7bN+8zef+AJ2KTqsoDnyivfPhkG+SQEpRC +%/vlwPim0b7/sZMovH2xsj1/OFyZTPxRAwve5vbnjkijPAugR4NA8LC6B+qMA +%rlVsSZ9lGk/5FsK9U3H3eZ5x1C+FIGlW/uuZ+J7yLuwab8GecNNY6p8iGO9+ +%4EzhjmjKvwgcbTSuPIt9R/1UBDGWv3lb9owkj2Lw06yb4r3uLXkUQ7NjBkdv +%k5I8iqFu3o8HI7XDyKMYcFl/j9arz8mjBB79+8a1bUQIeZTAyqiAxNa3weRR +%Am8MF036cuMheZRCt1PtYX7Pbyt9fnmUwoDjh5J/O3eNPEohzF0ybo3DGfIo +%A6ltcsLzU0vJowwKl/+5aPPxo6D2KINhTwt+t553GdQe5ZAoVsaeHn0L1B7l +%0G3btXHHpj4AtUc51C31i9g/LBjUHhVQNfNA46vwp6D2qADz/unbP9o9B7VH +%BdhscL75Kf8VqD0q4UyNM7zYqwS1RyW0FS7vcffkW1B7VEJvQWa3MY6RoPao +%gjEF5+18LaJA7VEFpx/vbjvOiQG1RxV8Xvjmo0NqLKg9qkF6LNBP+eAD1VMN +%a68EO1sd+ghqn2qwG7Dkej+nBKqvGvz17Zf0MkwCtVc1bPg2vL1EmEz1VsPY +%1QLfqNZkUPtVQ+VX5Qf/8hSqvwYMBla+cMn+BGrPGuAG7p1skfyZ8qiByg+T +%fpS9SwW1bw2sjO5Xe/tFGuVTA6/GTHA7F5QOau8aMJlqZ+3l/4XyqoXIurm3 +%V1//Cmr/Whi4fyLf5UIG5VcL1teK3Lz/yQT197MWjF2PZoeczKI8a+HJvVnv +%dY99A3V/1MLmiKySE0eyKd866FXzunrm4e+g7pc6GBXTuf+SVQ7lXQfc2kWB +%+pE5oO6fOrjbvqZ4ik0u5d/1/O4B3lrRudRPdRDwMbl9v20eedRDbIXy7am4 +%PPKoh8yLgZZWdj/Iox7e7L5w6+anH+RRD2aen4fdXJxPHvVQ5j55vEd2PnnU +%g2dzg26+cwF51EPtjUdT+OUF5KECYWhP5+JtheShAvel//O93lZIHl33VU4h +%zYeLyEMFE6Lt+hpJi8lDBa17/+gluVJMHioY1So/8W5ACXk0QIjDswKbJyXk +%0QBr37y56Iul5NEAW+L8xsQllJJHA7xvaX4Wv6yMPBogvnlQw7OKMvJoAGXd +%UdHefeXk0QivfDJmWEoryKMRhnlJL3z+r4I8GiHgjPNchzGV5NEI/w4LPZH+ +%oZI8GmGUym6X3Yoq8mgEh2aVLKKhijyaYE5q1jKT6dXk0QQlw8w+LztbTR5N +%IE57eXFUTjV5NMEGvVozT4sa8miCfO302fN315BHE3zof8Ip4n0NeTTB5rTT +%VyJ61JJHM3B2Xk+yX1dLHs2QluP7ZF9XX6o9mqGh3qvZll9HHs1gpJ138fGS +%OvJohhGpbSvCH9aRRzMknvxTd3NnHXm0AHSGXky0ryePFhgzufVOhn89ebTA +%FIXlf0da60H9vmwBB72ipsa5KvJogVjf7lLjOyryaIFkc9tGfoeKPFrBcOti +%8aMFDeTRCrevX3GxCGwgj1boF5dvtFmnkTxaISfwndOBlY3k0QomRgY/Noc1 +%kkcrHHlwr8aiRxN5tEF+5KrEDI8m8mgDX/25591TmsijDXQ0C3PLRzSTRxtE +%X4j0W+nTTB5t0DvJ1zClupk82mBZvHTxNPsW8mgD/rT3CyKftpBHOzxtfHd/ +%aY9W8miHovSG97y9reTRDkGizVMS81rJox3mbDuT/nZGG3m0w4qNx4wKgtvI +%ox0875Q9ntm7nTw6IPjI56aWw+3k0QFXt3OOltW0k0cHuKnyAu2Xd5BHBwQd +%uh5mkNBBHh0wanODxazJneTRAaNzd5xvDeokj04oWeJZcEnGQbVHJ7wr2nRE +%PJuDao9O+G+tcdRcbw6qPTqhbrGv65ZIDqo9OkF/4PUH2zhcVHt0wgl/XbcN +%k7lIxw8UJI05u2gvF9X1cBCl9scwnItqHw6aTkgy+62Di+r6OFh4PmCbFfJQ +%7cXBD/d77v3Li4e/ynXhYNbMBSPjY3io9uOg2bkZWavEGqh+P3DQ4KzK64/5 +%Gqj25ODMx3O27L+qgeo8OCjteBpiXqiBal8O8rYlr5w+UhPV+XAQ7jcpyvZq +%otqbg8YbRm0e+1ET1XlxUf/FX74j+2ih2p+Lnat6DO3cpEX5cfFl8FiLgLda +%qO4HLh4VGQ9ebqBNeXLxRVzfxQPctFHdH1z8ZJQ01uK9NuXLRV9pypgL/XVQ +%3S9dOXlM2Hf0kA7lzcVlwvVLpIU6qO4fLjYt9xi3c6Yu5c9FLc+P2U3Buqju +%Jy7+I9g47mofPnnwcNhL311hx/nkwcPoDudTfm188uBhvJ5BwPGtAvLg4d8O +%UWbPywXkwUOx8/LprzYKUd1/PBxfHFflXyUkDx6+CVq3a+guEXnw8O7Cjffu +%6orJg4cbfWIL4q6LyYOHt5Pfz5AF9SYPHnb7LBoSy+1DHjwMM3QOaV3chzx4 +%GGR6rLdhSB/y0MC/0/Iza7obkocGZk8JsRXuNyQPDVz1oXTUkkpD8tBANIve +%2telL3lo4Pht33pvKOpLHhp4v8+2qhEeRuShgTcW/jN/rE4/8tDAThPrP244 +%9iMPDZQs3DHHLqwfeWjg14FBwQJzY/LQQM9v376Y3jEmDw08qLI/vWKSCXlo +%otuIJIlzowl5aOLIwRn+O8pNyaPr/iGRbdX8/uShiQZZ3+dO6DGIPDTxYWd8 +%jCKjJ3loYknACkHIoO7koYmPex61bXymTx6aOH5MmMAxWUEemijWd/3DaKiC +%PDTR8L1NYa+HcvLQxChweatvIycPTVyy0ju6tlxGHpr4+ftTU/xPRh5aGP17 +%4m7jlTLy0MIBPoOzjg2UkYcWLo25u1G7XkoeWniJJ+x5LUpKHlo4u1D/wror +%UvLQwo+9dwYleEjJQwtv3g84NXWelDy0MP+CXbTbCCl5aGFa6qvF9TIpeWhh +%k7zVtVeZHnlooXsfk+jqED3y0EJN/81nZYf0yEMba819Xcct0CMPbXwdHjWF +%a65HHtoY9Ubv3ymdEvLQRp6Z7+176RLy0MaRL+6M7PtYQh7aaOx83Hn9aQl5 +%aGNV+czgTa4S8tDGluKpjzVnS8hDGz1qb+jqW0jIQxtNjZdpe+pJyKNrfQOu +%pPSvE5OHNmrsSQpsSxeThzb2EY/dXRYuJg8dLHGqds+7JSYPHdTP0f6ccFJM +%Hjro8yfY+G8Xk4cOLg/33Dx/uZg8dNBb9iQ2aYaYPHTQSDJimNRSTB466G/S +%mqpjLCYPHTTfGaJ5VywmDx107N1rZ2WLiDx0cGSywcfEYhF56GD47/XdbNNF +%5KGDzQ+99s+OFpGHLmbrTHzyKUREHrpYzdsd8e22iDx0cfSGqMmrzovIQxeH +%9c7hO3iLyEMXX05J7/F6p4g8dLG5V8m8UxtE5KGLi55effDOUYTq/VMXvxqp +%+jrYichDF2M8suNmW4nIQxc5OS79L1qKyEMX2wyyD04wF5GHLh75Ni1wmKGI +%PHQxooeBdJtMRB58/LtgtCNPW0QefMy//1dxVouQPPgYv2b31tau96nag4+v +%XgSecCoQkgcfl5ekGWhkCsmDj896xRgXJgnJg4+Dd52M1IoRkgcfT1gIuCvC +%heTBx3spQcl1T4TkwcdvB9Ymvr4vJA8+2rqeLgq7KSQPPl6/sci++qKQPARo +%5yX0/NNHSB4CXLpte1WRt5A8BHhO5BR2b7+QPAT4tv2R34W/hOQhwDcnF395 +%7Mb2DwFqu64c27iW7R8CDDgy+tnqFULyEODU0szHzYuF5CHA0nqN5KfzhOQh +%wPXNrYfPzxSShwCXcS1DL1sLyUOAbgdbPkVMFJKHAEuMv6tEY4TkIURJdIVo +%13AheQgx2DsmX9OceQix0Op+aJAJ8+iqqxjSPfswDyFO/2qdv6o78xCi6Vfj +%UBcp8xDioI8Hg70FzEOIs2dGit9pMg8hjjBJutirU0AeQuzu1/j2RLOAPLpy +%0fJw6V4vIA8h5o/au+ZFpYA8RJhitX7dthIBeYhwT8PVy9PyBeQhQutX872G +%f2f7uQgnjHs/aEQG289FuNEsbfTUVAF5iNDC/4iPe5KAPET4wcZD+1GcgDxE +%6DVP8kQjRkAeInx18bnTxggBeYhQ3zH0ZmG4gDxEqMed+JvHCwF5iNCy4mu/ +%biEC8hChysMuOPqRgDzEaFw9euvxhwLyEOPShA6bFXcF5CHGPtutA6fdEpCH +%GJ10Ok5MvCEgj6733mj9xdZXBeQhRpdHdpccLgrIQ4yTps4u3X9OQB5iHLiE +%0/+pj4A8xJguWu/SdJJ5iDFlgK/FzGPMo+s9G9Gt495h5iFGf4HLgx4HmYcE +%b1yO/ufcPuYhweDq0j2Gu5mHBL+9rjj55C/mIcFdaXXOCz2YhwR3xjsO0trC +%PCRYse+k6s0m5iFBt14Bhkc3MA8JDrsksF26lnlIcHPGWfPxzsxDgsbufZ+a +%rWQeEuSb/q+ppxPzkGBBt57+PZcyDwmKeesHmy1mHnq4qseQK+MWMA893JOn +%cXixPfPQw5PWAScP2jEPPbxzQFUQMpt56OH7IXOSamcwDz18azD56LjpzEMP +%df13NR+1YR56qCzjD86bwjz08EuMz1MbYB56aJt57a+gicxDD+tdtf82Hc88 +%9DDZ82xf39+ZhxT3h5z+x9SS1SPFpcX3W4JGMR8pxqc8gqkjWH1STH3s0TvP +%gnlJ0fSDzcIjQ1i9UozrnDxplDnzk6JzxSyrwgGsfik6ZZ22v2nGPKXoenXo +%59UmLA8pZo9qX2jRT0DnGSnaD18wqMOQ5SPFT52Gw1J7M28pHtedavekJ8tL +%irEjxuZeNBDQ/w9SfL3uqMehbiw/Kc77WuC4Q8H6QYobrrQI3GQsTynOsLm4 +%c5Me6w8pHtq1ymarmOUrxYhYTYO9QtYvUsw38Lx9is/yluKQXe9e+umw/pHi +%rPMh+W+0WP5SNJ46uyNPg/WTFDO+WFQJeMxDhqtj/XljOcxDhhkV3kUbOth5 +%RIaH95Q5/td1HlF7yPBskEonp4WdT2RoLbr+p0kznzxk6PV41sv1jXzykGFm +%wZX1z1R88pBh9/DWYzr1fPKQYa0lRC6r5ZOHDDfYTk57Xs2n75sMeZYPAntU +%8clDht3qvYP3VPDJQ4Z7jYOHFnbti2oPGbbt8Hr5ZymfPGRoar9hX1Qxnzxk +%2KNuz/cJRXzykKEiup/LswI+eciwMbNshmU+nzy66onIcnyexycPGRY6KEWQ +%yycPGTqvtDr64TufPGRd++W6IodsPnl0Pe+4Pqk8i08eMrx+uWz6oUw+echQ +%33SWX98Mdh6U4z1RfHb4Fz55yFFof7f7inTmIceTcycaaacxDznGrYvTfPSZ +%echxzb31lss+MQ85BuS2TpekMA85Jtne3hSRxDzkODzlwlLPROYhx/xdlncs +%E5hH13p8EuPqPjIPOe6LmjE+JI55yHGuR3CO5wfmIcd0T9v/Wb1nHnLsNcRv +%oSSWechxXuTV4qxo5iHHPSr90YFRzKPr+VkNd7zeMQ854qL+q5dEMg85rm41 +%0bWMYB5y5G7b1iF/yzzkeG7B/rJaJfPoWp972j9pr5mHHHdecVgWFs485Cg7 +%fbrzVhjzkGPaC9l2n1fMQ4HXE9eH73vJPBRoNUU0x+0F81Dg6CVyyernzEOB +%X6sbZQ6hzEOBKxx5bfOeMQ8F7iqT1c4OYR4KfPhxnfGsp8yj69z3tDhx5hPm +%ocC+kdut/njMPBTIvdB9rX0w81DgmS9xdQ6PmIcCqw6dPLc6iHko0GtC761u +%gcxDgTNqHhzeG8A8FLjK23n86YfMQ4ELx0zk3HzAPLrGFy6c8ew+81DgvLuu +%ofH3mIcC1xTuVRXeZR4KNLvnNIB3l3kocOe3yQeN7jCPrvWIXGPBn3ko8Nje +%KcNX3mYeCjRasrvl0C3mocDl/LOj7vsxD31U8aOPp/zHx/8Dq98lgg== +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw92XVYVNvXwPGhmWKKDhEMbEUM1Ktrid3YCihiKxa2qIAoBnaDioqg2Ipd +%iIFSiiAlId3dHe/8nLXf+ec+4zDnnL0++3tm5rlmyzfPWaXM4XCKVTic//2X +%PbQjVt3cYFYdulf78aRs1+3g/sXs10Yue34Ivlu9ilpRXRX6potX6ew3p6D3 +%mIPB81Kq6PULcCLT8uSYz1WhV3z/PeDzLT+b7vfY31+HuqG1aZyzVaFls//3 +%L/6w03WFS8Ju9v5AMHs28NatZVWhvOSALaN5QbCxuKlu5WR2vHtw9IJdTI9B +%VaG9HU5/qe/1ENLfKIen67HjP4Zpa0yFxp2VoYHydycHPIX7g7qt3xhTSecL +%hulPXSKe+1WGWv17PIfsgLEJRRsq6fwv4Ph7nrbhf5WhX+t7yc/wEj41GBkO +%4lfS9byGjdHN0aNTK0LnKE4Ih78b3xp5r4Ku7y04mTSt67e7IjTb9X8HfAfn +%qnfacSdV0PV+gKqCRz9zdCpCXf53eVtCYAVf9jMor5yu/yPsteqMdHheHqrQ +%+ASeE0MylDzLaT2f4Exd3RV/2/LQyf9O8Bly7xvs72taTuv7DEFfs/Y9LC8L +%/d/Z5KcEbtZSf+MPZbTeryAJG3d0t3dZ6L/lJH+F/FuvdvxYVEbrD4PBwxY3 +%yyzKQk3/dzldvoGp46os2/pSmsc3cJDdtNj5tTR0zer/Pb5D65m2Bu+zpTSf +%cICEdKubjqWh/zua9uNwcNjeZdT1/qU0rwiYe0IWeaq1JLThf4erj4CUA15n +%N0SW0PwiIWxa4f1xl0tCx/w7YBSYHjlkpLyqhOYZBQ4TT4z4OLgk9N/leUXD +%U6zf6sgpofn+gKRLQzM5McWhP3/8ewDf763D8avFNO+f0PbXeKXGuuJQnX8X +%GAPqNjONXYYV0/xjIM7JqiFWpTj03+EcfkGBXmhit7gi8oiFoea/nddeL6L1 +%xEJiqvYdX+ci8omFlFNZbh+ti2h9seD+ZKJ7sloRecXBDY/pq9N+F9J64+CY +%hQ8/+UYh+cWBe9SQ/z5vKKT1/4bvzt+KL44oJM/fwD9oc9tZvZDm8RsuRK8d +%1iu+gHzjgXMoKiPxRgHNJx6W+IU/2r2hgLzjYYX/8yz+iAKaVwKExV/tf0Kt +%gPwTYM3Er5eUf+fT/BLA5dZI8fLr+bQfEsFpc9cL79bn0zwTwbjlV1nHsHza +%H4mQvN5NNkoln+abBEe2XJ239Fce7ZckaIjUXbXvah7NOwlS2/5YHFqTR/sn +%GVotM57vs8qj+SfDHu1xHWs7c2k/JYNd9439JkXnkscfaEvj6Usu55LHH2j0 +%MkqOXp5LHn/A13995p4BueTxB56knjxq0JJDHilwf6nG5wffcsgjBRYdsftt +%cTaHPFLgs9vTyEsOOeSRCvYf9h4rtMgJPfPPIxU0jvNVR9dmk0cqSIzUc90+ +%ZpNHGvwYtS324bFs8kgD2xubT36fl00eaVBQ9X1/smk2eaSDU6uKb0JJFnmk +%Q0+uWuOHl1nkkQ53annfrnpkkcdfUDq+bZXTtCzy+AtN+3+46OtmkcdfaNlj +%cufG50zyyICzi7UHpCzIJI8M8I/aYZRcmkEeGZAuWWr52yODPDJB9PSkNFAn +%gzwywW30om5T7/8lj0wYZlh0+euYv+SRBWbFaR0Yn07ryYLgbNNLm9ekk08W +%TEm6vdK9NY3WlwXlPw3/XDydRl5ZcG7bss0e3dJovVlw1K/oVP/XqeSXBd+G +%PJ4cPTWV1p8NxVMKbC0zUsgzG1JOO0494pJC88iGiPx7NrdUU8g3Gy6Kxzje +%vvyH5pMN6a1DL13q84e8s0HT0UAyISSZ5pUDw/bsX58/K5n8cyBsQ6bvvJwk +%ml8OOK5v331mexL1mQOcHxDwQz2J5pkDHaOFWaE+ibQ/cmDtfjO/q30Sab65 +%cMBlgWzShwTaL7mwdUzLk6QZCTTvXNihrZ3aLzOe9k8ubN7/Jsp5SzzNPxf0 +%cnydzynF037KheHdIq9uOPebPPJgdYKpBXT7TR55cClx4tqE53HkkQf+eht/ +%TB0fRx55oPJSctAnIZY88uDswgHWL1bGkkceCPaa2B8u/kUeeTBw29fBNZq/ +%yCMfbv1NO2vTK4Y88iEvob/n6kk/ySMfVreUCKtW/SCPfNCf/eQU/1A0eeTD +%3uhlBma3osgjH8oadgmuh0aSRwHIek/vofQ3gjwKAB1GDMpqDiePArDy3cs7 +%oxtOHgWgE/9+/73B38mjAAbqlZU+nvmNPAogWtnSqff6MPIohNJC7Y/WXl/J +%oxCiFoS/HHTzC3kUgsEj9ef+7z6TRyGI+L0CTiZ8Io9CODOieOGZ9FDyKISc +%Xe5/F2t9JI8i6Dzq0aMJP5BHEfxYfyqqzuUdeRTB+T32vLRbb8ijCIKsH327 +%HfeKPIrAvjN+XDfOS/IogvXBJqKSAc/Jowhix63hdtgHk0cxOP83dMgXyRPy +%KAbPoaf7DVv7gDyK4b8dU5/afAwij2JY/evarHeSQPIohlvzB00dsOomeRSD +%o/vxzqz1vuRRArsTZaVzNM6RRwl8nvJshfYUL/IogafF2fnCn+voflkCHwev +%0zgVtQUUHiWwMtHiid64I6DwKIG6D0fX+krPg8KjFP7Embw9e+wKKDxKYdPQ +%8eMfKvmDwqMU5t6o5Ol+DgSFRykkcDpDAw7cBYVHKchOtK6WTHgICo9SeDKA +%Z/pK9SkoPMpAd+noUVGFwaDwKINIx7KNrtHPQeFRBpaW81ISnr4EhUcZpGWU +%ds649BoUHmWQ9KjT5sL+t6DwKIO+r3q1c1a9B4VHGTzLyf4weWYIKDzK4Rtu +%MRpoHQoKj3IYZZf3adnZT6DwKIe/qycen3LkMyg8yqFL1igfbbcvoPAoBxU/ +%G+G77V9B4SF//ip+KW4IA4VHBbgOM88UrvwGCo8KmOXzNPazw3dQeFTAgfqw +%9PL54aDwqICffYpCvGdFkEcFfM/ZZL17SiR5VMDxSV8Tt46PIo9KaEjs8mgG +%RJNHJej8mvV8+qgf5FEJu0KMcvyG/ySPSpjvn198dkgMeVRC+fvY86qDf5FH +%JVzYaFAxQD+WPKqgclvtm02XY2k9VTAtN6mbnl4c+VRB7dlrZXqX42h9VTB0 +%4fusgXq/yasKmhcmX313+Tf8W+7qKjgU+2D8Lv148quCHqoLQ375xoPi/iA/ +%vn1/tzFGCeRZBUPumZ9W9UugeVTBjA+LDfRME8m3ClaHR5697p9I86mCbDuz +%Oyu7J5F3FaTd/V1+PiiJ5lUNE23umRX0SSb/ajhuONbmiPx7jGJ+1eCghIa+ +%g//QfqgG3xNPHgte/6F5VkP4l+3hBaNSaH9Uw9NUx3Fln1JovtXwsNqzfMfE +%VNov8ufvT2k6/EileVfD/KcvLh6ck0b7pxoM9Mr366ek0fyr4aeT3ZZWx3Ta +%T9VQHBI1aW5hOnnUgFr40sdKm/+SRw28nhxwR6nxL3nUwKrOX3FH3DPIowaK +%DI/lzdLMJI8aWBIWl3jgbCbtvxq4Mi8x6I4wizxqYNyJn2qX52SRRw3EHxUM +%iricRR7y4wVd0xz5N4s8aiBn9Phbf82zyaMGVKpGiB+szSaPGkh92f3Yg8fZ +%5FEDvMXqFcV12eRRC5xxO1rmjMohj1r4e33qT65nDnnUwtnYX5dqInLIoxbm +%LbpiqinOJY9aGKqnKVi8MJc8aqFQ8mtp4fVc8qgFu6v+V98V5JJHLaRr3Twe +%PSCPPGrB4m3IQY1deeRRCy3LNqqvD80jj1pQ43HaLDXzyUN+PXfjK6xn55NH +%HTgdWbLK7ko+edSBpvY9i4jcfPKog3737p850L+APOrgS5vw0rZdBeRRB/7O +%2c+DPheQRx0E1AgdtQSF5FEHPl39e6csKCSPOkj/o4Qp/oXkUQfz/Fc/E5UX +%kkcd3LbS+3DRuog86kB0eELEskNF5FEHPd1X9F0SW0QedRAvOvr9rXExedRD +%2LvqyCXrismjHjbN21jR81UxedTDt4DYpctUSsijHsww7VyYbQl51EPkWusd +%ftdLyKMeYkK6C26WlZBHPdiEZvatGVlKHvWwIXj7voPHSsmjHob0DN22608p +%edRD/rzaO18tysijHjr87p5evKuMPOoh/rvznOnhZeTRABt87ilt0isnjwb4 +%41I+qHZNOXk0gJnJjx0Rb8rJowEe1RzrVcStII8GaN88qmKRfQV5NED+144/ +%Ux9VkEcDTOAZzdzWWUEeDfC8JHWj6pxK8miA99WDs7MDK8mjAQ7ZbpCNaKok +%jwYI/FRlGNejijwaADr2rI2ZVUUeDXBqit7Quj1V5NEIz5qrtNYEVpFHI1zx +%+vAzPKaKPBrhq75j8IHmKvJohIvaSeUruleTRyNYmxuZuc6qJo9GKFvNmRDq +%Wk0ejTAxqb3PpDvV5NEI+guqVIrjqsmjEZRTalzT26vJoxHMqkdpN/euIY9G +%2P22Z8qwBTXk0QidX3LLP3nWkEcTcLtnmxx+UkMeTbDQ4eqU1ek15NEEM/c5 +%V5zm1pJHE3iPW8ipH1ZLHk1g+ZRvfnJlLXk0gavLI32Hc7Xk0QQ8aC3a8KmW +%Pj+bIKET1j+vqCWPJlCa9PLGCJM68miCQwYTZqpOryOPJtBqfSZrknem8GiC +%GXYux0bcryOPJkgZ3e/E55Q68mgG7V+/9m3g1pNHM9TxdtusH1FPHs0Q5587 +%JmhdPXk0w6TdoweNvlJPHs2w+/Sjbs1R9eTRDKbJzq86WuvJoxlcZLOU5/Rv +%II9mUNpbujhnaQN5NMPZiMaAkDMN5NEMWZMHL6j40kAezZAU8mPIKvk+Ung0 +%Q2FHjptJr0byaIGVPXtv7WnfSB4tMMVr5FuXU43k0QL9HhWNNZY7KzxaYETl +%S2N+QyN5tMDYqEvR0/o0kUcLvBo/7savpU3k0QIbe8R+DDvfRB4t0PfdzB6N +%EU3k0QI7Fm5o9OpoIo8WCLFYOF8/Q4oKjxaIfXJO716pFBUeLaD5JF40q1mK +%Co8WkG36XifUkKHCoxVarG0PpWvLUOHRChmaHZN/mstQ4dEKCw+Z7Pw5SIYK +%j1b45N1rS9EYGSo8WsFhsINJlxkyVHi0wpqVnGtb7WWo8GiFQ5udshPWyVDh +%0QptGxdtddgtQ4VHK1SEZ5nUHpahwqMVat2dBPcuylDh0QqOlrsa9gTKUOEh +%//vpD6rXPZehwqMN7IOXzHX9IkOFRxvYuukdvhYnQ4VHG2SmvHEpzJKhwqMN +%rH4ccJxYJUOFRxv0O9/o+aNThgqPNjgwrO7ZZpE2KjzaYGHiEu3hptqo8GiD +%8+laa7oO1EaFRxu8v1jxujtoo8KjDUz6en6ZOUubPNogfH/ieD9HbfJoAyja +%0SLdok0ebVAx8uqpJx7a5NEO626UqK09q00e7VBuUpc46ZY2ebTDD2fJ7kXP +%tcmjHQb7LjA4FqZNHu1wYULt4cJEbfJoh9l/zu7dXqhNHu2g17Ww26BmbfJo +%h2U3eF15fB3yaAdlw/fnzU10yEP+3MZnxcqBOuTRDk3LqnwSx+qQRzvMeZw4 +%fc88HfLogEQ3t6Xz1uiQRwcsueMhXemqQx4dMNxNsvjxSR3y6ICSEaeDevvr +%kEcHWOxy6Z33Qoc8OqBVvfJBSoQOeXTA/uH3tsn+6pBHB+ztlr/0WLUOeXRA +%26WGmxPUdcmjA5Tm9soZY6RLHh3gZDq2+PAgXfLogFmrHs/Qm6hLHh1gddNA +%udFelzw64YptnorJVl3y6IQ+1UOWXTuqSx6dsPH4VgOXG7rk0QkhLuU9/F7p +%kkcnpFefi+kVo0senaAzwfqdYYEueXSC2q4wvmOHLnl0wpi/Wp56enrk0Qmn +%BY5lYwbpkYf8+ZGlvIQpeuTRCcsObXuXukKPPDpBd9vgCkc3PfLg4L4bhfdd +%fPVoPRz8kHxCvf2FHvlwMPY294xxnB6tj4NuEw8e+16uR14cNEy2tdTh69N6 +%Odir4Ty3vZc++XEw7WTKxzuT9Gn9HDR+YN2larU+eXIwtCxGWH1Yn+bBwQil +%P88ig/RR8XuGg9OcvVLXRurTfDiof23AqOJSffLmYPLNgPEzRQY0Lw4K04Xd +%Aq0MUPH9gYOcZuONnYsMaH4cDBKHa69wN6D9wMFxyz28iu8Y0Dw5uGLtg+sn +%fhnQ/uDgn54R8TubDWi+HDzQ2NF6s7sh7RcOWnIe2JrONqR5c1DdL+Zoi5sh +%7R8Obgy4u6zbY0OaPwffzEk79iPDkPYTB20M1B8ki43IQwknfM4Zaz3eiDyU +%8HAT72L8biPyUEL19xdFux8bkYcSbsxtHPktz4g8lLDzmajFxMiYPJQwymRD +%6WtbY/JQwqS5QeXcQ8bkoYRio/jMzmfG5KGED8x9z0xKNSYPJZzqeMSgvs6Y +%elPCaxNmDBe2GZOHEo7fYdTVqMCYPJTQPnZyq/NdY/JQwkpf9Z+bRxqTh/z9 +%vMC0l1eMyEMJhzyueFUXb0geSsiPFJ01b2YeSmh0vXfJTWPmoYQyk6FqxtP1 +%yUMJ7XY9Vg47qUceSng0+kLj8Fxd8lDC0dkvSmymsZ7lr7tVeZwMY/cnJWyo +%35//aLoOeShh1/aYyxOztclDGTt8Bl1K+//7rzKqOz/MaeqtTR7K2Ffz0Zf6 +%VPb5qIy/3+/9r/Ac+3xRxm6hya2/bdnnpTKGe7wWWGizzxtltKn3GV6eJiUP +%ZdQN6xfWFiQlD2V0KDJKu7RLSh7KOMki3mX6VCl5KOOOlgwDblcpeSjj9skD +%T65pkpCHMp5eobPVNF5CHsp4Yu2CublPJOQhX1+QqdGDUxLyUMZXt4/b9N0s +%IQ9l1MozzbKdLSEPZSzh+quVDpGQhzLe7TjSu8hAQh7y+Ri+knTjSMhDvl7t +%tEzfQjF5KKOSUso6vVgxeShjd+eFPrffislDGfd/abUyDhSThzL2i6vftOW0 +%mDxUcIy7hZ/PXjF5qGBj4rTxXmvF5KGCz6fbv7VcICYPFZxpO7DywHgxeahg +%c8VNHXcrMXmo4M//bjiKuonJQwVbu8WsHCkTk4cKtvCvDstWEZOHChbVP1Dq +%XSIiDxW0sbkzY9dPEXmooJnOw1sfgkXkoYJ8lWnd4i+JyEMF7bc0xETuE5GH +%Cm6/8PTjiRUi8lDB9NERyWrTROShgufn3JwxzEpEHipo+XLsXB1jEXmo4M0Y +%8UM/NRF5qOCms35XvlVqkYcKBjtZHz2YqkUeKqjT8Wzat29a5KGCNeu7jrwd +%rEUeKmg4++bkTj8t8lDBBKt7L7K8tchDBeFQ7dzRu7XIQxWnDgmMEa/WIg9V +%jPT2rbadp0UeqvjNs2O/yjgt8lDFDM/T3C6DtchDFc8Ihx/yMdMiD1XUv3Gj +%p6tEizxU0a7s/In3SlrkoYondYLHz6oRkocq2jxetnxsrpA8VNGZa831ShCS +%h/x1/dm3TL8LyUMVh/f7O0PtjZA8VNEqqN9ly/tC8lDFiVHCF8+uCclDFR3a +%ElN3nhaShyre6Ts02sNTSB6qaB2f6xmzQ0geqhhq+Mhr8ToheaiiZnjnDosl +%QvJQxcMfDacMnC0kD1W8cWe01dYJQvJQxc04e2PFCCF5yOc5v9zjzgAheaji +%f86LOk53E5KHKo4Xhbk80xeShxq+//7KkaclJA813DvtcffjKkLyUMPO1LGb +%xzQLyEMNh8ycsbd7pYA81FBzafC4ofkC8lDDZTqLTm9NE5CHGpZ83dUtM05A +%Hmo4OfI+uEQIyEMNt3vUDx0UKiAPNfRatq639JWAPNRw4+s6E9NHAvJQw1yl +%J5NnBwrIQw1/Lr3RHnhVQB5quDXe84b5eQF5qOH333bdQrwF5KGGK/8ILT08 +%BeShhhseZoU4ugrIQw1jq9+kLdsqIA81NNo91v3QegF5qOGLVhXJ9+UC8pDP +%79jYwB72AvJQw69P6sJuzRWQhxo+5R+fNma6gDzUcMfb/a4N4wXkoYZ/Ays6 +%I0cLyEMdDVTjo94ME5CHOoYtGZjyaaCAPNSxxwZeeVYv5qGOylayRV3MmYc6 +%Kk3bULDNiHmo49PDe3pkaTMPdczUNLBbo8U81LFzrOC8iibzUMcgt4jVL5WY +%hzqeqNRxcW/lk4c6JmXMVXes55OHOu6rThsxr5JPHur4M2BM45JiPnmo4xKx +%uoVrLp881DEw6q7347988lDHN00bDjQm88lDHR1XPmmx/c0nD3UM2TGr5f0P +%Pnmo41uLfPPR4XzyUMf3XScm/vjMJw91HL0xw3jzBz55qOOaWbUt3V/zyUMd +%v0wzby8P5pOHOvZ0con48pBPHvJ5Tc4/8zCITx4aOL0gyuz2LT55aKBHbdzG +%h3588tBAFeWOO999+OShgYPuDl1Xdp5PHhq4T999TI/TfPLQwAdrLSY7e/PJ +%QwMbi5ZqhnjxyUMDV6dzLc0P8MlDAys7V/c6u49PHhqobpPqJN7NPDSwn8Y5 +%U79tzEMDNV+knB+6mXloYEfTy81/1zMPDYztkht/ZjXz0MDD4Sufzl7OPDRQ +%X9u0xWQp89DANYMMZzcuZh4amOxU/Tp9PvPQwL4R9f4/ZzMPDez9dtLh6BnM +%QwOPTg6I+z2FeWigmv3EcQUTmIcGcvu7RKjaMA8NLDFvshw4hnlo4NZjQZdW +%jmQemphpvsY7YBjz0MQV6v0elg9mHppoNeveoLEDmYcmzk0/GHijL/PQxEXh +%uhyNXsxDfrzpAR57ujMPTUzqca9fXVfmoYmhb2o/7DZhHppoPO+JqaYh89DE +%6w45//nrMg9NvBf6MBNkzEMTF98Z3a9UxDw08THf8JafgHloom5tSuICLp9+ +%f8rPd6StzEideWhi3TqxZrEy89BED8f5Az508shDEwNd327wbeORhybqhF2f +%tq+ZRx7y9RmE9Hdu4JGHJg7Jq3uyrJZHHpo4zcZCd0kVjzw0ce/X+Sucynnk +%IZ+v8fwZziU88tDESouOcrdCHnlw0aBvbqRvHo88uHi83XTju2weeXBxwfTq +%1twMHnlwcbLm9q/SdB55cDHt5rlBU1N45MFFvp2T9+EkHnlw8dQmA6/oeB55 +%cDH8nN4unTgeeXAx0/Ne56oYHnlw8cql5SEfo3nkwcVjmya3GkfyyIOLXX0m +%9T3wnUceXGzWbU8s/cojDy6WdB8w3+Ezjzy4+GDw+te/P/LIg4vWx9OCZ33g +%kQcX3wgmmce/ZR5c1N8cKXR4zTy4GCBy8C5+wTy4uK1gw4V9z5gHF98GCLrq +%PWUeXOzhHVf48hHz4OKQ08eP2D1gHlzc3vKsWfUe8+Bi28bAvy/uMA8epsVX +%aq8NZB487Mrv9sbsFvPg4f2tKh8zbzAPHkbFb7UN9GMePMzQnvZjw1XmwUO9 +%BVuPjfJlHjx0nucUL7zMPHj4ouBeVP4F5sFD4cQTsz+dYx48PLX4euutM8yD +%hzu52hVHTjEP+XUc0lbfdoJ58NDnQr27kzfz4GGu/lWzeUeZBw9DHk57PP0w +%8+BhdPnpBZMPMQ8eFo7JmjDFk3nwcNecFS0zPJgHD7+nxzfNd2Me8jnv1lJe +%vo958HDgJf3D21yZBw9t1+isOrabefBQypEW+O9kHjycXTN04aftzIOHomsj +%qrK2Mg8+ui0xDldzYR7y+9Cd8V0HbmYefAy4UHnJfiPzkN8nNrhPOuHMPOT3 +%sbbi+Z/WMQ8+frC/kdO0hnnw8b86s19Wq5mH/L543/zPtpXMg4/L591seLmc +%efAx92fuxbZlzIOPYpHD3YmOzIOPEVM6Us4vYR58FF1WM8y3Zx58XNX+NNPa +%jnnw8XifAMGZRcyDjxMnqF8pWcA8+HhS2WP2lPnMQ35fvJJ69f5c5sHH4RyL +%3lpzmAcf0xd9qdlhyzz4eFWUbJY9k3nwceDjZp+ZM5gHH826aPf+NI158NGg +%aM9Iq6nMQ/658tYp+v5k5iFAs0dzw7tNYh4C1Lh3a5P/BOYhQLcOpzVm45mH +%ALd0fNALsGEeApxun7Cl91jmIcC7pTJRMDAPATrH2W3+bwzzEGBS6C7zqP+Y +%hwA3+ElLFo9iHgL8o5TBqRjBPOTfe0uyfQ9ZMw8BxvYP5pkMZx4C7GNjFfZ6 +%KPMQoP9Tw/L5Q5iH/Hvfw8fRDYOZhwBfF6X4+FoyDwEO2NeUPGYQ8xDgs9zZ +%WDCAeQhwtNeGfWf7Mw8BRs8O7j6mH/MQoM+kdQ3lfZiHAPe47hx4ozfzEKD9 +%1GPFc3sxD/n36h7h/XgWzEOAPz73bP3cg3kIMSanydWtO/MQ4sUTGVUjuzEP +%IX7RXb6t1Yx5CLFS1yL3Q1fmIUT3wDfOB0yZh/x30/4818ldmIcQh5760CYy +%YR5CPPSpMyPNiHkI0XLryJV3DZmHEBOnJq3bZcA8hPj2L+f1ZH3mIcSI+Dgv +%Iz3mIcQFrqHSah3mIUTrETFvw7WZhxB7Buzz8JcxDyHOzR9lsF/KPITYlqGe +%YCdhHkKc/t3+ywgx85DPozM92FDEPIR4xpQf2S5kHvLX7wXeyhEwDyEunryU +%E8VnHkI8V9hd/TmPeQgxuM/PD35c5iH/3edxcI23JvPQwhGnFlvs0WAe8t/5 +%9SZX1qkzDy2M9HB+YqfGPOS/s9+VBc1UZR5aaF615MJ4Feahhd57M9tHKTMP +%Law+btIyRIl5aKGL2oIRgzjMQwvPLVxr2a+TSx5aqFbb9rpPB5c8tDCxxapv +%33YueWhh6YjlZv3auOShhYVHuv4a2MolDy3c7jkwf0gLlzy08EzQMruRzVzy +%0EK35W6fbJq45KGFuW0DNKc3cslDC78Vx6QtbOCShxYKVk1atFL+uavw0MJ3 +%JywOba3jkocWFl+J6eJZyyUP+Xq9hxy8UMMlDy0cr+kfcqeaSx5a+OitrOhd +%FZc8tLDPz+9r4iq55CFC3cSka0UVXPIQYR/D1c5K8ucKDxHOv6xhZ1TOJQ8R +%ThumNmF4GZc8RPh+bMvcefLvDQoPEc749DhnawmXPESo4pq8/1wxlzxE2COm +%0fFFEZc8RLindO/VpELmIcIt3OdzWwuYhwgLB9w50bWAeYjQr8T29cR85iG/ +%3guD52zOYx4i/Har+bNPLvMQ4X2Tl9Zfc5iHCO0GaAytzGYeIqySFv0yzmYe +%Imzt+Nx3Wpb8e9M/DxF6fGtw35vJPER40v7gvocZzEOEO9y2xGX8ZR4ilIQc +%PSL9yzxEuOqS7bpJ6cxDhJrfbdfuT2MeIowcYnTlRSrzEOPie38sylNoPWpi +%HDql++yeKcxHjJMD9qx3+sNV/L4SiTFj9PrVfsnMS4zCLocbUpNovQZiHJQb +%9Uk/ifmJsf/GH6sXJdL6u4lxn3FXb58E5ilG28lvrFPjaR4DxGjS093TJJ75 +%yq/vcOVJp9+0X63FCHc44XfimLcYI3vN61keS/OyEaPHU9fbVrHMX4x/0qe4 +%7/tF85suRpedvZ98i2H7QYwOa5MniWNongvFeHez5lr7n2x/iFG6qSwj6AfN +%10mMu33E3vXRXPr/M2LcmfLOcXw0zdtZjKFFtyaej2L7R4xTQxKm5kXS/HeI +%Uav+cvbQSLafxDjs774DRyPIw0OMF6dKPNPD2f4So9L7oNGDwsnDW4xL736x +%PvydPE6LUfy08EP6N/K4IMa9nQNdhnxj+0+Mzlofu58II4/rYoz1v7Qr/yvb +%j2Lsod3zGH4lj7tiNL/qtfXqF7Y/xVgbURTR+Jk8notxTop9+bzP7P4hn2fR +%nsPPPpFHiBi9v8ycLfnE9q8Yx7ROt3YJJY9wMdpMKNz7+yPbz2J8bRmYN/gj +%ecSJ8Zn3tbGXQtj+FqOeS6R58wfySJfP67j7tSUf2H4Xo07WyxVf3pNHgRjD +%v3Gn9XrP9r8Y59343n7qHXlUiTH54CnlhresBzHyup8/svQtebSIceIky6jw +%N6wPCfImunEGv2F9SHB5w+kF116zPiT4yajIU/M160OCI3RTi7a/Yn1I8P6B +%rvNyXrI+JHhmXvDbWS9ZHxLcmnd7QOgL1ocEN2+z2j/gBetDgpUOlrIbz1kf +%EhRJTs8TP2d9SHD6l/SOA89YHxLM1vXIqwtmfUjQRFY+Ym0w60OCQ66V6aY/ +%ZX3Ir0fXPmX2U9aHBK8Orov+/oT1IcGVfNmm0U9YHxJU7Xzj+OIx60OC5Q+l +%yX0fsz4k6DRo95/AR+x+KsFeTU+bTR6xPiRombYs+vJD1ocEfU+UBkgfsj4k +%aBqy/NnJB6wPCe538+XzHrA+JDiH093/8H3Wh/z6ZitPUr3P+pA/r3dw8rzH +%+pBghVvIcqV7rA8Jrg3r2ONxl/UhwdATlQ2cu6wPCRZPNz1+IIj1IcHMEVwP +%5SDWhwTx5gMrrzusDwmm3+NlaNxhfUiwR9rXTO/brA8J7ozO6SW6zfqQ4MJ+ +%M36dD2R9SHCFZuZ4g0DWhwSl16q8bgSwPiTodUWrqEcA60OC9kX3vz68xfqQ +%yH9PRlUNucX6kOAMfdd+If6sDwk6SAP6TfQnjwIJTvjoZhZ7k/UhwbFGz/Xt +%brI+JBhXku6fd4P1Id9PE/c1b77B+pBgxrQ67bbrrA8pug2fa3f0OutDik/s +%dq7Quc76kOLMhcEzA/xYH1I8YCrbaenH+pCiclBYbug11ocUc0x9fs26xvqQ +%4rqI/g8yr7I+pNhRa6W+5SrrQ37+RWeclK6yPqTo02XbwPNXWB9SvGp+jtvj +%CutDig8Ww7nXvqwPKR4/kxs71Zf1IcXYniYLM3xYH1IcvXhb3VYf1of89cu9 +%MjR8WB9SBM/lln6XWR9StN07c7bVZdaHFIc2+a2JvMT6kGJJxeHJyy6xPqS4 +%I8o2o/Ei60OKl/PcY09fZH1IsV4y06zXRdaH/HifNJs/XWB9SDFyx9M3dhdY +%H/J5j7C2rTvP+pDiO9kjvdPnWR9SXPGr5mDv86wPKf52F+K3c6wPKQq2meg6 +%nWN9SHHMqoCX7WdZH1LMU3Oec+Us60OKTpsLXYafZX1IcXF08qLEM6wPKe50 +%SF+77QzrQ4oG5zdNlp5hfcjnYf94TvBp1ocU9z27mmZ7mvUhxe4xfl2rT7E+ +%pNimOePz2VOsD7m/Wr+8wadYH3JfTumwhJOsDykeGzR0z86TrA8pJrmHb9I/ +%yfqQ4vh5zRbvT7A+pFho88pu6QnWhxS//ne9WukE60OKfbgD228fZ31IMfPS +%lZCpx1kfUvQYc+ZXpTfrQ4YXu3wruODN+pDhbmn/8yO9WR8y/HZhQlvWMdaH +%DMdm7j5/5BjrQ4ZvRjvYDTjG+pDhzqcLkhOPsj5kqPLMInX/UdaHDCP9Hkzs +%eZT1IcOS1idhMUdYHzJU35aXt+sI60OGsn6qaHaE9SHDISk7rKIPsz5k2K1o +%ydYdh1kfMpxnOrzG9DDrQ4ZDBeP+RHmxPmRoXmG/facX60O+fjX1ueZerA8Z +%+k7Xt4s5xPqQYbD5r7+uh1gfMrwQvJHb6xDrQ4b97cVWiQdZHzIcPmvBE8+D +%rA8Z3r/829jyIOtDhjru+ksyPVkfMsxcN8zllCfrQz7Pvu8jRnuyPmR4t8fg +%JeUHWB/yeW1sWuB3gPUhw6kfg/rNOMD6kKGueVpnuwfrQ4bTnxkrPfFgfchw +%WlwNd5kH60OG1qXyu4wH60OGASPqm766sz5kuMP53eqd7qwPGbo+K2ro5c76 +%kGGb106zdDfWh3z+41f7nHZjfchQeewUu3FurA8Zbnru4dq4n/Uhwz/n3oc9 +%2M/6kOHxVe+PLNvP+pCh1bCJ3jr7WR8yTHjbMjl6H+tDhhmZCYke+1gfMvy1 +%wSV/2D7Wh3y/nMsYV76X9SHDo9OOBwfsZX3I3/986F27vawP+d/r6CyU7mV9 +%aGNE04ukSFcu/h+i+QLA +% "]]}, +% {GrayLevel[0], LineBox[CompressedData[" +%1:eJw113lczNsbB/BpmmavZokkW7cI19ZybZfOI5VoVymKJCJbiVC3ouxLJJWI +%uOLey00JP1uYuAiFlLQo2hftm/b6DXOe7z+9pu/M+Z7n8z5zzjM6a3yXrmMy +%GIxOJQbjx1+8Ul26/o4JaJf9oZG8qCxoP7EvejblzHx8HU3Kp3Tv3KfSLrs/ +%5kC9w/0EYvLPcO7Gt230/hUSCL43zWPaZOfO/riukUvnfW4OX9lG359MLp2e +%4fRVr03W4PDjP6nkOLep5s+GVvr5O8R/ZtyTlXdaZfz8RL/5/HvkN9fQC5rB +%rXS8B0R9W1JLxsJW2ST3k886Jz4i3df91u0UtNLxn5CuwPe3dXNbZFfkn85P +%TCdX6xcp551roc97SsaeXSA7sKZFZvTz+o+cu2fBNpjcQp//nGw94u32sqJZ +%9l/nRPkTXpBj6y1fGm5vpvPJIAe1JiYnMZtlS38+8BVJ3xWvPzOqic7vNXmX +%EvnwjU6TrCzox4BviOGo0dt9UhvpfLPIRH1zkfaCRtm2H9Pze0vYlebXKrIb +%6PzfkfS0E4bpqxtkCo1sovf0TMejlnpaTzYxStlZk763Xmb58wEfiEWWzrh2 +%UT2t7wMZ+76vYOaf32Q/nuZ+Moes+8e++ILBN1pvLulmDSpNe1Yn+1lOfi7J +%2q+kW7C0jtb/kahbvitKrqiVjf0xnTF55G7pwekXd9TSPPLIxOEub9JUamXr +%vX9cn8iI7SY3hWdqaD75ZJnBKub+iTWyH6NpJOeTzZuAO/FhNc2rgESvbe6u +%saqWff8xXGcBufAx51FuSRXNr5DYvXROH+ZXJTP5OWARmRzfp7qDWUXzLCLW +%He4uddGVsp/TO/CZBCzmBH3Sr6T5FpOh/7UEZD2skL3N+nEVE6dLHZ6DNhU0 +%7xIyI377/ZCyctmwnxP8QtiJLrGTAspp/l/I4/P321R45bKfw7l/JfY7Pe5r +%XyijHqVkzICdT4JBGa2nlFyws3TUfllKfUpJsdLhEqZbKa2vlPS/D9zlWPqV +%epURrdyQ2z6Cr7TeMtKt22sTP/ML9Ssjq13q/FevKaH1l5M7ET3VshPF1LOc +%kDuSwZK0zzSPcjLX0C00qK6I+lYQ3U3SeDfNIppPBdGPdB78xaKQeleQw8KO +%x3sCCmhelWTqNqdxm6/mU/9K4mhePz4z7xPNr5K4f+v9PMj+RNdDFel6wdRM +%nJ1H86wi1zh3H52OZoNifVSR624L1xgks0GRbzXZ8i79iyyDDYr1Uk36NijX +%TStjgyLvamL6yND/VC8bFOunhlxcJ/izUMoBRf41JInt/og3lQOK9VRDLryI +%mDDVggMKj1rSvuLwQkMPDig8asn1lPDrWrs5oPCoJbuW/6vaFskBhUct0Q4X +%cFKvcUDhUUfMohOuOT3jgMKjjqiP6tSsLeKAwqOOpAdUqy1r54DC4xvRyOmX +%3RZwIfKnxzfya5ZdbKcuFxQe38jbm0kfpfO4oPCoJ5n9jp1jnbig8Kgn+5M4 +%9fzNXFB41JNAPe7Bin1cUHg0kKIzyeHR8VxQeDSQB8Xx18ff5oLCo4Esn9v6 +%9NIbLig8GoljSnrGYBkXFB6NJOiwh/eMHi71aCRXhwseWol41KOJbMn0Zi3S +%51GPJpImWOM8zIRHPZqIzW1OZoYTj3o0k4TR0+4s38SjHs3kaKnV8PQwHvVo +%JiwDHw+lOB71aCFWMIM1NplH62khUS9reRrPedSnhVgbGJ//XMij9bUQVePI +%wh3NPOrVQiKs1OrqWHxabwt54B+UPmUkn/q1kFcjWx1Np/Np/a3EyPjRhwlm +%fOrZSlK5r5vyXfk0j1bCuFbW5rCFT31bSalhsvf5MD7Np5W0rPhNIymGT71b +%SXH1dM2Qa3yaVxvxbw3J037Mp/5thPVCemR3Np/m10ZeXnTNiKngg+L72Ubu +%elZxt37n0zzbSO7iPZUqPAFdH21EZ6bqTXttAc23nZjbs7udpgroemknc097 +%zFcnApp3O3kxm1+xw15A1087+T1jUVCkp4Dm305m/hK/aqm/gK6ndhJ0+qHa +%w3AB9eggy2sdZ72NElCPDtJQlKsTdllAPToIz1qq/y5VQD06CPu37G1P0gXU +%o4McKw2fZ/FeQD06SEZtZLRXiYB6dJCjG7duEdULqEcnCf3rz9k23QLq0UkK +%HErbR6gIqUcn+Tt+dukmsZB6dJKsC8b3zEYLqUcn6XltrHt5opB6dJI6rvX6 +%UCMh9fhOVmUM+uTNF1KP7yQyqOvBP4uE1OM7ebvdM6zNXkg9vhPd3P2bUpcL +%qcd38m6q/5QyTyH1+E469l2Yvd9HSD26SB37zalYPyH16CLrJ++ql+4SUo8u +%EqezIHgwWEg9ukiQC+uEY7iQenSRoqilT6SHhNSji+zZ4Fv7+zEh9egmH9Sm +%2r0/IaQe3US3503em1NC6tFNJp2McjOIFlKPbjLayrKOGSukHt3k5DdG3IIz +%QurRTbQttkqb5K8VHt0E3L5bCeKE1KOH7LnrnHNOfl/h0UP+47jMuxSLHj1E +%dbxgkXYMevSQvcabWBqn0aOHzDDfueB4JHr0kD7fuqDDEejRS3Z1nq/jH0WP +%XmJw+Z3WiIPo0UtsjmnF3AsT0v2yl7wemPBLQzB69JJTUu20tF3o0UsKTQaL +%J/mjRx/JC1N78Ptm9Ogjz6Z5CLrXoUcfMbw4RcvLAz36yGpXJdtwV/ToI9yC +%m87uDujRR9JWbwvrWIwe/eTS+3y2gyl69JMvo6bl75yLHv3Ex+PTk42G6NFP +%vubv/NNwMnr0E795re45OujRT9gr8sfba6FHPzENeTGYJEKPAfIhKHKoiYMe +%A2TNeda/2kMC6jFAjpjxcmZ+F1CPATL54f63CxoF1GOA/JUOdxdUCqjHAPFe +%/+z4/M8C6jFIApqjOUY5AuoxSMIuGSye8FpAPQZJV9XacaPk32+FxyDpfMxa +%NeyegHoMkqf3v/2nkYz71SCJPrabMe4q7ldDJDUmjGt4HverIeIoOKu+7DTu +%V0Pk1OQRJYeO4n41RPr3Maa+DsP9aojcC7YyHBWI+9UQmfXilvp+P9yvGDBB +%i1U7uB7rYUD7pkTdUx64fzHAMMi9ZI4L1seASZ4uen22uJ8x4KXppLAPFgL4 +%Wa43A/ROTD3z0gT3NwbUloqO58wU0P2BAQEaVqo903C/Y0BC2JK7s/QxDwbY +%JwiXxY7F/U8+fuHBa6ojMB8G9J4rq7oswv2QATc812gv5WFeSlAyh9czion7 +%oxLMspycxOrj0/yUABqjVTkdeH4pQaL5yJLxjXyapxI0vp/DW12N55kSJI3R +%s7n7lU/zVYIp1e6dEwvxfFMCW/u7pXdy+DRvJQh+ZfjOPQvPOyXYslY8oPOS +%T/NXgm0HC58y0vH8U4KeUCLpesCnHky4t/d4GP8OnodMmLUuzW9mMp96MCGx +%57ZO8D94PjLBOfn60+LLfOrBhJXR622WXsDzkgm/ivcwq8/wqQcTtG1SNkdE +%4fnJhNa7anE2EXzqwYSooazB8YfxPGXCwemZK4ftw/6ACQsjR87QCuVTDyY8 +%VdeuNw7EfoEJSapm1T47+NRDGeIfx7Ym+2L/oAy/eV2yUd2EHspwYkdeQag3 +%eiiDkW6OWGUNeihDjdGXlxdXoocyjH2X3WazHD2U4Y1txr9qzuihDJ8fP3lW +%YY8eyuA/+GHtG2v0UAb192eVn1uihzIE2M43yjZDD2X41M1PagD0YIF63PhC +%7fnowYJXGr7PV8xBDxa8WP5r/7+/oQcL7G61HxUaogcLtoYdbv9jGnqwYPtS +%k4yByejBgojzR8Mi9NGDBcPZA+ZT9NCDBQZeB1IKx6EHCz5/U74YOxo9WLCs +%rt3McyR6sKDu2j3vOZrowYKKPz6+HquBHirQo9PDlIrRQwUCnM9HaaihhwoE +%dwY80BGghwq8rPaYO4+LHiqwt1LNxksFPVQg+6lgZCwTPVRgR88vbz8O8aiH +%ChBPVcexA9i/qkBJlaZbQC+PesjvP9FOyuviUQ8V2FXtc9q0k0c9VODwHKWZ +%D9qw32XDgb73z+e2YH/NBqXogIEXjdj/sqE0EhLc63nUgw2XN6Sf7K/FfpgN +%1rKBwqvV2I+zIS2WMcG1Evtj+Xhz42KGl/OoBxt87w0zK/6K/TIbTB24XddL +%sH9nQ9jhb9Xhn3nUgw3m1o4Fa+T9tsKDDR990q4syedRDzZ0ufZdn5eH/T4H +%1mjV+M3M5VEPDri0n1SZ/YFHPTjwNeBxKrznUQ8OJIZYDjq8xd8HHAi0LXyz +%MZNHPTiw+9n220df86gHB1zvORWnZqAHByygmXx9gR4cyDArDBz2HD04kKSf +%luvwDD04cPNzqUtMOnrI75cHZ355gh5cuKW82Xv6Y/Tgwh33tcUH0tCDC5wl +%3ZnlD9CDC8Yfnn02v48eXJCcDhtMvoseXHC9IRga/T/04ELyQG9M1G0ePT+5 +%ULv5oonqLfTgQlTK/7wjbqIHF/bYjd8iTUEPLixq+scj4QZ6cCEwM9FrWhJ6 +%yOfj98Du+XX04EHXaaWkVdfQgwftgWNeD/6NHjzY57t7deJf6MGDzQWMYuur +%6MEDr4sZ4r5E9OCBVbzm8OTL6MGDML249+v+RA8ehLfpOOlcQg8eHKpLaitL +%QA8e6OU97//rAnrwYHLMQnO/8+jBAzuN2Ffz49GDD51Z043Uz6EHH4pWm9hV +%xqEHH2xcSooen0EPPviOq4qMj0UPPvz++Oyp4Bj04EOL+Pkjz2j04ANv7Mnz +%S06jBx/e39qvNysKPfhwNXFzoP4p9ODDwI2cbdqR6MGH7sTqPZKT6MGHhfXb +%U4Qn0IMPnBmH7vIj0EMAq41/1RMeRw95H+HsrSk6hh7yvm3oYq/mUfQQwOxr +%Goa6R9BDAAvVHhobHEYPAUStdHY0PYQeAvC3t/BadhA95H3Rp7YNWw6ghwDq +%0z1WHNqPHvK+4tIo3Sv70EMAWrmRr/8LRw/550e/elgVhh5CMPUKuCAIQw8h +%KJtHjTHaix5CmB5XHr5yD3oIoWMo6tvRUPSQ/66asD7vQQh6CKH1jmpCQzB6 +%yPve/KHfdYLRQwgjuLVrXf9ADyE070v5cCoIPYTAE1otfxuIHvLXHZJPgkD0 +%EELgUNYU693oIYSWRP+CE7vQQxWKwtR4H3eihyo8d24pH7kTPVTB0WNayNoA +%9FCFsoHWTyk70EMV3EadujOwHT1UITmj09lmO3qowqHbK9IT/NFDFe6daE5s +%24YeqpB9vcHccht6qAIj2/fART/0UAWf6kSVHl/0UIUd43LWO/mihxp4KU27 +%mroVPdSgpj/EUbQVPdRgha5+yLYt6KEGUxP4IR83o4caXPnlatjszeihBluP +%hYYnbEIPNfBxel7F3oQearDD8V2o30b0UAP/htBzn33QQw02xD6KsPRBD/l8 +%0pd8u7sBPdRgVohV44QN6KEGUZ1/x8StRw91UJ+496lgPXqoQ16/m/9eb/RQ +%h1yp6Oz3deihDtlHvlpuXYce6mDIH7GxZi16qIPrScsBz7XooQ6/apV1lHih +%hzqUelUGunmhhzpIh91eWLQGPdRBP6NUe8Ua9JC/P+1V2WdP9FCH56OFD1d5 +%oocIGkbYnilfjfWIwFaTKd6wGn1E4P6x41GTB9YngtEtYck7PdBLBKmGUZVD +%q7BeETzYdsLh6Cr0E4Gnw+bKYauwfvn9xBdNl1eipwgkqcojDVZiHiIw0ro1 +%IJPv44rfMyKwV7pB7N0xHxFsX5yQXOaG3iLomLbXdIcb5iWCvz6Gf+W4Yf8g +%Am1vm8rzKzA/EUQZ5hwwXIHrQQRZDDXH18sxTxEEly177rkc14cI9qyWru11 +%xXxFkGtmuyDaFdeLCFaGul+f7op5i2B+t8+iTBdcPyJghbWKNrhg/iLYrR+s +%z3bB9SSCfQdYrleXoYcY1vXPyzFbhh5iOJq+p6bKGT3EUN68J+uQM3qIQbfY +%Y/RkZ/QQg1bqf5ffOqGHGD7pbyzb5oQeYuAtyPDVdEIPMawqGHPrsSN6iGFj +%+pHCtY7oIYZXupeyhI74fZO/n6/E/99S9BCD2vclh1ctRQ8xGD0ouMVdih5i +%MEzJHrjtgB5iEPeWtXs4oIe83mEuSUIH9BBDQo/6uIf26CGff2ZjyAZ79BDD +%jTDueE179BDD6+E1rS/t0EMMA8t6L++yQw8xtB62ZE6yQw/5eAHLm4ps0UMM +%Q580QyJs0UMMe6b9dgxs0UMCV3b01HXYoIcEbK80ZF+zQQ8JtAUVqnjYoIcE +%fHXemAyzQQ8JnHKZfSvTGj3k9x9ywvZZo4cE1v7x7tbv1ughgUxVu/Z2K/SQ +%wNQPX1JuWKGHBIxkxhnrrdBDAmXujdN/sUIPCSwxWGBesgQ9JHDd5eTss0vQ +%QwLvh5+86rwEPSTAMTh/TLIEPSTwarePT/Zi9JDAv4bxlScWo4cEsk5PLbBZ +%jB4SsOHMSRIuRg8JpD4dpvfWEj0k8LHZ+EiEJXpIYNSdDeNsLdFDAj47Hqap +%W6KHBLT0nmrnLEIPCVSY6dZHL0IPKVwNFX5zWYQeUri7eIqR9iL0kAJX/1dG +%qQV6SOHI5CneVyzQQwqhST1nfSzQQz7eVqn1dAv0kAJ71ZOTneboIYXHN71M +%HpmjhxQcnGM37DNHDymEWNx3tDJHDynMGLHAV2qOHlK4b7x8eLEZekjB7eMx +%vatm6CEFE+ulwVvN0EMKKddI0Gwz9JCC8dncCqYZekjB1W+4yduF6CGFywfW +%jDq7ED2kwGl69mjtQvSQwuDe2rIZC9FDCtWnvEIHTNFD/vwt7/a+MUUPKVjH +%6XLiTNFDCn9kjvmwzhQ9pFD4yiXfyBQ9NGCbumUmU/76/4f9glI= +% "]]}}, +% AspectRatio->NCache[GoldenRatio^(-1), 0.6180339887498948], +% Axes->None, +% AxesOrigin->{0, 0}, +% Epilog->{ +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.02\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.02"]], TraditionalForm]], {0.8, 4}, BaseStyle \ +%-> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.05\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.05"]], TraditionalForm]], {1.67, 6.4}, \ +%BaseStyle -> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.07\"", TraditionalForm]], "Text", "TR"], +% Text["|q| = 0.07"]], TraditionalForm]], {2.85, 12}, \ +%BaseStyle -> 14], +% InsetBox[ +% BoxData[ +% FormBox[ +% InterpretationBox[ +% Cell[ +% BoxData[ +% FormBox["\"|q| = 0.1\"", TraditionalForm]], "Text", "TR"], \ +% +% Text["|q| = 0.1"]], TraditionalForm]], {3.2, 7}, BaseStyle \ +%-> 14], Null}, +% Frame->True, +% FrameLabel->{ +% FormBox["\"Impact parameter, b\"", TraditionalForm], +% FormBox["\"Spatial rotation, \[Chi]\"", TraditionalForm]}, +% FrameStyle->{{14, +% GrayLevel[1]}, {14, +% GrayLevel[1]}}, +% FrameTicks->{Automatic, {{0, +% FormBox["0", TraditionalForm]}, { +% NCache[Pi, 3.141592653589793], +% FormBox["\[Pi]", TraditionalForm]}, { +% NCache[2 Pi, 6.283185307179586], +% FormBox[ +% RowBox[{"2", " ", "\[Pi]"}], TraditionalForm]}, { +% NCache[3 Pi, 9.42477796076938], +% FormBox[ +% RowBox[{"3", " ", "\[Pi]"}], TraditionalForm]}, { +% NCache[4 Pi, 12.566370614359172`], +% FormBox[ +% RowBox[{"4", " ", "\[Pi]"}], TraditionalForm]}}}, +% FrameTicksStyle->{16, 16}, +% ImageSize->600, +% PlotRange->{All, All}, +% PlotRangeClipping->True, +% PlotRangePadding->{Automatic, Automatic}, +% TicksStyle->16]], "Output", +% CellChangeTimes->{3.556953242036603*^9, {3.556953596625984*^9, \ +%3.556953702375863*^9}, {3.556953796337514*^9, 3.556953956593231*^9}, \ +%{3.556954020687426*^9, 3.556954030706046*^9}, { +% 3.558693364370013*^9, 3.5586933829491863`*^9}, \ +%{3.55869345851305*^9, 3.5586934742140837`*^9}}] +%%EndMathematicaCell +p +np 33 1 m +33 273 L +469 273 L +469 1 L +cp +clip np +p +np 35 3 m +35 271 L +467 271 L +467 3 L +cp +clip np +3.239 setmiterlimit +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +0 g +0.36 w +[ ] 0 setdash +3.25 setmiterlimit +78.19 226.892 m +78.919 226.724 L +79.648 226.555 L +80.376 226.386 L +81.105 226.217 L +81.834 226.048 L +82.562 225.878 L +83.291 225.707 L +84.02 225.537 L +84.749 225.366 L +85.477 225.194 L +86.206 225.022 L +86.935 224.85 L +87.663 224.678 L +88.392 224.505 L +89.121 224.331 L +89.849 224.158 L +90.578 223.984 L +91.307 223.809 L +92.035 223.635 L +92.764 223.459 L +93.493 223.284 L +94.221 223.108 L +94.95 222.932 L +95.679 222.755 L +96.407 222.578 L +97.136 222.4 L +97.865 222.222 L +98.593 222.044 L +99.322 221.865 L +100.051 221.686 L +100.779 221.507 L +101.508 221.327 L +102.237 221.147 L +102.965 220.966 L +103.694 220.785 L +104.423 220.604 L +105.152 220.422 L +105.88 220.24 L +106.609 220.057 L +107.338 219.874 L +108.066 219.691 L +108.795 219.507 L +109.524 219.323 L +110.252 219.138 L +110.981 218.953 L +111.71 218.767 L +112.438 218.581 L +113.167 218.395 L +113.896 218.208 L +114.624 218.021 L +115.353 217.833 L +116.082 217.645 L +116.81 217.457 L +117.539 217.268 L +118.268 217.078 L +118.996 216.889 L +119.725 216.698 L +120.454 216.508 L +121.182 216.316 L +121.911 216.125 L +122.64 215.933 L +123.368 215.74 L +124.097 215.547 L +124.826 215.354 L +125.554 215.16 L +126.283 214.965 L +127.012 214.77 L +127.741 214.575 L +128.469 214.379 L +129.198 214.183 L +129.927 213.986 L +130.655 213.789 L +131.384 213.591 L +132.113 213.392 L +132.841 213.194 L +133.57 212.994 L +134.299 212.794 L +135.027 212.594 L +135.756 212.393 L +136.485 212.192 L +137.213 211.99 L +137.942 211.787 L +138.671 211.584 L +139.399 211.38 L +140.128 211.176 L +140.857 210.972 L +141.585 210.766 L +142.314 210.56 L +143.043 210.354 L +143.771 210.147 L +144.5 209.939 L +145.229 209.731 L +145.957 209.522 L +146.686 209.313 L +147.415 209.103 L +148.144 208.893 L +148.872 208.681 L +149.601 208.47 L +150.33 208.257 L +151.058 208.044 L +151.787 207.83 L +152.516 207.616 L +153.244 207.401 L +153.973 207.185 L +154.702 206.969 L +155.43 206.752 L +156.159 206.534 L +156.888 206.316 L +157.616 206.097 L +158.345 205.877 L +159.074 205.656 L +159.802 205.435 L +160.531 205.213 L +161.26 204.99 L +161.988 204.767 L +162.717 204.542 L +163.446 204.317 L +164.174 204.092 L +164.903 203.865 L +165.632 203.638 L +166.36 203.409 L +167.089 203.181 L +167.818 202.951 L +168.547 202.72 L +169.275 202.489 L +170.004 202.256 L +170.733 202.023 L +171.461 201.789 L +172.19 201.554 L +172.919 201.318 L +173.647 201.081 L +174.376 200.844 L +175.105 200.605 L +175.833 200.365 L +176.562 200.125 L +177.291 199.883 L +178.019 199.641 L +178.748 199.397 L +179.477 199.153 L +180.205 198.907 L +180.934 198.661 L +181.663 198.413 L +182.391 198.164 L +183.12 197.914 L +183.849 197.664 L +184.577 197.412 L +185.306 197.158 L +186.035 196.904 L +186.763 196.649 L +187.492 196.392 L +188.221 196.134 L +188.95 195.875 L +189.678 195.615 L +190.407 195.353 L +191.136 195.091 L +191.864 194.826 L +192.593 194.561 L +193.322 194.294 L +194.05 194.026 L +194.779 193.757 L +195.508 193.486 L +196.236 193.214 L +196.965 192.94 L +197.694 192.665 L +198.422 192.388 L +199.151 192.11 L +199.88 191.83 L +200.608 191.549 L +201.337 191.266 L +202.066 190.981 L +202.794 190.695 L +203.523 190.408 L +204.252 190.118 L +204.98 189.827 L +205.709 189.534 L +206.438 189.239 L +207.166 188.942 L +207.895 188.644 L +208.624 188.343 L +209.353 188.041 L +210.081 187.737 L +210.81 187.43 L +211.539 187.122 L +212.267 186.811 L +212.996 186.499 L +213.725 186.184 L +214.453 185.867 L +215.182 185.548 L +215.911 185.226 L +216.639 184.903 L +217.368 184.576 L +218.097 184.248 L +218.825 183.917 L +219.554 183.583 L +220.283 183.247 L +221.011 182.908 L +221.74 182.566 L +222.469 182.221 L +223.197 181.874 L +223.926 181.524 L +224.655 181.171 L +225.383 180.815 L +226.112 180.455 L +226.841 180.093 L +227.569 179.727 L +228.298 179.358 L +229.027 178.986 L +229.756 178.61 L +230.484 178.23 L +231.213 177.847 L +231.942 177.46 L +232.67 177.069 L +233.399 176.674 L +234.128 176.276 L +234.856 175.872 L +235.585 175.465 L +236.314 175.053 L +237.042 174.637 L +237.771 174.216 L +238.5 173.79 L +239.228 173.359 L +239.957 172.924 L +240.686 172.482 L +241.414 172.036 L +242.143 171.584 L +242.872 171.126 L +243.6 170.662 L +244.329 170.192 L +245.058 169.716 L +245.786 169.233 L +246.515 168.744 L +247.244 168.247 L +247.972 167.743 L +248.701 167.232 L +249.43 166.713 L +250.159 166.186 L +250.887 165.65 L +251.616 165.106 L +252.345 164.553 L +253.073 163.99 L +253.802 163.417 L +254.531 162.834 L +255.259 162.241 L +255.988 161.636 L +256.717 161.02 L +257.445 160.391 L +258.174 159.75 L +258.903 159.095 L +259.631 158.426 L +260.36 157.743 L +261.089 157.044 L +261.817 156.328 L +262.546 155.595 L +263.275 154.844 L +264.003 154.074 L +264.732 153.283 L +265.461 152.471 L +266.189 151.635 L +266.918 150.774 L +267.647 149.887 L +268.375 148.972 L +269.104 148.026 L +269.833 147.047 L +270.561 146.033 L +271.29 144.98 L +272.019 143.886 L +272.748 142.746 L +273.476 141.556 L +274.205 140.31 L +274.934 139.004 L +275.662 137.63 L +276.391 136.179 L +277.12 134.642 L +277.848 133.005 L +278.577 131.258 L +279.306 129.378 L +280.034 127.344 L +280.763 125.125 L +281.492 122.683 L +282.22 119.959 L +282.949 116.88 L +283.678 113.326 L +284.406 109.112 L +285.135 103.916 L +285.864 97.083 L +286.592 86.986 L +287.321 66.511 L +288.05 107.215 L +288.778 120.201 L +289.507 125.455 L +290.236 128.764 L +290.964 131.167 L +291.693 133.043 L +292.422 134.577 L +293.151 135.87 L +293.879 136.983 L +294.608 137.958 L +295.337 138.825 L +296.065 139.602 L +296.794 140.306 L +297.523 140.948 L +298.251 141.537 L +298.98 142.081 L +299.709 142.585 L +300.437 143.054 L +301.166 143.493 L +301.895 143.904 L +302.623 144.291 L +303.352 144.655 L +304.081 145 L +304.809 145.327 L +305.538 145.636 L +306.267 145.931 L +306.995 146.212 L +307.724 146.479 L +308.453 146.735 L +309.181 146.98 L +309.91 147.214 L +310.639 147.439 L +311.367 147.655 L +312.096 147.863 L +312.825 148.062 L +313.554 148.255 L +314.282 148.44 L +315.011 148.619 L +315.74 148.792 L +316.468 148.959 L +317.197 149.12 L +317.926 149.276 L +318.654 149.427 L +319.383 149.574 L +320.112 149.716 L +320.84 149.853 L +321.569 149.987 L +322.298 150.117 L +323.026 150.243 L +323.755 150.366 L +324.484 150.485 L +325.212 150.601 L +325.941 150.713 L +326.67 150.823 L +327.398 150.93 L +328.127 151.035 L +328.856 151.136 L +329.584 151.235 L +330.313 151.332 L +331.042 151.426 L +331.77 151.518 L +332.499 151.608 L +333.228 151.696 L +333.957 151.782 L +334.685 151.866 L +335.414 151.948 L +336.143 152.028 L +336.871 152.106 L +337.6 152.183 L +338.329 152.258 L +339.057 152.331 L +339.786 152.403 L +340.515 152.474 L +341.243 152.542 L +341.972 152.61 L +342.701 152.676 L +343.429 152.741 L +344.158 152.805 L +344.887 152.867 L +345.615 152.928 L +346.344 152.988 L +347.073 153.047 L +347.801 153.105 L +348.53 153.161 L +349.259 153.217 L +349.987 153.271 L +350.716 153.325 L +351.445 153.377 L +352.173 153.429 L +352.902 153.48 L +353.631 153.53 L +354.36 153.579 L +355.088 153.627 L +355.817 153.674 L +356.546 153.721 L +357.274 153.766 L +358.003 153.811 L +358.732 153.855 L +359.46 153.899 L +360.189 153.942 L +360.918 153.984 L +361.646 154.025 L +362.375 154.066 L +363.104 154.106 L +363.832 154.145 L +364.561 154.184 L +365.29 154.222 L +366.018 154.26 L +366.747 154.297 L +367.476 154.333 L +368.204 154.369 L +368.933 154.405 L +369.662 154.44 L +370.39 154.474 L +371.119 154.508 L +371.848 154.541 L +372.576 154.574 L +373.305 154.606 L +374.034 154.638 L +374.763 154.669 L +375.491 154.7 L +376.22 154.731 L +376.949 154.761 L +377.677 154.791 L +378.406 154.82 L +379.135 154.849 L +379.863 154.877 L +380.592 154.905 L +381.321 154.933 L +382.049 154.96 L +382.778 154.987 L +383.507 155.014 L +384.235 155.04 L +384.964 155.066 L +385.693 155.092 L +386.421 155.117 L +387.15 155.142 L +387.879 155.166 L +388.607 155.19 L +389.336 155.214 L +390.065 155.238 L +390.793 155.261 L +391.522 155.284 L +392.251 155.307 L +392.979 155.33 L +393.708 155.352 L +394.437 155.374 L +395.166 155.395 L +395.894 155.417 L +396.623 155.438 L +397.352 155.458 L +398.08 155.479 L +398.809 155.499 L +399.538 155.519 L +400.266 155.539 L +400.995 155.559 L +401.724 155.578 L +402.452 155.597 L +403.181 155.616 L +403.91 155.635 L +404.638 155.653 L +405.367 155.672 L +406.096 155.69 L +406.824 155.708 L +407.553 155.725 L +408.282 155.743 L +409.01 155.76 L +409.739 155.777 L +410.468 155.794 L +411.196 155.811 L +411.925 155.827 L +412.654 155.843 L +413.382 155.859 L +414.111 155.875 L +414.84 155.891 L +415.568 155.907 L +416.297 155.922 L +417.026 155.937 L +417.755 155.952 L +418.483 155.967 L +419.212 155.982 L +419.941 155.997 L +420.669 156.011 L +421.398 156.025 L +422.127 156.04 L +422.855 156.054 L +423.584 156.067 L +424.313 156.081 L +425.041 156.095 L +425.77 156.108 L +426.499 156.121 L +427.227 156.135 L +427.956 156.148 L +428.685 156.16 L +429.413 156.173 L +430.142 156.186 L +430.871 156.198 L +431.599 156.211 L +432.328 156.223 L +433.057 156.235 L +433.785 156.247 L +434.514 156.259 L +435.243 156.271 L +435.971 156.282 L +436.7 156.294 L +437.429 156.305 L +438.158 156.316 L +438.886 156.328 L +439.615 156.339 L +440.344 156.35 L +441.072 156.36 L +441.801 156.371 L +442.53 156.382 L +s +78.19 227.244 m +78.919 227.062 L +79.648 226.879 L +80.376 226.696 L +81.105 226.513 L +81.834 226.329 L +82.562 226.144 L +83.291 225.96 L +84.02 225.774 L +84.749 225.589 L +85.477 225.403 L +86.206 225.216 L +86.935 225.029 L +87.663 224.842 L +88.392 224.654 L +89.121 224.465 L +89.849 224.277 L +90.578 224.088 L +91.307 223.898 L +92.035 223.708 L +92.764 223.518 L +93.493 223.327 L +94.221 223.135 L +94.95 222.944 L +95.679 222.751 L +96.407 222.559 L +97.136 222.366 L +97.865 222.172 L +98.593 221.978 L +99.322 221.783 L +100.051 221.589 L +100.779 221.393 L +101.508 221.197 L +102.237 221.001 L +102.965 220.804 L +103.694 220.607 L +104.423 220.409 L +105.152 220.211 L +105.88 220.013 L +106.609 219.813 L +107.338 219.614 L +108.066 219.414 L +108.795 219.213 L +109.524 219.012 L +110.252 218.811 L +110.981 218.609 L +111.71 218.406 L +112.438 218.203 L +113.167 217.999 L +113.896 217.795 L +114.624 217.591 L +115.353 217.386 L +116.082 217.18 L +116.81 216.974 L +117.539 216.767 L +118.268 216.56 L +118.996 216.352 L +119.725 216.144 L +120.454 215.935 L +121.182 215.726 L +121.911 215.516 L +122.64 215.305 L +123.368 215.094 L +124.097 214.883 L +124.826 214.67 L +125.554 214.458 L +126.283 214.244 L +127.012 214.03 L +127.741 213.816 L +128.469 213.6 L +129.198 213.385 L +129.927 213.168 L +130.655 212.951 L +131.384 212.733 L +132.113 212.515 L +132.841 212.296 L +133.57 212.076 L +134.299 211.856 L +135.027 211.635 L +135.756 211.413 L +136.485 211.191 L +137.213 210.968 L +137.942 210.744 L +138.671 210.52 L +139.399 210.294 L +140.128 210.068 L +140.857 209.842 L +141.585 209.614 L +142.314 209.386 L +143.043 209.157 L +143.771 208.927 L +144.5 208.697 L +145.229 208.466 L +145.957 208.234 L +146.686 208.001 L +147.415 207.767 L +148.144 207.532 L +148.872 207.297 L +149.601 207.06 L +150.33 206.823 L +151.058 206.585 L +151.787 206.346 L +152.516 206.106 L +153.244 205.865 L +153.973 205.623 L +154.702 205.381 L +155.43 205.137 L +156.159 204.892 L +156.888 204.646 L +157.616 204.4 L +158.345 204.152 L +159.074 203.903 L +159.802 203.653 L +160.531 203.402 L +161.26 203.15 L +161.988 202.897 L +162.717 202.643 L +163.446 202.387 L +164.174 202.131 L +164.903 201.873 L +165.632 201.614 L +166.36 201.354 L +167.089 201.092 L +167.818 200.83 L +168.547 200.565 L +169.275 200.3 L +170.004 200.033 L +170.733 199.765 L +171.461 199.496 L +172.19 199.225 L +172.919 198.953 L +173.647 198.68 L +174.376 198.405 L +175.105 198.128 L +175.833 197.85 L +176.562 197.57 L +177.291 197.289 L +178.019 197.006 L +178.748 196.722 L +179.477 196.435 L +180.205 196.148 L +180.934 195.858 L +181.663 195.567 L +182.391 195.274 L +183.12 194.979 L +183.849 194.682 L +184.577 194.383 L +185.306 194.083 L +186.035 193.78 L +186.763 193.475 L +187.492 193.169 L +188.221 192.86 L +188.95 192.549 L +189.678 192.236 L +190.407 191.921 L +191.136 191.603 L +191.864 191.284 L +192.593 190.961 L +193.322 190.637 L +194.05 190.31 L +194.779 189.98 L +195.508 189.648 L +196.236 189.313 L +196.965 188.976 L +197.694 188.635 L +198.422 188.292 L +199.151 187.946 L +199.88 187.598 L +200.608 187.246 L +201.337 186.891 L +202.066 186.533 L +202.794 186.171 L +203.523 185.807 L +204.252 185.438 L +204.98 185.067 L +205.709 184.692 L +206.438 184.313 L +207.166 183.93 L +207.895 183.544 L +208.624 183.153 L +209.353 182.759 L +210.081 182.36 L +210.81 181.957 L +211.539 181.549 L +212.267 181.137 L +212.996 180.72 L +213.725 180.299 L +214.453 179.872 L +215.182 179.44 L +215.911 179.003 L +216.639 178.56 L +217.368 178.112 L +218.097 177.657 L +218.825 177.197 L +219.554 176.73 L +220.283 176.257 L +221.011 175.777 L +221.74 175.29 L +222.469 174.796 L +223.197 174.295 L +223.926 173.785 L +224.655 173.268 L +225.383 172.742 L +226.112 172.207 L +226.841 171.662 L +227.569 171.109 L +228.298 170.546 L +229.027 169.971 L +229.756 169.387 L +230.484 168.79 L +231.213 168.182 L +231.942 167.561 L +232.67 166.927 L +233.399 166.28 L +234.128 165.617 L +234.856 164.94 L +235.585 164.246 L +236.314 163.535 L +237.042 162.806 L +237.771 162.058 L +238.5 161.289 L +239.228 160.499 L +239.957 159.686 L +240.686 158.848 L +241.414 157.985 L +242.143 157.093 L +242.872 156.17 L +243.6 155.216 L +244.329 154.226 L +245.058 153.198 L +245.786 152.13 L +246.515 151.016 L +247.244 149.854 L +247.972 148.638 L +248.701 95.265 L +249.43 93.925 L +250.159 92.513 L +250.887 91.021 L +251.616 89.437 L +252.345 87.751 L +253.073 85.948 L +253.802 84.008 L +254.531 81.914 L +255.259 79.635 L +255.988 77.137 L +256.717 74.37 L +257.445 71.271 L +258.174 67.749 L +258.903 63.665 L +259.631 58.799 L +260.36 52.774 L +261.089 44.836 L +261.817 33.16 L +262.546 10.25 L +263.275 102.86 L +264.003 118.171 L +264.732 124.159 L +265.461 127.877 L +266.189 130.553 L +266.918 132.626 L +267.647 134.311 L +268.375 135.722 L +269.104 136.932 L +269.833 137.986 L +270.561 138.919 L +271.29 139.753 L +272.019 140.505 L +272.748 141.189 L +273.476 141.814 L +274.205 142.389 L +274.934 142.921 L +275.662 143.415 L +276.391 143.875 L +277.12 144.305 L +277.848 144.709 L +278.577 145.088 L +279.306 145.446 L +280.034 145.784 L +280.763 146.105 L +281.492 146.409 L +282.22 146.698 L +282.949 146.973 L +283.678 147.236 L +284.406 147.486 L +285.135 147.726 L +285.864 147.955 L +286.592 148.175 L +287.321 148.386 L +288.05 148.589 L +288.778 148.784 L +289.507 148.972 L +290.236 149.152 L +290.964 149.327 L +291.693 149.495 L +292.422 149.657 L +293.151 149.814 L +293.879 149.965 L +294.608 150.112 L +295.337 150.254 L +296.065 150.392 L +296.794 150.525 L +297.523 150.654 L +298.251 150.78 L +298.98 150.901 L +299.709 151.02 L +300.437 151.135 L +301.166 151.246 L +301.895 151.355 L +302.623 151.46 L +303.352 151.563 L +304.081 151.663 L +304.809 151.761 L +305.538 151.856 L +306.267 151.949 L +306.995 152.039 L +307.724 152.127 L +308.453 152.213 L +309.181 152.297 L +309.91 152.379 L +310.639 152.459 L +311.367 152.537 L +312.096 152.613 L +312.825 152.688 L +313.554 152.761 L +314.282 152.832 L +315.011 152.902 L +315.74 152.97 L +316.468 153.037 L +317.197 153.102 L +317.926 153.166 L +318.654 153.228 L +319.383 153.29 L +320.112 153.35 L +320.84 153.409 L +321.569 153.466 L +322.298 153.523 L +323.026 153.578 L +323.755 153.633 L +324.484 153.686 L +325.212 153.738 L +325.941 153.789 L +326.67 153.84 L +327.398 153.889 L +328.127 153.937 L +328.856 153.985 L +329.584 154.032 L +330.313 154.078 L +331.042 154.123 L +331.77 154.167 L +332.499 154.21 L +333.228 154.253 L +333.957 154.295 L +334.685 154.336 L +335.414 154.376 L +336.143 154.416 L +336.871 154.455 L +337.6 154.494 L +338.329 154.532 L +339.057 154.569 L +339.786 154.606 L +340.515 154.642 L +341.243 154.677 L +341.972 154.712 L +342.701 154.746 L +343.429 154.78 L +344.158 154.813 L +344.887 154.846 L +345.615 154.878 L +346.344 154.91 L +347.073 154.941 L +347.801 154.972 L +348.53 155.002 L +349.259 155.032 L +349.987 155.061 L +350.716 155.09 L +351.445 155.118 L +352.173 155.146 L +352.902 155.174 L +353.631 155.201 L +354.36 155.228 L +355.088 155.255 L +355.817 155.281 L +356.546 155.306 L +357.274 155.332 L +358.003 155.357 L +358.732 155.381 L +359.46 155.406 L +360.189 155.43 L +360.918 155.453 L +361.646 155.476 L +362.375 155.499 L +363.104 155.522 L +363.832 155.544 L +364.561 155.567 L +365.29 155.588 L +366.018 155.61 L +366.747 155.631 L +367.476 155.652 L +368.204 155.672 L +368.933 155.693 L +369.662 155.713 L +370.39 155.733 L +371.119 155.752 L +371.848 155.772 L +372.576 155.791 L +373.305 155.81 L +374.034 155.828 L +374.763 155.847 L +375.491 155.865 L +376.22 155.883 L +376.949 155.9 L +377.677 155.918 L +378.406 155.935 L +379.135 155.952 L +379.863 155.969 L +380.592 155.986 L +381.321 156.002 L +382.049 156.018 L +382.778 156.034 L +383.507 156.05 L +384.235 156.066 L +384.964 156.081 L +385.693 156.097 L +386.421 156.112 L +387.15 156.127 L +387.879 156.141 L +388.607 156.156 L +389.336 156.171 L +390.065 156.185 L +390.793 156.199 L +391.522 156.213 L +392.251 156.227 L +392.979 156.24 L +393.708 156.254 L +394.437 156.267 L +395.166 156.28 L +395.894 156.293 L +396.623 156.306 L +397.352 156.319 L +398.08 156.331 L +398.809 156.344 L +399.538 156.356 L +400.266 156.368 L +400.995 156.38 L +401.724 156.392 L +402.452 156.404 L +403.181 156.416 L +403.91 156.427 L +404.638 156.439 L +405.367 156.45 L +406.096 156.461 L +406.824 156.472 L +407.553 156.483 L +408.282 156.494 L +409.01 156.505 L +409.739 156.515 L +410.468 156.526 L +411.196 156.536 L +411.925 156.546 L +412.654 156.557 L +413.382 156.567 L +414.111 156.577 L +414.84 156.587 L +415.568 156.596 L +416.297 156.606 L +417.026 156.616 L +417.755 156.625 L +418.483 156.634 L +419.212 156.644 L +419.941 156.653 L +420.669 156.662 L +421.398 156.671 L +422.127 156.68 L +422.855 156.689 L +423.584 156.697 L +424.313 156.706 L +425.041 156.715 L +425.77 156.723 L +426.499 156.732 L +427.227 156.74 L +427.956 156.748 L +428.685 156.756 L +429.413 156.764 L +430.142 156.772 L +430.871 156.78 L +431.599 156.788 L +432.328 156.796 L +433.057 156.804 L +433.785 156.811 L +434.514 156.819 L +435.243 156.826 L +435.971 156.834 L +436.7 156.841 L +437.429 156.849 L +438.158 156.856 L +438.886 156.863 L +439.615 156.87 L +440.344 156.877 L +441.072 156.884 L +441.801 156.891 L +442.53 156.898 L +s +78.19 227.796 m +78.555 227.699 L +78.919 227.602 L +79.283 227.504 L +79.648 227.407 L +80.012 227.309 L +80.376 227.211 L +80.741 227.113 L +81.105 227.016 L +81.469 226.917 L +81.834 226.819 L +82.198 226.721 L +82.562 226.622 L +82.927 226.524 L +83.291 226.425 L +83.655 226.327 L +84.02 226.228 L +84.384 226.129 L +84.749 226.03 L +85.113 225.93 L +85.477 225.831 L +85.842 225.732 L +86.206 225.632 L +86.57 225.532 L +86.935 225.433 L +87.299 225.333 L +87.663 225.233 L +88.028 225.133 L +88.392 225.032 L +88.756 224.932 L +89.121 224.832 L +89.485 224.731 L +89.849 224.63 L +90.214 224.53 L +90.578 224.429 L +90.942 224.328 L +91.307 224.227 L +91.671 224.125 L +92.035 224.024 L +92.4 223.923 L +92.764 223.821 L +93.128 223.719 L +93.493 223.618 L +93.857 223.516 L +94.221 223.414 L +94.586 223.311 L +94.95 223.209 L +95.314 223.107 L +95.679 223.004 L +96.043 222.902 L +96.407 222.799 L +96.772 222.696 L +97.136 222.593 L +97.5 222.49 L +97.865 222.387 L +98.229 222.284 L +98.593 222.18 L +98.958 222.077 L +99.322 221.973 L +99.686 221.869 L +100.051 221.765 L +100.415 221.661 L +100.779 221.557 L +101.144 221.453 L +101.508 221.349 L +101.872 221.244 L +102.237 221.14 L +102.601 221.035 L +102.965 220.93 L +103.33 220.825 L +103.694 220.72 L +104.058 220.615 L +104.423 220.51 L +104.787 220.404 L +105.152 220.299 L +105.516 220.193 L +105.88 220.087 L +106.245 219.981 L +106.609 219.875 L +106.973 219.769 L +107.338 219.663 L +107.702 219.556 L +108.066 219.45 L +108.431 219.343 L +108.795 219.236 L +109.159 219.129 L +109.524 219.022 L +109.888 218.915 L +110.252 218.808 L +110.617 218.7 L +110.981 218.593 L +111.345 218.485 L +111.71 218.377 L +112.074 218.269 L +112.438 218.161 L +112.803 218.053 L +113.167 217.945 L +113.531 217.836 L +113.896 217.727 L +114.26 217.619 L +114.624 217.51 L +114.989 217.401 L +115.353 217.291 L +115.717 217.182 L +116.082 217.073 L +116.446 216.963 L +116.81 216.853 L +117.175 216.743 L +117.539 216.633 L +117.903 216.523 L +118.268 216.413 L +118.632 216.303 L +118.996 216.192 L +119.361 216.081 L +119.725 215.97 L +120.089 215.859 L +120.454 215.748 L +120.818 215.637 L +121.182 215.525 L +121.547 215.414 L +121.911 215.302 L +122.275 215.19 L +122.64 215.078 L +123.004 214.965 L +123.368 214.853 L +123.733 214.74 L +124.097 214.628 L +124.461 214.515 L +124.826 214.402 L +125.19 214.288 L +125.554 214.175 L +125.919 214.061 L +126.283 213.948 L +126.648 213.834 L +127.012 213.72 L +127.376 213.605 L +127.741 213.491 L +128.105 213.376 L +128.469 213.262 L +128.834 213.147 L +129.198 213.032 L +129.562 212.916 L +129.927 212.801 L +130.291 212.685 L +130.655 212.57 L +131.02 212.454 L +131.384 212.337 L +131.748 212.221 L +132.113 212.105 L +132.477 211.988 L +132.841 211.871 L +133.206 211.754 L +133.57 211.636 L +133.934 211.519 L +134.299 211.401 L +134.663 211.283 L +135.027 211.165 L +135.392 211.047 L +135.756 210.928 L +136.12 210.81 L +136.485 210.691 L +136.849 210.572 L +137.213 210.452 L +137.578 210.332 L +137.942 210.213 L +138.306 210.093 L +138.671 209.973 L +139.035 209.852 L +139.399 209.732 L +139.764 209.611 L +140.128 209.49 L +140.492 209.368 L +140.857 209.247 L +141.221 209.125 L +141.585 209.003 L +141.95 208.881 L +142.314 208.758 L +142.678 208.635 L +143.043 208.513 L +143.407 208.389 L +143.771 208.266 L +144.136 208.142 L +144.5 208.018 L +144.864 207.894 L +145.229 207.77 L +145.593 207.645 L +145.957 207.52 L +146.322 207.395 L +146.686 207.269 L +147.051 207.143 L +147.415 207.017 L +147.779 206.891 L +148.144 206.764 L +148.508 206.637 L +148.872 206.51 L +149.237 206.383 L +149.601 206.255 L +149.965 206.127 L +150.33 205.999 L +150.694 205.87 L +151.058 205.741 L +151.423 205.612 L +151.787 205.483 L +152.151 205.353 L +152.516 205.223 L +152.88 205.092 L +153.244 204.962 L +153.609 204.831 L +153.973 204.699 L +154.337 204.567 L +154.702 204.435 L +155.066 204.303 L +155.43 204.17 L +155.795 204.037 L +156.159 203.904 L +156.523 203.77 L +156.888 203.636 L +157.252 203.502 L +157.616 203.367 L +157.981 203.232 L +158.345 203.096 L +158.709 202.961 L +159.074 202.824 L +159.438 202.688 L +159.802 202.551 L +160.167 202.414 L +160.531 202.276 L +160.895 202.138 L +161.26 201.999 L +161.624 201.86 L +161.988 201.721 L +162.353 201.581 L +162.717 201.441 L +163.081 201.301 L +163.446 201.16 L +163.81 201.018 L +164.174 200.877 L +164.539 200.735 L +164.903 200.592 L +165.267 200.449 L +165.632 200.305 L +165.996 200.161 L +166.36 200.017 L +166.725 199.872 L +167.089 199.727 L +167.454 199.581 L +167.818 199.435 L +168.182 199.288 L +168.547 199.141 L +168.911 198.993 L +169.275 198.845 L +169.64 198.696 L +170.004 198.547 L +170.368 198.397 L +170.733 198.247 L +171.097 198.097 L +171.461 197.945 L +171.826 197.793 L +172.19 197.641 L +172.554 197.488 L +172.919 197.335 L +173.283 197.181 L +173.647 197.026 L +174.012 196.871 L +174.376 196.716 L +174.74 196.559 L +175.105 196.403 L +175.469 196.245 L +175.833 196.087 L +176.198 195.929 L +176.562 195.77 L +176.926 195.61 L +177.291 195.449 L +177.655 195.288 L +178.019 195.126 L +178.384 194.964 L +178.748 194.801 L +179.112 194.637 L +179.477 194.473 L +179.841 194.308 L +180.205 194.142 L +180.57 193.976 L +180.934 193.809 L +181.298 193.641 L +181.663 193.472 L +182.027 193.304 L +182.391 193.134 L +182.756 192.963 L +183.12 192.791 L +183.484 192.62 L +183.849 192.447 L +184.213 192.273 L +184.577 192.098 L +184.942 191.923 L +185.306 191.747 L +185.67 191.57 L +186.035 191.392 L +186.399 191.214 L +186.763 191.035 L +187.128 190.854 L +187.492 190.673 L +187.856 190.491 L +188.221 190.309 L +188.585 190.125 L +188.95 189.94 L +189.314 189.755 L +189.678 189.568 L +190.043 189.381 L +190.407 189.192 L +190.771 189.003 L +191.136 188.813 L +191.5 188.622 L +191.864 188.429 L +192.229 188.236 L +192.593 188.042 L +192.957 187.847 L +193.322 187.65 L +193.686 187.453 L +194.05 187.254 L +194.415 187.054 L +194.779 186.854 L +195.143 186.652 L +195.508 186.449 L +195.872 186.244 L +196.236 186.039 L +196.601 185.832 L +196.965 185.625 L +197.329 185.415 L +197.694 185.205 L +198.058 184.994 L +198.422 184.781 L +198.787 184.567 L +199.151 184.351 L +199.515 184.134 L +199.88 131.817 L +200.244 131.597 L +200.608 131.376 L +200.973 131.153 L +201.337 130.93 L +201.701 130.704 L +202.066 130.477 L +202.43 130.249 L +202.794 130.019 L +203.159 129.787 L +203.523 129.554 L +203.887 129.318 L +204.252 129.082 L +204.616 128.844 L +204.98 128.604 L +205.345 128.362 L +205.709 128.118 L +206.073 127.873 L +206.438 127.625 L +206.802 127.376 L +207.166 127.125 L +207.531 126.872 L +207.895 126.616 L +208.259 126.358 L +208.624 126.099 L +208.988 125.837 L +209.353 125.573 L +209.717 125.307 L +210.081 125.038 L +210.446 124.767 L +210.81 124.494 L +211.174 124.217 L +211.539 123.939 L +211.903 123.657 L +212.267 123.373 L +212.632 123.086 L +212.996 122.797 L +213.36 122.504 L +213.725 122.208 L +214.089 121.91 L +214.453 121.608 L +214.818 121.302 L +215.182 120.994 L +215.546 120.681 L +215.911 120.366 L +216.275 120.046 L +216.639 119.723 L +217.004 119.395 L +217.368 119.064 L +217.732 118.728 L +218.097 118.389 L +218.461 118.044 L +218.825 117.695 L +219.19 117.34 L +219.554 116.982 L +219.918 116.617 L +220.283 116.248 L +220.647 115.872 L +221.011 115.491 L +221.376 115.103 L +221.74 114.71 L +222.104 114.31 L +222.469 113.903 L +222.833 113.488 L +223.197 113.067 L +223.562 112.637 L +223.926 112.199 L +224.29 111.753 L +224.655 111.297 L +225.019 110.833 L +225.383 110.358 L +225.748 109.874 L +226.112 109.378 L +226.476 108.871 L +226.841 108.353 L +227.205 107.821 L +227.569 107.276 L +227.934 106.717 L +228.298 106.144 L +228.662 105.556 L +229.027 104.951 L +229.391 104.329 L +229.756 103.689 L +230.12 103.031 L +230.484 102.352 L +230.849 101.653 L +231.213 100.932 L +231.577 100.19 L +231.942 99.426 L +232.306 98.638 L +232.67 97.827 L +233.035 96.995 L +233.399 96.143 L +233.763 95.275 L +234.128 94.395 L +234.492 93.515 L +234.856 92.647 L +235.221 91.816 L +235.585 91.055 L +235.949 90.419 L +236.314 89.987 L +236.678 89.872 L +237.042 90.228 L +237.407 91.227 L +237.771 93.008 L +238.135 95.595 L +238.5 98.813 L +238.864 102.357 L +239.228 105.928 L +239.593 109.305 L +239.957 112.388 L +240.321 115.15 L +240.686 117.611 L +241.05 119.793 L +241.414 121.734 L +241.779 123.473 L +242.143 125.035 L +242.507 126.442 L +242.872 127.722 L +243.236 128.889 L +243.6 129.959 L +243.965 130.941 L +244.329 131.852 L +244.693 132.693 L +245.058 133.477 L +245.422 134.21 L +245.786 134.895 L +246.151 135.537 L +246.515 136.14 L +246.879 136.711 L +247.244 137.249 L +247.608 137.759 L +247.972 138.243 L +248.337 138.702 L +248.701 139.14 L +249.065 139.557 L +249.43 139.955 L +249.794 140.335 L +250.159 140.699 L +250.523 141.048 L +250.887 141.382 L +251.252 141.704 L +251.616 142.013 L +251.98 142.31 L +252.345 142.596 L +252.709 142.871 L +253.073 143.138 L +253.438 143.394 L +253.802 143.643 L +254.166 143.882 L +254.531 144.115 L +254.895 144.339 L +255.259 144.557 L +255.624 144.768 L +255.988 144.972 L +256.352 145.171 L +256.717 145.364 L +257.081 145.551 L +257.445 145.733 L +257.81 145.91 L +258.174 146.082 L +258.538 146.25 L +258.903 146.413 L +259.267 146.572 L +259.631 146.727 L +259.996 146.878 L +260.36 147.025 L +260.724 147.169 L +261.089 147.309 L +261.453 147.446 L +261.817 147.579 L +262.182 147.71 L +262.546 147.837 L +262.91 147.962 L +263.275 148.084 L +263.639 148.203 L +264.003 148.32 L +264.368 148.434 L +264.732 148.546 L +265.096 148.655 L +265.461 148.763 L +265.825 148.868 L +266.189 148.97 L +266.554 149.071 L +266.918 149.17 L +267.282 149.267 L +267.647 149.362 L +268.011 149.455 L +268.375 149.547 L +268.74 149.636 L +269.104 149.724 L +269.468 149.811 L +269.833 149.896 L +270.197 149.979 L +270.561 150.061 L +270.926 150.141 L +271.29 150.22 L +271.655 150.298 L +272.019 150.374 L +272.383 150.449 L +272.748 150.523 L +273.112 150.595 L +273.476 150.667 L +273.841 150.737 L +274.205 150.806 L +274.569 150.874 L +274.934 150.941 L +275.298 151.006 L +275.662 151.071 L +276.027 151.135 L +276.391 151.198 L +276.755 151.259 L +277.12 151.32 L +277.484 151.38 L +277.848 151.439 L +278.213 151.497 L +278.577 151.554 L +278.941 151.611 L +279.306 151.666 L +279.67 151.721 L +280.034 151.775 L +280.399 151.828 L +280.763 151.881 L +281.127 151.932 L +281.492 151.983 L +281.856 152.034 L +282.22 152.083 L +282.585 152.132 L +282.949 152.181 L +283.313 152.228 L +283.678 152.275 L +284.042 152.321 L +284.406 152.367 L +284.771 152.412 L +285.135 152.457 L +285.499 152.501 L +285.864 152.544 L +286.228 152.587 L +286.592 152.63 L +286.957 152.671 L +287.321 152.713 L +287.685 152.753 L +288.05 152.794 L +288.414 152.833 L +288.778 152.872 L +289.143 152.911 L +289.507 152.949 L +289.871 152.987 L +290.236 153.025 L +290.6 153.062 L +290.964 153.098 L +291.329 153.134 L +291.693 153.17 L +292.058 153.205 L +292.422 153.24 L +292.786 153.274 L +293.151 153.308 L +293.515 153.342 L +293.879 153.375 L +294.244 153.408 L +294.608 153.44 L +294.972 153.473 L +295.337 153.504 L +295.701 153.536 L +296.065 153.567 L +296.43 153.597 L +296.794 153.628 L +297.158 153.658 L +297.523 153.688 L +297.887 153.717 L +298.251 153.746 L +298.616 153.775 L +298.98 153.803 L +299.344 153.832 L +299.709 153.86 L +300.073 153.887 L +300.437 153.914 L +300.802 153.941 L +301.166 153.968 L +301.53 153.995 L +301.895 154.021 L +302.259 154.047 L +302.623 154.072 L +302.988 154.098 L +303.352 154.123 L +303.716 154.148 L +304.081 154.173 L +304.445 154.197 L +304.809 154.221 L +305.174 154.245 L +305.538 154.269 L +305.902 154.292 L +306.267 154.315 L +306.631 154.338 L +306.995 154.361 L +307.36 154.384 L +307.724 154.406 L +308.088 154.428 L +308.453 154.45 L +308.817 154.472 L +309.181 154.493 L +309.546 154.515 L +309.91 154.536 L +310.274 154.557 L +310.639 154.578 L +311.003 154.598 L +311.367 154.618 L +311.732 154.639 L +312.096 154.659 L +312.461 154.678 L +312.825 154.698 L +313.189 154.717 L +313.554 154.737 L +313.918 154.756 L +314.282 154.775 L +314.647 154.793 L +315.011 154.812 L +315.375 154.83 L +315.74 154.849 L +316.104 154.867 L +316.468 154.885 L +316.833 154.902 L +317.197 154.92 L +317.561 154.938 L +317.926 154.955 L +318.29 154.972 L +318.654 154.989 L +319.019 155.006 L +319.383 155.023 L +319.747 155.039 L +320.112 155.056 L +320.476 155.072 L +320.84 155.088 L +321.205 155.104 L +321.569 155.12 L +321.933 155.136 L +322.298 155.152 L +322.662 155.167 L +323.026 155.183 L +323.391 155.198 L +323.755 155.213 L +324.119 155.228 L +324.484 155.243 L +324.848 155.258 L +325.212 155.272 L +325.577 155.287 L +325.941 155.301 L +326.305 155.316 L +326.67 155.33 L +327.034 155.344 L +327.398 155.358 L +327.763 155.372 L +328.127 155.386 L +328.491 155.399 L +328.856 155.413 L +329.22 155.426 L +329.584 155.439 L +329.949 155.453 L +330.313 155.466 L +330.677 155.479 L +331.042 155.492 L +331.406 155.505 L +331.77 155.517 L +332.135 155.53 L +332.499 155.542 L +332.864 155.555 L +333.228 155.567 L +333.592 155.579 L +333.957 155.592 L +334.321 155.604 L +334.685 155.616 L +335.05 155.628 L +335.414 155.639 L +335.778 155.651 L +336.143 155.663 L +336.507 155.674 L +336.871 155.686 L +337.236 155.697 L +337.6 155.708 L +337.964 155.719 L +338.329 155.731 L +338.693 155.742 L +339.057 155.753 L +339.422 155.763 L +339.786 155.774 L +340.15 155.785 L +340.515 155.796 L +340.879 155.806 L +341.243 155.817 L +341.608 155.827 L +341.972 155.838 L +342.336 155.848 L +342.701 155.858 L +343.065 155.868 L +343.429 155.878 L +343.794 155.888 L +344.158 155.898 L +344.522 155.908 L +344.887 155.918 L +345.251 155.927 L +345.615 155.937 L +345.98 155.947 L +346.344 155.956 L +346.708 155.966 L +347.073 155.975 L +347.437 155.984 L +347.801 155.994 L +348.166 156.003 L +348.53 156.012 L +348.894 156.021 L +349.259 156.03 L +349.623 156.039 L +349.987 156.048 L +350.352 156.057 L +350.716 156.065 L +351.08 156.074 L +351.445 156.083 L +351.809 156.091 L +352.173 156.1 L +352.538 156.108 L +352.902 156.117 L +353.266 156.125 L +353.631 156.133 L +353.995 156.142 L +354.36 156.15 L +354.724 156.158 L +355.088 156.166 L +355.453 156.174 L +355.817 156.182 L +356.181 156.19 L +356.546 156.198 L +356.91 156.206 L +357.274 156.214 L +357.639 156.221 L +358.003 156.229 L +358.367 156.237 L +358.732 156.244 L +359.096 156.252 L +359.46 156.259 L +359.825 156.267 L +360.189 156.274 L +360.553 156.282 L +360.918 156.289 L +361.282 156.296 L +361.646 156.303 L +362.011 156.311 L +362.375 156.318 L +362.739 156.325 L +363.104 156.332 L +363.468 156.339 L +363.832 156.346 L +364.197 156.353 L +364.561 156.36 L +364.925 156.366 L +365.29 156.373 L +365.654 156.38 L +366.018 156.387 L +366.383 156.393 L +366.747 156.4 L +367.111 156.407 L +367.476 156.413 L +367.84 156.42 L +368.204 156.426 L +368.569 156.433 L +368.933 156.439 L +369.297 156.445 L +369.662 156.452 L +370.026 156.458 L +370.39 156.464 L +370.755 156.47 L +371.119 156.476 L +371.483 156.483 L +371.848 156.489 L +372.212 156.495 L +372.576 156.501 L +372.941 156.507 L +373.305 156.513 L +373.669 156.519 L +374.034 156.524 L +374.398 156.53 L +374.763 156.536 L +375.127 156.542 L +375.491 156.548 L +375.856 156.553 L +376.22 156.559 L +376.584 156.565 L +376.949 156.57 L +377.313 156.576 L +377.677 156.581 L +378.042 156.587 L +378.406 156.592 L +378.77 156.598 L +379.135 156.603 L +379.499 156.609 L +379.863 156.614 L +380.228 156.619 L +380.592 156.625 L +380.956 156.63 L +381.321 156.635 L +381.685 156.64 L +382.049 156.646 L +382.414 156.651 L +382.778 156.656 L +383.142 156.661 L +383.507 156.666 L +383.871 156.671 L +384.235 156.676 L +384.6 156.681 L +384.964 156.686 L +385.328 156.691 L +385.693 156.696 L +386.057 156.701 L +386.421 156.706 L +386.786 156.71 L +387.15 156.715 L +387.514 156.72 L +387.879 156.725 L +388.243 156.729 L +388.607 156.734 L +388.972 156.739 L +389.336 156.743 L +389.7 156.748 L +390.065 156.753 L +390.429 156.757 L +390.793 156.762 L +391.158 156.766 L +391.522 156.771 L +391.886 156.775 L +392.251 156.78 L +392.615 156.784 L +392.979 156.788 L +393.344 156.793 L +393.708 156.797 L +394.072 156.802 L +394.437 156.806 L +394.801 156.81 L +395.166 156.814 L +395.53 156.819 L +395.894 156.823 L +396.259 156.827 L +396.623 156.831 L +396.987 156.835 L +397.352 156.84 L +397.716 156.844 L +398.08 156.848 L +398.445 156.852 L +398.809 156.856 L +399.173 156.86 L +399.538 156.864 L +399.902 156.868 L +400.266 156.872 L +400.631 156.876 L +400.995 156.88 L +401.359 156.884 L +401.724 156.887 L +402.088 156.891 L +402.452 156.895 L +402.817 156.899 L +403.181 156.903 L +403.545 156.907 L +403.91 156.91 L +404.274 156.914 L +404.638 156.918 L +405.003 156.921 L +405.367 156.925 L +405.731 156.929 L +406.096 156.933 L +406.46 156.936 L +406.824 156.94 L +407.189 156.943 L +407.553 156.947 L +407.917 156.95 L +408.282 156.954 L +408.646 156.958 L +409.01 156.961 L +409.375 156.965 L +409.739 156.968 L +410.103 156.972 L +410.468 156.975 L +410.832 156.978 L +411.196 156.982 L +411.561 156.985 L +411.925 156.989 L +412.289 156.992 L +412.654 156.995 L +413.018 156.999 L +413.382 157.002 L +413.747 157.005 L +414.111 157.008 L +414.475 157.012 L +414.84 157.015 L +415.204 157.018 L +415.568 157.021 L +415.933 157.025 L +416.297 157.028 L +416.662 157.031 L +417.026 157.034 L +417.39 157.037 L +417.755 157.04 L +418.119 157.043 L +418.483 157.047 L +418.848 157.05 L +419.212 157.053 L +419.576 157.056 L +419.941 157.059 L +420.305 157.062 L +420.669 157.065 L +421.034 157.068 L +421.398 157.071 L +421.762 157.074 L +422.127 157.077 L +422.491 157.08 L +422.855 157.083 L +423.22 157.085 L +423.584 157.088 L +423.948 157.091 L +424.313 157.094 L +424.677 157.097 L +425.041 157.1 L +425.406 157.103 L +425.77 157.105 L +426.134 157.108 L +426.499 157.111 L +426.863 157.114 L +427.227 157.117 L +427.592 157.119 L +427.956 157.122 L +428.32 157.125 L +428.685 157.127 L +429.049 157.13 L +429.413 157.133 L +429.778 157.136 L +430.142 157.138 L +430.506 157.141 L +430.871 157.143 L +431.235 157.146 L +431.599 157.149 L +431.964 157.151 L +432.328 157.154 L +432.692 157.156 L +433.057 157.159 L +433.421 157.162 L +433.785 157.164 L +434.15 157.167 L +434.514 157.169 L +434.878 157.172 L +435.243 157.174 L +435.607 157.177 L +435.971 157.179 L +436.336 157.182 L +436.7 157.184 L +437.065 157.187 L +437.429 157.189 L +437.793 157.191 L +438.158 157.194 L +438.522 157.196 L +438.886 157.199 L +439.251 157.201 L +439.615 157.203 L +439.979 157.206 L +440.344 157.208 L +440.708 157.21 L +441.072 157.213 L +441.437 157.215 L +441.801 157.217 L +442.165 157.22 L +442.53 157.222 L +s +78.19 230.21 m +78.919 229.983 L +79.648 229.756 L +80.376 229.529 L +81.105 229.302 L +81.834 229.074 L +82.562 228.847 L +83.291 228.619 L +84.02 228.391 L +84.749 228.163 L +85.477 227.935 L +86.206 227.707 L +86.935 227.479 L +87.663 227.251 L +88.392 227.023 L +89.121 226.795 L +89.849 226.567 L +90.578 226.339 L +91.307 226.111 L +92.035 225.884 L +92.764 225.656 L +93.493 225.429 L +94.221 225.201 L +94.95 224.974 L +95.679 224.747 L +96.407 224.52 L +97.136 224.294 L +97.865 224.067 L +98.593 223.841 L +99.322 223.615 L +100.051 223.39 L +100.779 223.164 L +101.508 222.939 L +102.237 222.714 L +102.965 222.49 L +103.694 222.266 L +104.423 222.042 L +105.152 221.819 L +105.88 221.596 L +106.609 221.373 L +107.338 221.151 L +108.066 220.929 L +108.795 220.707 L +109.524 220.486 L +110.252 220.265 L +110.981 220.045 L +111.71 219.825 L +112.438 219.606 L +113.167 219.387 L +113.896 219.169 L +114.624 218.951 L +115.353 218.733 L +116.082 218.517 L +116.81 218.3 L +117.539 218.085 L +118.268 217.869 L +118.996 217.655 L +119.725 217.44 L +120.454 217.227 L +121.182 217.014 L +121.911 216.801 L +122.64 216.59 L +123.368 216.378 L +124.097 216.168 L +124.826 215.958 L +125.554 215.748 L +126.283 215.539 L +127.012 163.232 L +127.741 163.025 L +128.469 162.818 L +129.198 162.612 L +129.927 162.406 L +130.655 162.201 L +131.384 161.997 L +132.113 161.794 L +132.841 161.591 L +133.57 161.39 L +134.299 161.188 L +135.027 160.988 L +135.756 160.789 L +136.485 160.59 L +137.213 160.392 L +137.942 160.194 L +138.671 159.998 L +139.399 159.803 L +140.128 159.608 L +140.857 159.414 L +141.585 159.221 L +142.314 159.029 L +143.043 158.839 L +143.771 158.648 L +144.5 158.459 L +145.229 158.271 L +145.957 158.084 L +146.686 157.898 L +147.415 157.714 L +148.144 157.53 L +148.872 157.347 L +149.601 157.166 L +150.33 156.985 L +151.058 156.806 L +151.787 156.628 L +152.516 156.452 L +153.244 156.277 L +153.973 156.103 L +154.702 155.931 L +155.43 155.761 L +156.159 155.591 L +156.888 155.424 L +157.616 155.258 L +158.345 155.094 L +159.074 154.931 L +159.802 154.771 L +160.531 154.612 L +161.26 154.455 L +161.988 154.3 L +162.717 154.148 L +163.446 153.997 L +164.174 153.849 L +164.903 153.703 L +165.632 153.56 L +166.36 153.419 L +167.089 153.28 L +167.818 153.145 L +168.547 153.012 L +169.275 152.882 L +170.004 152.754 L +170.733 152.631 L +171.461 152.51 L +172.19 152.392 L +172.919 152.278 L +173.647 152.167 L +174.376 152.06 L +175.105 151.956 L +175.833 151.856 L +176.562 151.76 L +177.291 151.668 L +178.019 151.58 L +178.748 151.497 L +179.477 151.417 L +180.205 151.342 L +180.934 151.271 L +181.663 151.205 L +182.391 151.143 L +183.12 151.086 L +183.849 151.033 L +184.577 150.985 L +185.306 150.942 L +186.035 150.904 L +186.763 150.87 L +187.492 150.841 L +188.221 150.817 L +188.95 150.797 L +189.678 150.783 L +190.407 150.773 L +191.136 150.767 L +191.864 150.766 L +192.593 150.77 L +193.322 150.778 L +194.05 150.79 L +194.779 150.806 L +195.508 150.827 L +196.236 150.851 L +196.965 150.88 L +197.694 150.911 L +198.422 150.947 L +199.151 150.986 L +199.88 151.028 L +200.608 151.073 L +201.337 151.121 L +202.066 151.172 L +202.794 151.225 L +203.523 151.281 L +204.252 151.339 L +204.98 151.398 L +205.709 151.46 L +206.438 151.524 L +207.166 151.589 L +207.895 151.656 L +208.624 151.723 L +209.353 151.792 L +210.081 151.862 L +210.81 151.933 L +211.539 152.005 L +212.267 152.077 L +212.996 152.149 L +213.725 152.222 L +214.453 152.295 L +215.182 152.368 L +215.911 152.441 L +216.639 152.515 L +217.368 152.588 L +218.097 152.661 L +218.825 152.733 L +219.554 152.806 L +220.283 152.877 L +221.011 152.949 L +221.74 153.019 L +222.469 153.09 L +223.197 153.159 L +223.926 153.228 L +224.655 153.296 L +225.383 153.364 L +226.112 153.431 L +226.841 153.497 L +227.569 153.562 L +228.298 153.626 L +229.027 153.69 L +229.756 153.753 L +230.484 153.815 L +231.213 153.876 L +231.942 153.936 L +232.67 153.995 L +233.399 154.053 L +234.128 154.111 L +234.856 154.167 L +235.585 154.223 L +236.314 154.278 L +237.042 154.332 L +237.771 154.385 L +238.5 154.437 L +239.228 154.488 L +239.957 154.539 L +240.686 154.589 L +241.414 154.637 L +242.143 154.685 L +242.872 154.733 L +243.6 154.779 L +244.329 154.824 L +245.058 154.869 L +245.786 154.913 L +246.515 154.957 L +247.244 154.999 L +247.972 155.041 L +248.701 155.082 L +249.43 155.122 L +250.159 155.162 L +250.887 155.201 L +251.616 155.239 L +252.345 155.276 L +253.073 155.313 L +253.802 155.35 L +254.531 155.385 L +255.259 155.42 L +255.988 155.455 L +256.717 155.488 L +257.445 155.521 L +258.174 155.554 L +258.903 155.586 L +259.631 155.618 L +260.36 155.648 L +261.089 155.679 L +261.817 155.709 L +262.546 155.738 L +263.275 155.767 L +264.003 155.795 L +264.732 155.823 L +265.461 155.85 L +266.189 155.877 L +266.918 155.904 L +267.647 155.93 L +268.375 155.955 L +269.104 155.98 L +269.833 156.005 L +270.561 156.029 L +271.29 156.053 L +272.019 156.077 L +272.748 156.1 L +273.476 156.122 L +274.205 156.145 L +274.934 156.167 L +275.662 156.188 L +276.391 156.209 L +277.12 156.23 L +277.848 156.251 L +278.577 156.271 L +279.306 156.291 L +280.034 156.31 L +280.763 156.33 L +281.492 156.349 L +282.22 156.367 L +282.949 156.385 L +283.678 156.404 L +284.406 156.421 L +285.135 156.439 L +285.864 156.456 L +286.592 156.473 L +287.321 156.49 L +288.05 156.506 L +288.778 156.522 L +289.507 156.538 L +290.236 156.554 L +290.964 156.569 L +291.693 156.584 L +292.422 156.599 L +293.151 156.614 L +293.879 156.628 L +294.608 156.643 L +295.337 156.657 L +296.065 156.671 L +296.794 156.684 L +297.523 156.698 L +298.251 156.711 L +298.98 156.724 L +299.709 156.737 L +300.437 156.75 L +301.166 156.762 L +301.895 156.774 L +302.623 156.787 L +303.352 156.799 L +304.081 156.81 L +304.809 156.822 L +305.538 156.833 L +306.267 156.845 L +306.995 156.856 L +307.724 156.867 L +308.453 156.878 L +309.181 156.888 L +309.91 156.899 L +310.639 156.909 L +311.367 156.92 L +312.096 156.93 L +312.825 156.94 L +313.554 156.949 L +314.282 156.959 L +315.011 156.969 L +315.74 156.978 L +316.468 156.988 L +317.197 156.997 L +317.926 157.006 L +318.654 157.015 L +319.383 157.024 L +320.112 157.032 L +320.84 157.041 L +321.569 157.049 L +322.298 157.058 L +323.026 157.066 L +323.755 157.074 L +324.484 157.082 L +325.212 157.09 L +325.941 157.098 L +326.67 157.106 L +327.398 157.113 L +328.127 157.121 L +328.856 157.128 L +329.584 157.136 L +330.313 157.143 L +331.042 157.15 L +331.77 157.157 L +332.499 157.164 L +333.228 157.171 L +333.957 157.178 L +334.685 157.185 L +335.414 157.192 L +336.143 157.198 L +336.871 157.205 L +337.6 157.211 L +338.329 157.217 L +339.057 157.224 L +339.786 157.23 L +340.515 157.236 L +341.243 157.242 L +341.972 157.248 L +342.701 157.254 L +343.429 157.26 L +344.158 157.266 L +344.887 157.271 L +345.615 157.277 L +346.344 157.283 L +347.073 157.288 L +347.801 157.294 L +348.53 157.299 L +349.259 157.304 L +349.987 157.31 L +350.716 157.315 L +351.445 157.32 L +352.173 157.325 L +352.902 157.33 L +353.631 157.335 L +354.36 157.34 L +355.088 157.345 L +355.817 157.35 L +356.546 157.354 L +357.274 157.359 L +358.003 157.364 L +358.732 157.368 L +359.46 157.373 L +360.189 157.377 L +360.918 157.382 L +361.646 157.386 L +362.375 157.391 L +363.104 157.395 L +363.832 157.399 L +364.561 157.403 L +365.29 157.408 L +366.018 157.412 L +366.747 157.416 L +367.476 157.42 L +368.204 157.424 L +368.933 157.428 L +369.662 157.432 L +370.39 157.436 L +371.119 157.439 L +371.848 157.443 L +372.576 157.447 L +373.305 157.451 L +374.034 157.454 L +374.763 157.458 L +375.491 157.462 L +376.22 157.465 L +376.949 157.469 L +377.677 157.472 L +378.406 157.476 L +379.135 157.479 L +379.863 157.482 L +380.592 157.486 L +381.321 157.489 L +382.049 157.492 L +382.778 157.496 L +383.507 157.499 L +384.235 157.502 L +384.964 157.505 L +385.693 157.508 L +386.421 157.512 L +387.15 157.515 L +387.879 157.518 L +388.607 157.521 L +389.336 157.524 L +390.065 157.527 L +390.793 157.53 L +391.522 157.532 L +392.251 157.535 L +392.979 157.538 L +393.708 157.541 L +394.437 157.544 L +395.166 157.547 L +395.894 157.549 L +396.623 157.552 L +397.352 157.555 L +398.08 157.557 L +398.809 157.56 L +399.538 157.563 L +400.266 157.565 L +400.995 157.568 L +401.724 157.57 L +402.452 157.573 L +403.181 157.575 L +403.91 157.578 L +404.638 157.58 L +405.367 157.583 L +406.096 157.585 L +406.824 157.587 L +407.553 157.59 L +408.282 157.592 L +409.01 157.594 L +409.739 157.597 L +410.468 157.599 L +411.196 157.601 L +411.925 157.603 L +412.654 157.606 L +413.382 157.608 L +414.111 157.61 L +414.84 157.612 L +415.568 157.614 L +416.297 157.616 L +417.026 157.619 L +417.755 157.621 L +418.483 157.623 L +419.212 157.625 L +419.941 157.627 L +420.669 157.629 L +421.398 157.631 L +422.127 157.633 L +422.855 157.635 L +423.584 157.637 L +424.313 157.639 L +425.041 157.64 L +425.77 157.642 L +426.499 157.644 L +427.227 157.646 L +427.956 157.648 L +428.685 157.65 L +429.413 157.652 L +430.142 157.653 L +430.871 157.655 L +431.599 157.657 L +432.328 157.659 L +433.057 157.66 L +433.785 157.662 L +434.514 157.664 L +435.243 157.666 L +435.971 157.667 L +436.7 157.669 L +437.429 157.671 L +438.158 157.672 L +438.886 157.674 L +439.615 157.675 L +440.344 157.677 L +441.072 157.679 L +441.801 157.68 L +442.53 157.682 L +s +P +0 g +0.144 w +[ ] 0 setdash +3.25 setmiterlimit +450.12 237.508 m +70.6 237.508 L +s +70.6 237.508 m +70.6 2.952 L +s +1 g +[ ] 0 setdash +70.6 2.952 m +450.12 2.952 L +s +450.12 2.952 m +450.12 237.508 L +s +0 g +[ ] 0 setdash +p +0 setlinecap +78.19 237.508 m +78.19 234.611 L +s +P +p +np 74 239 m +74 253 L +82 253 L +82 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 75.19 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.19 -1.668 m +-2.19 14.332 L +7.81 14.332 L +7.81 -1.668 L +cp +clip np +/MISOfy +{ + /newfontname exch def + /oldfontname exch def + oldfontname findfont + dup length dict begin + {1 index/FID ne{def}{pop pop}ifelse}forall + /Encoding ISOLatin1Encoding def + currentdict + end + newfontname exch definefont pop +}def +%%IncludeResource: font Times-Roman +%%IncludeFont: Times-Roman +%%BeginResource: font Times-Roman-MISO +%%BeginFont: Times-Roman-MISO +/Times-Roman /Times-Roman-MISO MISOfy +%%EndFont +%%EndResource +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(0) N +P +[1 0 0 1 -75.19 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +92.764 237.508 m +92.764 235.77 L +s +P +p +0 setlinecap +107.338 237.508 m +107.338 235.77 L +s +P +p +0 setlinecap +121.911 237.508 m +121.911 235.77 L +s +P +p +0 setlinecap +136.485 237.508 m +136.485 235.77 L +s +P +p +0 setlinecap +151.058 237.508 m +151.058 234.611 L +s +P +p +np 147 239 m +147 253 L +155 253 L +155 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 148.058 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.058 -1.668 m +-2.058 14.332 L +7.942 14.332 L +7.942 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(1) N +P +[1 0 0 1 -148.058 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +165.632 237.508 m +165.632 235.77 L +s +P +p +0 setlinecap +180.205 237.508 m +180.205 235.77 L +s +P +p +0 setlinecap +194.779 237.508 m +194.779 235.77 L +s +P +p +0 setlinecap +209.353 237.508 m +209.353 235.77 L +s +P +p +0 setlinecap +223.926 237.508 m +223.926 234.611 L +s +P +p +np 220 239 m +220 253 L +228 253 L +228 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 220.926 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.926 -1.668 m +-1.926 14.332 L +8.074 14.332 L +8.074 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(2) N +P +[1 0 0 1 -220.926 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +238.5 237.508 m +238.5 235.77 L +s +P +p +0 setlinecap +253.073 237.508 m +253.073 235.77 L +s +P +p +0 setlinecap +267.647 237.508 m +267.647 235.77 L +s +P +p +0 setlinecap +282.22 237.508 m +282.22 235.77 L +s +P +p +0 setlinecap +296.794 237.508 m +296.794 234.611 L +s +P +p +np 293 239 m +293 253 L +301 253 L +301 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 293.794 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.794 -1.668 m +-1.794 14.332 L +8.206 14.332 L +8.206 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(3) N +P +[1 0 0 1 -293.794 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +311.367 237.508 m +311.367 235.77 L +s +P +p +0 setlinecap +325.941 237.508 m +325.941 235.77 L +s +P +p +0 setlinecap +340.515 237.508 m +340.515 235.77 L +s +P +p +0 setlinecap +355.088 237.508 m +355.088 235.77 L +s +P +p +0 setlinecap +369.662 237.508 m +369.662 234.611 L +s +P +p +np 365 239 m +365 253 L +374 253 L +374 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 366.287 239.668 ] concat +1 w +[ ] 0 setdash +p +np -2.287 -1.668 m +-2.287 14.332 L +8.713 14.332 L +8.713 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0.75 10.5 m +(4) N +P +[1 0 0 1 -366.287 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +384.235 237.508 m +384.235 235.77 L +s +P +p +0 setlinecap +398.809 237.508 m +398.809 235.77 L +s +P +p +0 setlinecap +413.382 237.508 m +413.382 235.77 L +s +P +p +0 setlinecap +427.956 237.508 m +427.956 235.77 L +s +P +p +0 setlinecap +442.53 237.508 m +442.53 234.611 L +s +P +p +np 439 239 m +439 253 L +447 253 L +447 239 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 439.53 239.668 ] concat +1 w +[ ] 0 setdash +p +np -1.53 -1.668 m +-1.53 14.332 L +8.47 14.332 L +8.47 -1.668 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(5) N +P +[1 0 0 1 -439.53 -239.668 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 210.041 m +73.497 210.041 L +s +P +p +np 61 203 m +61 217 L +69 217 L +69 203 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 62.44 203.666 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -1.666 m +-2.44 14.334 L +7.56 14.334 L +7.56 -1.666 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(0) N +P +[1 0 0 1 -62.44 -203.666 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 157.941 m +73.497 157.941 L +s +P +p +np 61 151 m +61 165 L +69 165 L +69 151 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 61.69 151.566 ] concat +1 w +[ ] 0 setdash +p +np -1.69 -1.566 m +-1.69 14.434 L +8.31 14.434 L +8.31 -1.566 L +cp +clip np +%%BeginResource: font Mathematica1 +%%BeginFont: Mathematica1 +%!PS-AdobeFont-1.0: Mathematica1 001.000 +%%CreationDate: 8/26/01 at 4:07 PM +%%VMusage: 1024 31527 +% Mathematica typeface design by Andre Kuzniarek, with Gregg Snyder and Stephen Wolfram. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00] +% ADL: 800 200 0 +%%EndComments +FontDirectory/Mathematica1 known{/Mathematica1 findfont dup/UniqueID known{dup +/UniqueID get 5095641 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +20 dict begin +/FontInfo 16 dict dup begin + /version (001.000) readonly def + /FullName (Mathematica1) readonly def + /FamilyName (Mathematica1) readonly def + /Weight (Medium) readonly def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + /Notice (Mathematica typeface design by Andre Kuzniarek, with Gregg Snyder and Stephen Wolfram. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00]) readonly def + /em 1000 def + /ascent 800 def + /descent 200 def +end readonly def +/FontName /Mathematica1 def +/Encoding 256 array +dup 0/NUL put +dup 1/Eth put +dup 2/eth put +dup 3/Lslash put +dup 4/lslash put +dup 5/Scaron put +dup 6/scaron put +dup 7/Yacute put +dup 8/yacute put +dup 9/HT put +dup 10/LF put +dup 11/Thorn put +dup 12/thorn put +dup 13/CR put +dup 14/Zcaron put +dup 15/zcaron put +dup 16/DLE put +dup 17/DC1 put +dup 18/DC2 put +dup 19/DC3 put +dup 20/DC4 put +dup 21/onehalf put +dup 22/onequarter put +dup 23/onesuperior put +dup 24/threequarters put +dup 25/threesuperior put +dup 26/twosuperior put +dup 27/brokenbar put +dup 28/minus put +dup 29/multiply put +dup 30/RS put +dup 31/US put +dup 32/Space put +dup 33/Exclamation put +dup 34/ForAll put +dup 35/NumberSign put +dup 36/Exists put +dup 37/Percent put +dup 38/Ampersand put +dup 39/SmallMember put +dup 40/LParen put +dup 41/RParen put +dup 42/Star put +dup 43/Plus put +dup 44/Comma put +dup 45/Minus put +dup 46/Period put +dup 47/Slash put +dup 48/Zero put +dup 49/One put +dup 50/Two put +dup 51/Three put +dup 52/Four put +dup 53/Five put +dup 54/Six put +dup 55/Seven put +dup 56/Eight put +dup 57/Nine put +dup 58/Colon put +dup 59/SemiColon put +dup 60/Less put +dup 61/Equal put +dup 62/Greater put +dup 63/Question put +dup 64/TildeFullEqual put +dup 65/CapAlpha put +dup 66/CapBeta put +dup 67/CapChi put +dup 68/CapDelta put +dup 69/CapEpsilon put +dup 70/CapPhi put +dup 71/CapGamma put +dup 72/CapEta put +dup 73/CapIota put +dup 74/CurlyTheta put +dup 75/CapKappa put +dup 76/CapLambda put +dup 77/CapMu put +dup 78/CapNu put +dup 79/CapOmicron put +dup 80/CapPi put +dup 81/CapTheta put +dup 82/CapRho put +dup 83/CapSigma put +dup 84/CapTau put +dup 85/CapUpsilon put +dup 86/FinalSigma put +dup 87/CapOmega put +dup 88/CapXi put +dup 89/CapPsi put +dup 90/CapZeta put +dup 91/LBracket put +dup 92/Therefore put +dup 93/RBracket put +dup 94/Perpendicular put +dup 95/Underbar put +dup 96/Hat put +dup 97/Alpha put +dup 98/Beta put +dup 99/Chi put +dup 100/Delta put +dup 101/Epsilon put +dup 102/Phi put +dup 103/Gamma put +dup 104/Eta put +dup 105/Iota put +dup 106/CurlyPhi put +dup 107/Kappa put +dup 108/Lambda put +dup 109/Mu put +dup 110/Nu put +dup 111/Omicron put +dup 112/Pi put +dup 113/Theta put +dup 114/Rho put +dup 115/Sigma put +dup 116/Tau put +dup 117/Upsilon put +dup 118/CurlyPi put +dup 119/Omega put +dup 120/Xi put +dup 121/Psi put +dup 122/Zeta put +dup 123/LBrace put +dup 124/VertBar put +dup 125/RBrace put +dup 126/Tilde put +dup 127/DEL put +dup 128/FractionBarExt put +dup 129/EscapeChar put +dup 130/SelectPlaceholder put +dup 131/Placeholder put +dup 132/Continuation put +dup 133/Skeleton put +dup 134/LSkeleton put +dup 135/RSkeleton put +dup 136/Spacer put +dup 137/Cross put +dup 138/DblEqual put +dup 139/Grave put +dup 140/Acute put +dup 141/DoubleAcute put +dup 142/OverTilde put +dup 143/OverBar put +dup 144/DblUpDownArrow put +dup 145/DblUpExtens1 put +dup 146/DblLongLArrow put +dup 147/DblExtens put +dup 148/DblLongRArrow put +dup 149/DblLRArrow2 put +dup 150/DblLongLRArrow put +dup 151/UpDownArrow put +dup 152/LongLArrow put +dup 153/LongRArrow put +dup 154/LongLRArrow put +dup 155/ColonEqual put +dup 156/Diamond2 put +dup 157/NotSquareSprsetEqual put +dup 158/AtSign put +dup 159/Solidmedsqr put +dup 160/OverDot put +dup 161/CurlyCapUpsilon put +dup 162/Prime put +dup 163/LessEqual put +dup 164/Fraction put +dup 165/Infinity put +dup 166/RuleDelayed put +dup 167/ClubSuit put +dup 168/DiamondSuit put +dup 169/HeartSuit put +dup 170/SpadeSuit put +dup 171/LRArrow put +dup 172/LArrow put +dup 173/UpArrow put +dup 174/RArrow put +dup 175/DownArrow put +dup 176/Degree put +dup 177/PlusMinus put +dup 178/DoublePrime put +dup 179/GreaterEqual put +dup 180/Multiply put +dup 181/Proportional put +dup 182/PartialDiff put +dup 183/Bullet put +dup 184/Divide put +dup 185/NotEqual put +dup 186/Equivalence put +dup 187/Approxequal put +dup 188/Ellipsis put +dup 189/ArrowVertEx put +dup 190/ArrowHorizEx put +dup 191/CarriageReturn put +dup 192/Aleph put +dup 193/IFraktur put +dup 194/RFraktur put +dup 195/Weierstrass put +dup 196/CircleMultiply put +dup 197/CirclePlus put +dup 198/EmptySet put +dup 199/Union put +dup 200/Intersection put +dup 201/ProperSuperset put +dup 202/NbSpace put +dup 203/NotSubset put +dup 204/ProperSubset put +dup 205/ReflexSubset put +dup 206/Element put +dup 207/NotElement put +dup 208/Angle put +dup 209/Gradient put +dup 210/RegTM put +dup 211/Copyright put +dup 212/TM put +dup 213/Product put +dup 214/Radical put +dup 215/DotMath put +dup 216/LogicalNot put +dup 217/Wedge put +dup 218/Vee put +dup 219/DblLRArrow put +dup 220/DblLArrow put +dup 221/DblUpArrow put +dup 222/DblRArrow put +dup 223/DblDownArrow put +dup 224/Lozenge put +dup 225/LAngle put +dup 226/Diffd put +dup 227/Expe put +dup 228/Imagi put +dup 229/Sum put +dup 230/LParenTop put +dup 231/LParenEx put +dup 232/LParenBot put +dup 233/LBracketTop put +dup 234/LBracketEx put +dup 235/LBracketBot put +dup 236/LBraceTop put +dup 237/LBraceMid put +dup 238/LBraceBot put +dup 239/BraceEx put +dup 240/Slot put +dup 241/RAngle put +dup 242/Intergral put +dup 243/IntegralTop put +dup 244/IntegralEx put +dup 245/IntegralBot put +dup 246/RParenTop put +dup 247/RParenEx put +dup 248/RParenBot put +dup 249/RBracketTop put +dup 250/RBracketEx put +dup 251/RBracketBot put +dup 252/RBraceTop put +dup 253/RBraceMid put +dup 254/RBraceBot put +dup 255/Wolf put + readonly def +/PaintType 0 def +/FontType 1 def +/StrokeWidth 0 def +/FontMatrix[0.001 0 0 0.001 0 0]readonly def +/UniqueID 5095641 def +/FontBBox{-120 -220 1544 923}readonly def +currentdict end +currentfile eexec +D8061D93A8246509E76A3EC656E953B7C22E43117F5A3BC2421790057C314DAE3EFBFF49F45DA34B +424A961BE670A194E7E4BF0FF295DEE23134A14A7C08B6602621D885EE631B1D8D3003CF2093E039 +4D77FCEFCA8BA8965D1783DCAD9EFE6C7E420CF7B898933309A89F84721ADE7F3AE4443C5EAE8155 +759A9EB6F738F7BA81C192EE45DAD00F398FFD6904111BBD91BFEE328004F2A18BCCD98DCDB2CE23 +961B00F204E50EA022D00CE2B68E653626D4BB5AFA334A0D657307416FAF7AA8C43ED4DC541F1B7B +B7500B3F423D9D369F8192FD00A59FD5E6ABC70F788FB70976CC1907DDC309F4B690AA2D2BF12CAE +C493958CC0E76CE9EB5FF8BD1F1650F659E5C123EE455B7D77C39952C212AF319BF19A91E36DE52F +0EF84B602704BD6C7C94E1B0E067210DB919F6231755A2CC5D5FE129279B43A2E2CCD27F56F00B05 +C8AC10AB07FABBEFB3509088301FE78CAF8B440C5BA2FFE06BBFCD066046618F3B6AA2E4B17B296E +1F3F1560E19CBAD54E16E4B7A65622E468F6BDF97C50277E355D1DD8843D0A449A147FFBA071BA99 +CF70D7576DC18B96FEAF8070BF25F3A3141F873241EF4A07F332306B56F1D5909A4F233A9DB3A08E +E43DF38DD6DB2D6DAB908967A907303EE1FA04F048FA6EBC531738C170B8B0F095FF3B05D14C2BDC +272F7EDA8926A77D9CDA49A90AE1387A51A24ECDB2E4E287B0F95A83AD2EC0310F9B6F396AC10479 +835035FD5D4C84D91917FE8A8755C976504AB1A830ED516B5F325EA4ADFD115900D23039A2BC84EE +D21CC21E2BBE29A5E0CF28BE047CBD515DF7785F37DDD4474042B102A1F28193BB8567FF6FDEF811 +25CE9A504BE5011C0010DCEBCF321824C9DA249D8DB11F79298F7B674CEDB6F33C111F8B0115E407 +99E0FE1B6CE9F6B2A3EED1827A9CEB453D643FE5CE2540DCBCAF7B2EA2C8F0AE9434D4BAAEAB3488 +FEC7541D57179BDEAA0BB6145EA783E73E70E0AA71A4FA58E81EB510E9FD2CF4ACFBF28E48CA313C +CF5ED2BE032B7C9A07ABBEC9CCD8D5AC9F775D36BECC252B9FE01B6AA79A70E22478904DADDA08BB +7CA3B66F3A83AEEBA37406127353790348AE5FBD144ABD8E1B32D1BCC70E2BC22A63E854D38317E6 +BB97C52A6C9B0C6AB5C336CE2D417A714825DCD237F7486C33925A995CD8AD3B359176C4CA775FE2 +2C6F8A7C7343F31F39D1B9A5A744973BF65D655DDB59E3495B28DBE2A877EBB32A22A4AB8EB13C67 +02B239F6932E2DC8B4B88915923B1C2AFF2F876A31F765B91747D5A858BD632B0BE4F135AC484016 +AE1BC01E44B6653A58EE1380B6DF24AEB73220A36FA8FDE9A152C16E049D2F247A9AA71CD2DF3D9E +653054FAF518BBC1EEB43200DB0BACA93FEA9445FA5F32A99926C4F1F2370B2E3E03CEFBEECE5D5C +DE299FE1641A9CE0C90E42E7C465DF8C19C153EA76C11791F8E112853E708CD0F6EFC22E44808A44 +3686442645D643E7118D128BF34188FD313E53B8E951E605E96825738C4DC893D942C145C3E1153F +CDED16D1EE10374626F45781461FFC94C77D431BCF167FD29136A0B369631E139A59B06AC5281B3D +52470B38237B469C0E49CBE74C82A940F8AAD10E05C5DD2A8F3218C4BE40DCED0982770869190845 +D2E8BA2A1A2F9CF16DDDE418B6E2013C3258FBE9AFCDACCD57B01C4FEF3526C06FD5BAB6F195C557 +23A96FA3E5CDB2ADC9AA8989DF78F163F19E26F3B9EAF60D44B745FCA49B09D9CE5CC16D1E2C78C5 +A28F14D3374956E9721C0542A01A731A3D5A771C7B51FB72960BB22C99BC8F216530AA86C1A0346B +3D986EF1DF68BCC46EC4C809C6D54FB9C50F225ABA2C96965F4DE77E2C5D131318231C6773F0976C +DBD016B803C38B8923822BDF160FB87BBE744D0953EDEBDE87394282B9950C89F58824D731355E8F +A3CE364F742E3337A983BD1219CE8787CFA6925B560001F6F78343647821D408B60965966C16C454 +394D33268483F51984614FD9964CCE5F1AA4AB12144874A72D3FE97F4416ABE4213B4EDCA9ECF73A +937B562F978E059705E561A6D0C8B89B59B4CAB2248BFC4A6D3D98F89FF38A4D1C3E5A4570E2E2E8 +D328772E11DEA2116C260B13328045139A819F5489A2C302F311898230CD26DD03E6CE3BE75EDB86 +0D982FBC8E1E24D1F83E8CA64174495B2F32BDF0505FC96E9C65FDB0EB0C4ADA410F0A1C4BB4D551 +93B1AA5EA1F988882A56413F77CF24FF6516CD5095077BA566116797895FD7EA616D10510957025A +1DA05064FD9176303A248C94CE2A9875C03C7D8755A1A8010B712B64BAD73BEA4195D51A328F076D +12C0E52E87D98A5269D157D544CD7B4E1CAAEA6BDD59F08D0C5FBB84A6B099BECF8BEB721A874BAA +1BD1F8CDB5ED5CD3B49A763EAA36D33837667A6643B83537EF5515DBF7659E28692E0ACEB48FD051 +45534A443092E0A510B05B5B51B402CB3372A4E8BAF98A7FEB6D8BEF6B364A5EA0F3FDF301A44EE2 +3485D4493E7B4C33E0E352202931188D156983C40F7A4B615B6F5281B33FB32A2E6A0F7AE84BEA2C +5C3635D7DA17371D608847EB402270B83293EC09E4B1D85996C1CC81196BE590EC9EF0F258364746 +BC4763976FDDB3707D37AFCBDC0F4EB2B1502F137EBBB1541B992EAD43B5D64CCDF505FF2F127115 +4857C69B689A11760979F6D2BB032CF0DCCBB33D2B6F6D6BB29A1D0E371AA5869A408225C0AFF523 +CEFB08A7D3D17DF9064010363196CC569C436E720B1B4573CDAE1CD93A10FD2D4ACB14E47046B5B7 +66B75B40BA97027FEC4218980B002FAB60A9F1F5A37861B9A63F696732F8127B2C6F036BF32311B8 +FF08A489E88218092D57A99C8981EF8CBBD09DF49CC836B9C1968692D1FB551F47619F020289F1A3 +D3898A96DC1C7D39A21B49C2D0DD9508CB8A8BD6A5EB40FF86F555FA32017B67AEC07BECD659E8C4 +8DD1D43E8D1EE08A117479015F78BF20D3318224572D9C90884856E4307A8AFFC83EDD317B008484 +3BBE8EB2A4E2D70077A639FE3637C3DCF87C67F1BE62E63CC67BCEF8EBD07E030524A53DD440F2A1 +3A019B7AA89E155AAD1497974258A01DA45DE1D94BB9F925290FE9BDDA29EA3F2BF1E64DF7EBCFC4 +23AB2C7310D9D87A5CA673EE95189135E1B134B431B231428FF2BF64C8F155CBDDAD17BCB524CF7E +ABD66B75705BFFB1DDB27B72D681D7AA19FAD0FF23EEF028B606DD20D58588AACB299A3CF38372A8 +E7494A65227C1E4A231AC7843962B8A4A7BDD90F3F8B83277E696F3514139BB8DD207641B62286F0 +0E32C7FAD7029DC0E092564C7CE1BC5240FCBFB00A06F075D9536F0326CFFBA958BA7A1A11AA047B +B14E7DE16761BB0FEF19ABE85154359807C339961B9695CDED59E489CA4D9BB75D86D9EDDF0502BC +0B4EC36B1D71FCD4E03881ECEC703E5DA23B3F5DB98CB8DAED81D5BA20B844A92A4B92FE5B082952 +6837237C7F994786878404BE744D0953C676E52CB05FCE193E8827F977B31929E36E320E770A6F1E +972B84AFA21E01719759DF0132C5CF9692BAA487E86E8453C09FF97642600D1024ED5D6C7D5B387D +CB5E6147D20B4A14D7D485B5747D765C5A1CA40B9416DC4EF5DC08F43F0706B27B1C90E901556E1C +EFF304EA8DF8D727E4B7E7CEAD14E4FC7E76002DBC37B92BD0E58AF29DA7DA91E6F99DADF1A9CBDD +04648E2F5B811F2AF0729646B17D32D7EF25AD47E267EE53A61A7CD744C9ABFDB1EDB71C0705412B +15769188CA1601AF0395796FAC0E2139EF90FAA65D27AAEEEE319D2831A56CE82203523097D0574D +22742D17E519D95560B8445B5B29033BF3454B94F20C23EBE8B90DDF26D903F1D66CB39D3A64579D +39D136C537CCD9FF2D6ACE5F79DE696C7330C0C4EA179D7911B7B67858D86CEE0833AB1E105B1993 +2A9BD572C41E78FB4A2A2421514DC2066E2F56101A9027F4BBA5D48E5DA9218E81CE46B95B50380F +560C67A5D547A8D32610AECECBB6F5A09DF44994F6DAC64F7399C07E182033BC1B9A32B69C41FDFC +7E1DCDDF273F045F154D727AFEE3CDB816CF2ECDB6733C579276353DD59C9D2AFA455935C1FCD0AB +7D57F9DD79FBCC7A45E5E5A501FF17EE1C5FF0823C6FDE29C60F85F6786940D8E3540E891B5BF7F5 +D36C57AC3AD359BFAB12B215E7FC94B933386C250E6B236506FA3D151ABAD893A817772071C09144 +6E5FB23A17591A4CECAA46DD46E3C41B4578F21D8894A59B72FAF6F88EE6E514FBD2FE69447D2B59 +9A6AA8BC5C1FD5C25E50BFB5CE5DBF5AD5771BC42FCC3706B6E9F7E4FAAFF2E63ED1684C5A4C136D +609D03E31EBCF31E864AAA74D8DDBCA52F85CCF14AB739CC55617EFC964D6CC6988AA45245B19CE2 +B63CB865DF1F1DA4A200A4A043C5CB706CD303EB31C32866ED92077AB11BF136D158840EAC67E7A1 +1BC2BFDCD5894AF735D677E1AC98BF3F19F69AF75355F168632037F6EDEBF61BE5854057AD05972C +7DA8D37AE65D35738547A9D835C6351D8D732F8C0DC49D7605F00A6A6045559D3A0B0CC21DFDD75E +2FCF25522001741EBBEB5CC97DDBD9DDCE490FE3CB186C101940DD02CACB685ECCB8C1DEDCDD4156 +F5F9F6B3CA53EC6E7E8A2F8159451CD5479D91BFBF91C6B32A1E0053017369330EAD2DDE323BCAC5 +EEC91A595473F447355B1BDFB873D0B538BF54AFB8EAADE5309E1B74283218F59272E59619D66405 +E74B5A9D6323CB76AF00FB27FD984F740601248C7206F59EF7FF4E95AF95327D12C47D2D34CBFF33 +29F28236831D7F0FD9D633B940D8282E1F1D5D95616CD839C6B41A7195A22B7A320864E48943CE99 +C68E3591A97689986A6536C3A1C37DA9838FF71A8C1A03094537C95D3585DF5AD9292874D8D05720 +030133B02E2754DA24712B52D6D8871422C709532B09D147EC8ACD861FA5392A82A6C2981347E327 +1986017B8315822B5FCB19A1B31BF6909E0D6490EC823434BFCE031045D20FFC675A73EBD7A39A50 +44C3B40906186CCF6E8502CD7915F053BC8CF6BE9FDD9A0330AE943D5C9A11D60D43F5BBE8A045EF +CDB7D19C0053F4F082303442C35C432E0DA4408C5917D24A6658DB807BD754AF140CE84A34F79851 +9323060D60F4EAC0E82D3E17042BB6729C69A8B8774904C6214C2EB016C528DC1428DB41075AA6C5 +4E4D849901838C6B6DADF7B67CD0CBC6EE1B415B44654D89397274D4A6AD2BA69DD81567F5B802F2 +684DD3516ECA0F6D66046EDA0B2B38F420A238D67D98676925ECBE6711D64DAE5DBE8AC5473A2EE9 +97AE65F0715A2E5DB2C75F20D9939EF58692EDA3AEA81F25AEC888327CFA6CC13D496714E63A1A87 +11FC50250D6D23FC7A8017038281F659A685ED7F1BB1ADBF8833ABC6DBEC0D96D08334E58C67E0F9 +0046132F0D4FBCB9CDF52EE74561498B42701AB826A6DD77F46C14753748E1EC66F4BD3583FCB4F1 +DC91050CF18B0D51BC366549261282779FC9694A7B987973C0D59F65CFF3CDB88D23952E46F5EEC1 +BDA0DC354188C11B0FA191F5C11A45BB9093701B33B8E7AC1A8621793D5326E92CDD3A76FB9B67D6 +860567B9CEE7B9F9056C6E9971F642DC0BCC1D2882B5BDF1B1CDCAA3FC61775A74E70CDFC128DE0F +9606091BB8E53905108EE77A1D3C506550FCFCAE454B020379BE3A7D6C62D162697AF1383F7BC8F2 +34FD616324C1E56E3E440277862CAB2C02395D9937816117F71A6828344182E6B5AF2799E29A6611 +9C0543E135300E44746EF2EBA57C4EABB1A15D8AC6D037F4BA2BE1EB4D1A7496F9711FC67E56D4D0 +FDA4D810B5383A72ACA15872DE9F3A9890E33759CE4DA493691BCA47A92E42D950DF588C3574D6FC +72B5AF7DDE915E3F5925B3E97DEBE6A4208CE7337754045607679296C4EEEA3A3EF1915E85EB1A32 +F1BBADB2904B09806631E20BBF5E57AF11BC784C75BF049B4BC7E479F1D4AE7754CBED7B11ED80A5 +2DD0006FAE5CC23A7C234CF628B42F4104A490E7D59E8B1F2A1395D37641C478FF8CB9FB602B29FD +3E48D6087CAEE48B523887F27F69DB32BF80B760B030A6766F8F9B19DE70704DAF81D3DED2BF663D +89B5BD8AF253BB8FA59DF84A52FDED83D2737532B6D66AFB9EF0446ACD44BFAB797AB9FDB47F2E18 +8F0A55887853772EBFD029B5FA0AFBAF10A88D464BD6F634C5839B230D583E36671A51DDB1EBF471 +8ABB84D057729D514751B0EEF4F756B91DEDAD7D9AD529B3886008F1F32645F3A205F304B2A8D808 +D37A38E389B8587E8D3654DC374568FCEBBA160395BE1D132B1ACB434164525FBF250A5AA30F3520 +F0F2A75F9D1B7C6EAB0223C209A120D894D6ECA336B57B7F2AB0E2A94E6616D7539010C700311966 +7B4A9EB3A812FEF4D100AB4C036401D21DDF8AEB393074638D154418D3A7AE51CD1F0C2D5CF4B475 +3B582D5071F91E6D3AFBFB09EAABBEAB2A60A5D388D16232939B5483CF7D5C1C9D05DDC85B266F39 +6F61E179AB9FAB644040014293EB53F2B50E8751F9D92D1DAE8DC89594905A39357C0098265FBD24 +E407F9A22027E36D5608FAF15BD9E354228E3BA943EC30301ABB2CB105F3B6D57C001EBF8328A8CA +318A7B1B999AE8BF9E2FD530B669640116149B622EB3C0A8CCDE62449DE9D39E0F5E5E01CBBF4F5E +52B33A2BD60B52FA4A5CE98B7313FE4C3FA30FA07DE4516109C3EAEE97ABE430C505647DD8B3DBF2 +BB4B3A806B14A9E0F629863F15D12A1CA62E8E055FA6ACABDE1926D3231CAC271C30A3F7AAC6084D +D88628B943052B618E4E39148600AC1EDB57B0835C291F73D29C51FCA3C5EFB1DB18A5CA47433B45 +C57EB3CB28AEBC68E4171E6DE357793B0FD2A1F4E76180248017112674DAD7ACA6ECAAF209CA174A +5603CEA5CE528F20919443900636E2FC79C303EA7B467A3F001EA8CB849BCF28BF40A3364C08ABC9 +B5D935CFEDA827A8C3FE2F6ABA54017D2AD0393A05AE21F7AE1F58AE1E992B5C889219DA157FA7EE +92E291DE2F9DFC244F2CF0FDCEFCACC487F0EA830985B687556D5AF53B81814DE9CE0C6C430DCBCE +51EBC6091B4E0227732E07DF20D59E9EED4D8A83761CED84CCE244BFD6A39103A2B48A684AEC9210 +5C94495D54FD6690AF857E5A3915E6A15689F1816F75F1FC8D5769B240639B339EBE54BC6D84794D +50F4EBE892530351984C6F8BEBE097CD46F4FED7D46E32830A16F77C13F13D17003737482F02D1B6 +F04C57B4C2B1929AA315B4BE1C7C9CB55F8981508546B67E4EBF84B6026C355C5E4E07CD98C95F07 +56F6643FB1DD7D8C77C4AF4C4F23602DD3F61D1C46583071AC460E74084F7F7CF812BC53975CAAF8 +B3C1160B5D6864AF08A1733FA893CE4248C8F8B46AEFCCF317DC726BC1E5F2445E462E0591BEAAEA +49AD8E4E2D3CF07F900EC46D596E9CDB3A8710A0B93CE5DA9D35E612596A6374F35AED0EF55DC46A +8E14A91163B87417259DE926BBC3FC5423FF0AE2AA6D740BFFD26981A57C8C1D97FB04A90A567296 +B07437F94C8FFF4709213DD5D8862A777CF3F97723F43A4F913F4A30F7554ACDAE34713654E21731 +C594C3718C0547FCDAF7BB1620B2D6BB19E65596A585290CC43F50B34A2FE6EB1E495ACFFB16DFEE +8784B66FCB9359600412969E2BDA330C421C68345A797C49728450A6CF41C926AE8EBBE80BD97863 +593C3E8AB5415A8BA625957F242378948F5C7EA9C2641312E8E46955FE5C1A1824C71D3B0C9FD211 +7CC965DA7C21D257587F291AB7C594459A11B977A278C74CF5E6598B0C75ABBB2FC1B3D167D7E31D +B519F4A0BDA650A3FE7F1A281DB9B69B57A23656BD820B22D0E30B07361FE7C90933E24A32B6DE2D +F240635D543315226E32487F264AFE83EFEAC74117321A82A92F4FC7B259F26DBE74C966B4CB3F4E +3499E30B9B326F72D73919D9FA9024AAC0B95B6751AD4CE569CC3DDFC399428DF1298FB1733FFCE6 +240FB3BE3A2837E1A66E08B784CDD131B5A61423508000785CDC5610CE4DA1DD314131767C8857E3 +3D9741EF3FD7B8D0AF0371CFFA3DCF74FF2D3B421AC0339EBC05FB4B61A2F46D6BD1144747AD148B +7968D7DF4D0F742AB39B0A04A88D66F5CF9876970879879872BFDA0C56C253DE5C72016B4990CEBB +2455DCDEC7465EE7C7E1C557B570E9F3EF3E31A22DC8AB9B17F34F48E135BE7820ACE383DB7C5B05 +8A9DC855C6850E49AB7D7D00ED2D23C50AEE671B11752F0A682EFE179CECBFAB47B76676AC0E8FD1 +0A47353D3AC3477A59B0F3CAF22849DE97AAC8B79935F7C57F3052DE7E13BA0FE7CEC4685C86E841 +EA8C7D6734D0FEEFF538CC2AA1414EC9126637E169FBE4ECAFDFA29A08451B25954F0094710195E1 +69E0A6004419E432E9319BE2AEC8D93407B25B758336946C6E30032657DD857BE9C0A05F487423D2 +0C2772D4416D27FEB5CCC971DDEDFE9B8C2D7DF9DEC90D0868D5DD18850BE567A04D08310883D9B2 +D50B817D0690F8550E238C1139C0A117B48199B1B4D489C9E52C58E0CA75E6C386ADD447D8AE52D1 +D979FD52A50D82BBCB8867B5D49C19BDEC414929DB67A00AF7C6482A87E46BD11D8E4E52783E5845 +FB2CC7439F99FF6552C7288354B436B1C361AB8C2932F77B14A50A7407FC0BCC29662D710248CA46 +AC42A03FBBEF9C972783942F3721BD32BDA3273D1E353D9E013D4CFF630BFE4E7C2963FECFE350A2 +860421D17D6ACA888FA26403726A0E16BD10638D8511A2C8A36C99E9B72A422038E1149BF88A7CA1 +B2DB196333F3AD32D6FE28F18FE1ADA06FD25E4597936A924E71C188B9E8779BDBA928A182042C96 +F6A14A6FAB5C1A819DB8E9374E32F77393734A65606809E90D188F749180A3E6CA7AD8044E168677 +15FDFF350D70B14454B692715DC3AE2CAA561FB953B478E873EB19C41A8D83288451E4E910D5988F +33D83C5974DD0EE4DF2E6C849C500D0714038ECB1E9336D71D852889F2FBCA6769949699BE6BBF19 +A9318CCD7C845D0EC7FF3CFD871328CF7B45E6BBBBD16094E8ABE4D3789DEAD2C930AC8D810C911C +03AF2BDA4EBA62810F19B4506592005ACFF16EB58E33D6A71EA4DAD28A2D7B10FF89ACAB4BCC0F96 +E622EBA20347AE04C62A298257D1623977D185BB46B42CCDD733F72B37072B8DFAA5D7FF77E35618 +3F6D25EE1D951F7EBFBEA8FA17A859B8545BDB212A6BFE9C3078C32124C4E5D4DA09404E40498F76 +7B7164C13E12BF006EE8DE408070296EF0A08AF759206DB3C445BF49EAB18ECDE1FEDEFFAB653FDC +B13FA4649B35D12266FD42D0498331560E96F54D4238678F70233F56097E1944FC671D6BB1AB66CD +E0E9DC24349E44D67C36B3E3A00B07755749A597DF31C25F28D55779841BD3F6221BCDE389852DD4 +590E075C1298B36181D9F64BDCB54A556C05A9EF661EA1CC7C7549A3E1CCF79B87A6E71C3ACDECC9 +C4EFB05B643744407029258B8225DBF960DE529EEC262D1D3113F6CDDBCF4BDAB706BF085C0FF2EE +E8420CF755E46B053B774DF37C5683F8432EEC183C96176BFB24B3F478FACACBF1FCB73D83D4D857 +2D0F15783C8AE95D7CE959D794FDE1D973867D8F30A301598BDB7F2824B2629D64B88F0FF4498B6F +3728CF8916EA884C5930677E7481F90C85ED41DD28AA00E714D3A4F2CC136E417A4C591C7944C409 +70D2BCBE410A940937C3CAA118FA32317444B401968B8ECB2F0B3C8DAF6D4886C2015000A71FDAD4 +066B82312A4CD1E49A9ACFA05C3E7CA5A5CB3FA713CA0AD9E66A34730A36612C72D1F803D4CB1501 +9184FA2FDB3E5D067BC64B29299D5531565264B70FFFF86F5A5B03848E55D73337650208D21F35BB +D5C14748CBE17EB3A7E02BE53136DC69E8740C597CE28472CAEEB96EF2A5752CF17CFBB82F6C104F +2BBB50C216C49E8AB7E661207E1742B35392752051A1E409BEDCDA2654CB5155B770C8C5E4A2968A +A35CF1924D0C74A8D23AB811B7DCE60F1EBC1494A295C8D670E84B7B886A6064151065BD96F2D364 +7BA049A444CF16EB6432CAFCC70FF2E8B354F55A192C94BF08D156856A6512403F5140DF4C8D254E +DA59B2B3ADEE19A35E9A61202B711193A7E7BA8EF427152157DA5A51084EA46510864E1CD7B4FD11 +16E74D7BA769ABCFAC556BBA7CC528C18003A2AE059CC97C18263500667F6A9A8C421F2ABDD73EAD +82D9D08940DEE1688D4AA200ED80C3AFEF6A4979154D99478D4F4EB3738333E8581A755190D87BE3 +09A319ED170A107512F056E4C619D4BB1615BA6C0680117610A26462F267B76F9DBC9A7A3AC08C9A +512A44697082A2718B62FD1D565010AC96E1006F958070AB7567A33E1FF7BD450681DF6BD4EBD265 +CF12726A7EFDEFBB9BA1E596BC5C2940A4FC9DE60116C9082138F1C672977F4AA6B7986ADABBB2B0 +651A7B0C06C4BD405C58B8C81BE075997E118E10FC008AD1F9ACF9F6AAC6C9E3F6DC7FCB838720E8 +D0D9BB99F203EEA2566F18232711E832810F10DD4E2DE44A0A81603EB1162E1BDB60AA1E2D771EC2 +E6E2C3B39508E5CA03A1E2A7F2860BC6F0B57240880DF54C325F4729EEFA1D2753D57F055CDFCA5C +E9C6630980C7121FC21E2F7223E6111C18FFDA0A0A7643A213DE9525AE138814C4183BF03A26A36F +EE9199821464C845A746945413629DC53F5A2B0D8CE9282B580ED662F2F07398D6A8B7093CFCC6F5 +E0F5A7926D2862AD1CCACB84D85902038551EE3EAED02AC7754E3B65818C530A0252C049A68862DC +A459DDD937F7BA64DB16AC86885B68AF46422D7C4923070E20CBAAC9F14E43979C5D5AC4C9321016 +7CCC52E7DA272146D68D8F61DB4D60063E74F6673B41ACB601DEEB1DF73517A8C5388F00E8A9D236 +9C85DBFE4C1E9864AB52E09F465EE98C194151E66CB98E6981EFFCADBC48532E9795290CF745FDA9 +CB7FD40BB77C148A8B90C0CA50176103E2ECCAA57030F7C0882F1E891F9EEBA077AA4992FAE38C75 +5470A4C35454EBAB45271DD76E4DBB2D9147817F7D3FB800EA136D3F27C84B4D45ACEAD13D3C91EE +BD6836AC04F95E578D1B7B8CE5B718E42FD1BBE91EF9A7873F0C6DC59AD231D08CEB4AE312F83F1A +9310155D2C4F216A6FC72385C899B5390EBADE7CF7BEB028F73DD76EDEEF639E9EDE034ACB25BA57 +8C7BEC3581FEE0B221F7340570489596FC60EC805405E0D2ACF0687A62A75358B3878972D4C039D9 +07D868DD00C5F7F3342384C09B01F1926F7D37C2B862FC516361253CBBDAB354898B31B1FE58F773 +61517F2C2E106708AB659D95CE3E9E7777B7327DE01AE2D1E0C84B9EE3887C094C149700CB095D5D +A5FEAF1AA9027AF257576A7370D79FF4DB98511AA27C5C083FA0CA60A3A5E66B8DA9A22FE5DD3DDF +C529BEA71E83881D8B010D140AD686DBEC9AF4B331086F6556B695CAB14BF24B5FE64382E6F9BC21 +5E9EC40C0914B1E27BC25F43E6F555B551726A8D9CD590D5AD3732777EF3B00CBAA89F8E8E0E0347 +7D45B00181A7B196FD4919F110C0F2C0F1B41231051AB5BC8F8025ED00C10F2C2F51E97B622A4E84 +E6AADA59F896F83EFADE779B37FACC56BDCA1892A3BD965B65D51B55AC1D73ABCD16D0EADE55C0BD +3C1BE9FDB323E9FBC9835C63F757051E29E773D3491706DEEBAA0F1182F0E02D0CB701661B30770D +94E240E1358195781006E18CBFC2D83F229A89C3066E35CAE1384A0266D5A37824A089E855F11209 +9F723AF03BC1C8B1C0BCFFDEBE3EF73A40BF83F5E038B63267DE5413B88D30155E62EDCFA35C0047 +0178E5558CDA2B30C4EE2A9854C93E0E484D4363E3614E5BE829FAEAE51935386D20DBFC00B42952 +7F56FB045EC4D97B3D649415045337AF44BCF4AD9B9F9BF3EA72151DB616FF8F6B13EF66516D9640 +67460FF123C7EA525A97F1D04BDE9D3D495602620659F6E5DCF1AFC5360D1C371BDF9984C4A7B462 +180A3CAA7098E0FB0BDCE694806BA466883BD28D77DB4CFB6635BB7DB45B4D83AAD4260A4CA0D411 +0E251AE7476A95327BD6AC1AC88F85CCB705FBD09993B9E2990D51C37F1110F78B887C54E4EFDA80 +4ADAE5D81477913B6938FE1B39913C6582021A1ACA834500D9D75C9942CE2375D0A2A73805751EC0 +970D6FA62D4354337A43D85DEA6C6F3334F40221FC473DD79344D028FAC689645963B371A55CDA92 +F6BC989F4F1880AC11D8A056DF7A0EE60471800126526488B95B785698D2AC488CC027D31190ECE2 +54F80757DC9B4FF18171409C457F5FC58DD1D43E8D1EE08A6AA2F374B7C245B7A21287DC333BCB1E +EB498A2BD723EE6BB30B76E574773F70A61F1E163A25941531C049ADEDDB59AE75B7A6725D997752 +10ED356DD93D0F0AD7EE6495F07869C570125125BC84946F6AA1152CA18FCAD3B68004A7F7AFC6E0 +9EE6E1B365A4DA15DA95AB980A566DEC7394F385FE041B3B7F46D62424F4726B55DCB4BD3625CA68 +1221CE01DAE78E24F8045EF21F85C13D27D1F0F1B589F936F62F699B93EF05930C6F5730B0AFDB7A +7A829F2ECBF0DD196ED66C7E4E4E7E05B565BB98385F017D01B1305856DB747C3634BF05DAB08836 +029674ED3A4053CC3DC696F758F86970F94034B4F0DFEAA4DBDE449800DB040B55E7FC83E6B5584F +A00F7C70ED18524037CCB48A22A5A69506A825DED81D98FE93177DEEFD42B485013D4C4184CD690D +714530B56E355FB01BC92DD90A7AE8095748A872F79121A28E965E6149F37C9D5F4DF64574F605E1 +B929174AE0CF533F77EBA0E10862BBAC46BEBF583B951BD0BFC5128B08CD998DE897033E7CA8B198 +625D865701394543845CDB7A45BF82DD8B6319658E28B34FD319E38E650869742BD2672623ED9810 +8DF14CE5C911AE0AF2035B4F9CC0D1D84176CF3AEBC4834E8BBF07B7720521C4E6C081A962FE29E0 +700C4F4ECFE92C39BEDD64C3DDF52959A4102CC242C35F56139643F22613D675F970CFDF8C6A57BE +9D168C9CDF93915F66A5CB37DDB9B8B729F8131391F9D6EADC8BDD5C13D22A0EF967622F3F7C08DC +C422E3D41B3BDA6B4520C98FD08262303E042DF37B9911C1447F3DC8A854973323E1C380A02DACDF +8A53A0D1EDE9BF34A50E8B76C5AD5B15F8E25B2783BCF4B1247309E9B70CC951CF3D98C1A25E9CB7 +11235352F3BA91FABA828F2D7D91F0FFC50852860C531C20A5FAAFBCE1197CA367F0F84DEB86A8FF +A9FF4C252EB2633AA2BDAB30F2094721F482CF926DA3299452177B79251B311AA60D4CC82F5A9F50 +E942703877AF1C10CD92DCFD16CF19BC7314FDA5A85284BDE964DE2BEE782F4D52D17FD2084E0A95 +59EBD5AADCC74A6DE64C1F799385F5EC2E2F5F869F78F7289A26B03A9FD906934C3A6BA4A7B36E7C +3B77A7581BE9CD62B49C34572A870053CBA85DCDB9FDDE407B29CB3D02AD1C752B54DBB995DF0F8F +CB117CF8500B97849275A4625EF9937AFD7C8751B9B021E778F7DE9A9B191BFC470823FB8EA919BA +DB8B68755DD83C6905B118FA18FAAE216E2494FDEE9C1125C3941092C592DEC7A5B0C763E7E0D3CF +DA02AF9FFCD234A01D75C661603820C37E9A681356A6DB424F5F991FACCFF78EAE3518C0747C35E0 +8EDEA2E108CBBFFA0B2D3BFD612B5743AC95CC4A0580A6739BE4EDE6CB1E8B2F4CB5C6FA4572329A +06080E0085748067462F3EAEBCAD734DDA18BF206EAEFE9F882807694B80A63AF2F40625E98DF55F +BE08AEEEC2C1BFBC16F1BB4D391B17122EFB5FB539005A613EF5C9F154BD50F05862F813F2083CEA +149FEDC651191259BA4FAA658A42AF151B03A7B553AA79726A44AF9175A724E0D65CE84F41F3B7B0 +E0B684656EA56B4E7E654946AEFABDABCC4F3876B3C3A67373F4133FA8498DCFEBDC27476FBB28C4 +003FBFB063267FEAB2B2BB8DC647345803B2723DBA8905AB0741137C43768B434CE118954AE55DD6 +61AAA1BB60B87ADE97D4197211A0C70CDD7855783B7C570FD742AE5A5940A3D90F9EFF687C4F1E4A +D3C4F5C3B9FF20B61C0076B9FF060EB99860F31C41B5AEC0D2C4DE626C6059678DFA35BAC5F2F171 +A3BD00065AB2007EABA499E4E7F30EB994F8BA757FF9BB0C9A2E3E384BC1DD4B62036E2D70819AD0 +A535816761800CFEA50681AFBF0057E69A0CDBB4BAAFB8B15F7727BE1169BDD4FAF692F9CEC12893 +E4E4425DE4CB20F330951EB5A7FBB0FC10DE78A7F9B0EF6FA629CA13E8C2F616A1BD42B1E26E2A0B +533DEA701AB7BA93526E2439755FB9BD5CB076C3309E73807A5935AF6CDBBDABD5DD7B036324130B +8BC40163FA0999364553CFBE351509F8A5167F205E228ECD66EC9375D0E0713563AE75B6C23423AE +63EB67167F2F4512BEFFE96B90913E6920F1F8B7139F1CAC9E38482B6CD26576776A01103FDEB164 +A176023370D08906E2EF27E1E8B6F6C27EC67A86EA36A6C82B681385F3A60AD53A8512E0880D7ACB +5567F2654731CCC1796C47E18DD6CCE30F65E27DDC5A3E56BFA0460DFC3F9FF1E98B7BDA9DDCC615 +718D7C6CD8DC1270E70FDD4973B287396E2B85ADFCC83C71DBEBB97346E858CFDA78713C0EDEFEF6 +B84984D719C4729C0A3F2A7422DFFBB2AA5FE61891D3D16CDC1BA4A84E7A74B0076FEBE0C2C74F4B +B9314E5246D7E67DE23466D47C8AA93AC4859B347D8CE31FCFB4D41137B51C41BF19D626A59D0999 +FF2A4FA5FE6FA60E029494EF99C8B00700E6E752F4F9ED52F2AF5845563ED8AA5D4E19F82DC0F87E +255ADA53AC62E3D7BC788EAA135431DFF49F2D3ACB61798353B27409D149FD635690F8AD324804DE +A99D12B02F15D9C6DAA01BE2C1512BB8DBE86EB31D7034866C10558C008D69DAD8830745F2BEFC2F +FCD957D0FEC30BFEC54F3C440F3A99BFDD7C6D0D657402A064F2656694E5F5A5524CF4A7A2AD4625 +5DE9D2E9916DB9DC2C39986A221C31F89A1884ADBF7DD62D4EBD47957E7A359F2ACFD38E073E8502 +5F907941ED233EE3582AA955CEF67A8ECE6D8B301EF37B7D40ED84FA9DD604C74C8E870F9C26A2D4 +DEC8F03563D29E1DFB974CA191D4696D877A468082951B02A88884B9B760961D9C37154F32D54512 +4F0E4357B68547CAE9CDB571089752D7881613E7FD8DAA8CFB98CA9E930B48B78AE13523E43A3568 +7B42DD2F0A99034ECA1DD782DA692EFF6AC99D6734DF1AED3616B198E6C242EA7A9954B7337ABA3D +13EBF06B95E16F19047AB0EDBAB6A8928D81003E593C1F134B0E2B8C250EA251B59CD04905F57016 +1662514225C393C42BCC172DD23D8871908522CFA5CE113EC05F39E4583EBDEB5DA425E4627A4A2B +D5C511F9C9C155BC81D0EFAFB0D0F2E96BD49A5C942933336EDF9AE0CDCBB159761DFC50F6180FB5 +024D2E5C2A20233AFF282FD3B1AAE9B65D2989BB8176AA8C7A1F58E43A9AF3A6D0168CADB6930706 +C4F974282D4A23F71B0A41C75086DC1C45CB98ED68ED0E4FC62807EDEF13C6C85741B11FA957D791 +D92B750F3B7BDFCA7E148149E55EDED66700483C4D5BFC3973580F7199FD99CE6B358B508FFF5DF1 +78A5E495977D851B0B06DC7F6B38388D5C94BC8934584D8EE2F4E0CCD3332A737BC066F042B14931 +57BE93622E346FC6B293B8DA0D3EED02508AD2183454FD4D5D21235268834B257EA8B06117F67589 +3E0505E64709FDE03F2D5C82B163C29629EEBF5D408547AC363758D8D134AD7B9A55AD9C7D90B67E +6DF3AAE6867759D2A75993265118BF6C5A261C6D80EF039A0163BCF8E6B40E643D1BF87BD2592BFD +6A41EFDF9CFC2E6DDCA83FEC28B5EEEA2E363C20AFB4A13B5EEB2CA3BAEB2CA6F4824AF1A59E4CBD +884CA4C62852A095E4B13CD018669AF3898DFC4840C874B97408C811E431163887E5BB8CEAC12FA4 +A1EC50E8305137437441AE0FDF8FA4DFFFC77C05FCCC41696E40889A02DC4B1267D5B892647D6AFB +BA549F737543652D3C3A051E36BDB692DD7EA0D8964EEC27BCAE8EF5FA853C54D67A8ABEF7B01FB5 +AA473DF48CFBD3D697C9B55C852773A35B9A7C97908DB701AB66DCFB274A824B60E275E1CB341147 +36B26E50EFB1DF01F01688E09E096533E95B3AF5D09D7823DED38487C58B4F10D6AC76EB48731CED +78AB098C452AC83CCEDFE4E8E4AEB4A93A715306A096F262BFDE5036F38A3B733B1A0134904A3EE0 +8A9F7C2723FB5D0535C5B57CB80C29E292A49AF936DAC66CDE5C01640490109E679FBDC13F892438 +D70CAFB12909FD2ABFEAB23ABF6D129F5628B36FA00548ACCC39C8312030DBB87364DA44FACF3818 +D4C8ACFE3302B1487D5CFED16E17B05CE9889219C13C9DEA28C9BAE5D788578C309CB7781244E30B +7DFFFAF5A9F594B8781F849EB20B1F3A346C2D697CFFEA6AB4134DD46C86BD0F89AB3EE9DBB2F87E +988D906C21A43E5ADE58BFE82D4D4009A39EA3D1E779FC374FF21B86BE48AA4A33186DFA0F97BBB3 +218CE963643EF2A35788D613DFF934139B3EEA36377E67A713D20BD3DF04720AB49834E3FCD78908 +1FB726CF642A5B87D5D23609661F7D694EA0256F3EA0DBAB7C2CF79CF89CA0FC9D25281EE0FC16B0 +D089DC5B156462343A375F1EA2682A53F70A1F239D58080F4F884DBFFA68CC6D63365A8CC308DC5C +BC2387BF3D3107C95FF4DDC3A902B31C3F414355748B10518EBE00C92682CFA413FD071A16B8D129 +4021B0ECC5025E33F6116C89C7B983C6BFC31C5C8D7FB5E5E81D3AC500123CC05B3C8DE01357E192 +0DCFD172EB4B488CEB9E1ED5FA1D235C96FAD22B319239FDBA08ECA2C5C1192B4D7A797ECE135228 +6BBF1E59AB3B54B8886E67A82AD971DFD1EB21CC5E3512CA922F9B870A48E6DC94F94181E422D274 +2D3A14FCB3939FC8C1D62CAF79033D6EF4DCC93751BDABE588BF5D97B52AFC5084C5BC17246FF977 +7AA4D738BB9B15E534ABFD68848B879A9840EEF4774734F0BACED5E7B6177DFD430E0497E36D1077 +7654F351348BCEAAA18C3B362B2791A006782C25C9D544CF1594EA623BB4C782C6AABCA95F9CBB1A +8C86318834E1430376406D2B6CD5AB09644361B83AFF66C96CF549C2D309F7439254C6C3A5B210E8 +23F83647FF420BE66901C317349C1B305014EE7E9F90DDA917E3F853F1A8AF3DF1528A81C50B76A0 +F02E933229C2743BFA639003025697612BFD8575DCEA0BA5FFF805EEB4D9FBFA8D2014BC239E9D5C +4C87E36D1C83E010B92F43C06733976BC84AAF1C05C0A0CF45CA7746ED7E1DF5A12F2401C0FFBEAB +EFA199A7299E4BE5089C2CD83E7838F163F6284FC299B213513F803E93ABD8D759595DBF513D68BF +96031B9FB95A945B7C9153B0B315436C850FA5F1415AA2C9565F6FA39E9F5C5FB265CEEEA8C98E4D +00A72CE7F9F6677DCA7E58C1A8C111A9C6C44781867AA5FC71F36486AEE73FB81C03BF4EC728E43D +75564244AACA3D66B6D36DFD38332AD05F150D4972FD475FD087E13C9312D5A17A83411B45740153 +81CB568CF85BD66428FF9EA2C07E7BF8D0AF4469AA367DAC0230650036240634AB81766E832EBA8A +2D8BFA402DA804D264757E74B3465EE21A1EA1C92929444DAB2EC83050AD169F257B77D3F4B9BA61 +B11361F5DA6DD2DD933E101B64F9DC82945A2D421807F09F3E587D4B13BE0FDD6D7133CE890C3AC7 +1D0880418880362E27635986795E2E8426A0A7D7E8E5C41317209D957B53B6CB9E4EE7C3EEAF3315 +E006B7FD90E7A58FAE5289AE513D7751201459BE029563B58D967AD24E90DE5E96357D37E86CAEB1 +6059CD8593F92617AF636C7D32E2B074A40B6A1C40828313C8DF1BCBB002DF276D424519EEE2F234 +FF9B9B27126996834BEFB6E05A7BFE958B4AFC810E8B77F0EDDCD43E81549154F81E282276A7133C +23650ACB159EFEA53ABABAE1C1CCEF5642898A5605A285205DB40DF6C768029D8CAF85C520AA66CE +5BF1E0A0520CC94917FCC3118953403A1B16312096DBD56883F03E78A14315A5F97345E13C363C06 +3A01D49492349D60A9114A77BB9FD48FC0C3AC76D190204DC69C3ED4A265B8148F2C5F2E147A504E +4F373637C065FB894446031F78C4BDEA68088681E0C5099CAB1D13833FA87AB96E511013A9B3D806 +E71EF6E0A1442C91FC2A1795A13145ACBBF5D18880695EF11832ACD328BDE6E0A7308B12759D12C8 +6B558CDA038590787704BC1BF49EE8C788C41594332624D56082ED8627EC110233CA328D2A0BEDAE +3511719EDAA726F338D324D1577593948A8B9F0300F27FD4420638C6972EEE2D6248B87643275DB7 +69F72E8F86803184035F6A539A7CDF43A79886ECF110ECD7053FC04EB5E51B3C7625B3BF95C0F5EC +044FB7226281BC723988AC2498ACC0489DF0BFD1DA82D04FE3ACF6B63EB269BA9489F8D5D07DA9A3 +AD04BA924B99B9C1EA64AFE7BB2886513EA6730462D4FB5DD82659C7DB7687F4CD8E006581A15EA7 +715E274C9B89F66F1ACE9C2AE7698FCF7479A04F2208DEBA6DE801A6D184A8A9AF6BC1B0E37CEE3B +323DC4EF93EAA8219F946DB9F4D9C133C6CE0FCC6884F9C2F3A816C4ABEF44DD6256E7BC4574600E +1D825080660BF6858B415648258399839118F3C11410C1C29B3C208A3B54AD5D7484708DCFBCA04C +849F2AAB79E4D96328D990C63DE05DC8E804DDAE255F94FC3D56270BAFF6F86190796F91BFA018C4 +FE4FAEC3F1ABE8ADE43D0DA18E710BC1F419F77DEBAACD3BE011BB93E111B18CCEDA8EB0352934F9 +690F3E73D71655191F150BC3788677D1FE46070FDB354BCCAC8C179009553A7D67C87518131A4D8B +4FFF85FB9485C9F30F4CD31EECBC4A44CC267F6C57AA05A11C6FCC09B5CCC83F189F6A32F8EA56F2 +2D20DA4D4008F08EFB1487675CCAE22BC9494441682F4E46839F0F4D2D16AD58AD0886C60C925DB9 +C7D9AA1A7FF41C94B6289E1B72382789006F40B99B78B05ED1FB1F715CE4C0A1078AAC02EFBE6306 +F53F5F7E73DAD249995DAEBF17E5F55082CC6885A54F93F1A935E0389FE54E8B1B6C5ED19D483620 +A697873D5F18CE9A48E3C2C1C871FD4739A78782D8112602DDF8D4FC497C459067A6B118AA998740 +6C8DE97C2F09CB9D388D341EAEC0A5BB4BBBED92BE59B273C77F3D6965418669BFCE0C43D5C86275 +D8E658BE1893DA8E698DC858CA459711969B2CBF4CE294071EA572496575CC35CAF57ED49C2FF1AC +CC21E19D189B7C2A1ABB1AEAD7185675413C224CC4C0E1AF4EB76BA9F44148A95D8609838B967784 +2391DECB30BB0FEB92CC890F224FD2E9E9D34466D6091443CB69E845D4419F9A04664706FC8D2D15 +9002422367F39CA1B1CB1A6C32A65F46230CA2376C3E5125CDFD367514E087E59873EEE569B7F376 +227DB126060F063662F118C7BD01946BD04172147B601BABA952A01E0AD31AB1147D48FC2C3F52BB +75D9618E6F03F1F1EA393AF0E8474025F451616C4ED236ED831E14F40BEF5B86806B73ED64AFE7B1 +2A3C1F5036BB9AE79862EDAF13BDFFF06C94939AFDA1A749364BEE73449520111CF56181527F9568 +F3189652FA7FFDF4BA1086DC5992C6E0282B6F88D7CB73F485F8A27A77453C151B0BA40E294653A9 +73298EBECE8132B440ED4B4437283356B79CADA8198512A45044A7AF04CD02CD4DF7F47D5D1E7FE9 +52C346B01D03D1F69904D1AB5E8C433B0615E88CF4D01B3C96361F82B5CF7CB4D92FF3971E44F0C3 +317D3C5B0BC8DF4BCD4DC63474D0E0B5BEFF3177E2722D4AD4AC4B4AB6269EE948BBAB6019ECF2F5 +846A3D215F6C0D999D489215D4328875DE21F2CA243CF184280B229ECA4B8A9C5290973503AE5883 +03C67DDECC577F12B41F0D4DA266772867F9D93E1863BF76C6AEA5DE3FC6567EECE93D96F717E39E +DF536ABDFEC14DF6748DC90A2CDF6066246DF69D2745D2944123AB3A6ADCFBE7C74EA8E8D712AE86 +F76B3059178E78FB2FBE8D1F25831C70F58FB6B5922B371A27501E7463E01C844A2226CBA263B570 +4E5C4D3E50FE31435437E1ED39E6E3BF47B4E2C4588274F044B3294E7B2BFE302E76EFA3CB74108D +A6CFBBBF383F5C456128ADB5E667E1F7ECB4C3E00AB8937769E5A2830520E9FC0A1DA1662F881ECC +DD7967647B5841D8FDE1DF7C9F5475523F236005EEC0DF307BFCEF379355C30A83EDC96D6086E224 +388DC7B5E951B819347AC5A1F9FBE7EF1907726EE7E972AA1DDECBF7F72658C20FDE99FEE686D7E5 +01A7759DEF55169938F34978A6BD4DB49E494883F67E868A9AB177FA8E6F81157A95A03B4D9DB572 +EC1CFF33B450BC13E00830BBA20AFF928CCE04B4F79F3795DB54A4A8B5A2F3CB323194990050CBBB +C7CD32103E0911160FF4DFF135A77DD0CC15867B994CC88E1EC10E3A097D329DBB90FBB62981FF61 +C2521F9AB4B9393C6764E5B4361D0FBB1938456CE437142F0AAD9341588BD15EA0F6EDDACF12A62E +C025F3294AB1AC45719C5EFEE94067390D579AED4D1D36041D358CF1A24446176DCC808CE2D6CE02 +7A2C2F6E517A5ADAC722EE94A1710BF61254DB4693B30225C12B9C4F856E1D24075327017D6B288B +B52EDE713B3710778A565EFF6C89656BE3C5F590F6ECA600390C1BFDE9B0EEBC2E4FBB9E0E2F405B +39738F7969F64E8228494B298C3FFF4C7DED00B0EEC336B7EFEAA892C4A80CE9ABAAB4318FB34348 +93AAE6A90FF00B892D1DEE254DACDB268A6308E91FB628A98989956958C9634896B878CF93F4E0AB +A0170C1B7BE2A0C4A0D514D7BBBC4CA114349D4D4985E96DB7E2ABB752C7828A9E36D9B0B4551DCD +D878C06C3C68D2C214EC8121F6675D8D03545606B582B09B76B6D8608DFCED5C4A721F7008FC2014 +1D877353E8BA5DEB1CB61F7C956D4A9F8CFFC8C4FF81B2660AE4BE45F7A63141BAEDEA05C43CAC2F +A04163ACECAEF90F61E0473E5CBF1F1994076B6A72CB5C33B17DD57E2632F7C6DEF7837F8A939055 +CF357795865D86C7745DE54C6079C791850B20C0C7349472FB6018521DD5924ED1505A1B8C8F9CF4 +C892CB40795C4ADBA3CC11C8A52A1DCFA8FD334D7F3C344CB4057E80A4B66AF6A97799F8DE817CDD +0202870BBF42E76C9BABD2D9B66D10F1A68388AF1A511887FFC50EC7D07581D4C4FC3F6C4F7EFCA9 +4799D74132B5EA25DF0C9557902C7EF1E04E612D9E40DBA459513E584C3A3EE5614ACEBA165E07A7 +CA3394969C2CF1FFF28B1C7DA85451270DF0FB71DE22A03FC2F17531FD59B12B55DEA7F5F56B0DF9 +34C96E26124342571BD04FAE6A12C6EB0E21F06275605FABE91C6EDCF55B298BC4CC52891DD90360 +AA5FDC150004BAE65225FFC42D13026F9C6FE343D7CE2F52229B4846F6E23BB2BBDB6DCF60F07A74 +8F19F74A1168DC5C67BEF840A3C68CA7A4D8CE7F94610F4CEE989459D0CDF1B194C63A2B82479746 +03A89150C4C6AC67AE3A1341F9516887C6BA254F81C5B552C527765A52ED5C4FC45D575F606E465F +7C2EDD2F5927319BE737D48099C333BBA84486F5F8CC0B32052DB6E57DC55A68019788DEABF8D649 +A531C1880D07E425D55D4DD4F3966B2FBD2A0B55E5C429051DCF0E3B9CB1DE6A5B3DA05DDBBDD3CC +1C81877AED2BD272FC7A10707C07DAA54FD6CD37E15C247E3100A1A0C527727B73D45C8E02798C44 +1864A2D1D30FAF94121F0CADA24753221C85DD5B43E5F00FCAF73A1E531D9453307581C6FA28BB5C +54D93F149D3871D2E017E6E7FFF7B0BEC71B83AD12AF353CD13311D3A6F16C51938986C9B6A24EAA +06B8BE6DF27A5090B3B120D1D1E064C6C1745536C6B0AE5C899C3DCA91BC38B7E900D9614F291B9D +BAA85AFBBBB57D58C3E1FA8713CCA1BF4EED469773EB4B9605125C08A8F7E998E37BD893F3533232 +ECDF47D26E8B2A0437220FAA760DC8E90FCBEF59AF6C1C55FE1A28A4C98E2A67DD5A7E55BD4FE272 +15533A56561F0D80989BFE15B321976CDEF26FD6530EF7A368A7239CC55D7AE2B8F0E980DF63CFE6 +F562F3ADD0AB906B60682BA447CE4A86E6D5538E13C6847791D8A16F5BA29E5840847A7E33AFF57E +BBEC1A5B329A461FD0E858DE5163A2120BA12839C3A216C44F364452A2D6401EB549791012C4B65F +4FEECA2B73B2D88CA49B44493802B01A23321470A2593A8F8ADE3F88D87247851561372137E11D10 +11A733C671C71D33EC939B05060C73697EE577A8F2BAE08309585887E5F314BCC642BA2715B51E0B +4D093F6B11CD37BD9728EE90A0C92D15BD1105637052F89B417F6F36340588601C9C2BE9526D01B2 +E88EEAFA300E38B0EB5E2B54341533B31DA1193588974DC054FFBDF374960D28F0C8C1AB8505CF5C +64988DB86E17213EF0D9D6D52ADB1BCEBCD02B5E16F0866D21D7C0FE108D551E695CEAFBB83AADC7 +362727C47BF24C482EAD6F122F1F35923DE5D6A248BB36433D044F73C944E6CE4D9FBDF0417ED53C +8F56B6D389519E7A539D6BE9444A4747957FEABCFFDCA5FA54BFF46F637B3A3299988929CDE008E9 +E3C4CCA97054A822C4AAD01ECF9861AF6238E6643358B0EA141B0E161C6ABCB45F38740B344D4112 +9D4E898DAEA8F2065263D68D97966FA24BF88E61BD86CF2C7BFD1F058FCF04AEB88F3A30C9D446C6 +3611112CCF30F163C103D6C7C5AF946ADD33B50A58FCD8EC612268F7E119BDF387104F22E4C2CAE2 +DAC407F206186F12D93BA87711FB05E6E96F17DE333305196FA7C33DC06255828744B590FA0E67CC +E4B0375276FC7AB9B324978B15FC228DAC17F955C7B3D441C540192145FBD002EE20FBDF6F397F95 +336C0A056609E28430E123A432EE91594E20A8D9E5774FE8768C84CC040A706D8D3590BBC9AFE373 +4A5536162846B6B7BB7B248924F1DAFD768B9546BD2A2CFA1203D6A3FF45C8EBDE2355F01744484C +DA9FC337CF7D9D54106407F54FEEA81B77BFEBEE088F344CA2E537644B615D8D6B6A79D03F6CBD9B +573FBA87EB00410B5251A29007CFB60E711F642C19847F58333B48F66B6758E4ABE524A4671B0635 +C491341B3CDEE650CB9F774A5B6FB92AA70FEC7D9A057084214D81EA5A36A7F8EBE9922A70F2B102 +121736E0BFB178A08ADC0A58E2A9DB347FB9B0BE707C038CC19A5519C3FA9AAEF660653086191E59 +320D0E696218CE1E8EA7DB5FB3318EEC98C130F3F1C45D0D2401223421FF99BD6E1546873C6F12C6 +F45D2E1C3EE41634F5A415FC8338A18EF2299EFCECEE00F9025E79610168548BF2A52623A479EEAE +CC55BE5172B0CF07F9B04E2B2AC08A2BC839DFBA9680F2BB2E438519BF3B434C71B4AD9E64262C76 +9F6C1AD174FCFF3F41B5BD7652AB296C543F323F0A16E88C6D1FD3025C33794551DCAED303B57A87 +CD1E7FA46E16ED357CE0FEBAAD2678A4D84E4D6B1635F412465C0F3E7246407BDDF934F0E2E0F5D3 +EFE318D7D63A7B6BD0CD5556B6DA811D521408EF336CCE2D2B6777AA472A9EE0DB9FE0D6914059FE +25EFF5D7E3D2A6EB96B23669118B9EFFEE5B7FB8F1EEEA355998BB48430156F14766FD77605BBF80 +CDEF19879E8F8C07B6998AF145B0AED86FA94A952F2F49D2891D41AB0184EFA8616B139E640A3A9B +D808BDF79E283B0CC4686935D0D96630D590A5F4A7C71C05E110BF3CDB5150D8CAC418AB25419BCF +DD5EF0A2015305561CA26494E267BA89892AD21E0BFF44D48E330694A1CE12183B9A7E4E25D78EA3 +498EDF9A22DD7718ABD06DE2C28D7762F609A1E9A5C40A878FB8AB33A60383152A119B9FA077B109 +90AF19C261CE43AFC116593C30BC27EE4D8CBCA0C0298BB84327FDBDD93F073B1E06F71933C0323A +6E7FD2AABA783E9BC995C4AED621434B82B34424B768B4427EE65228E612581B0B8A7AAC3149788D +FDD106A6AB93E01771802AD93B63EC386B057690E5C34A409421E532C6614EC61A0197C13EAEE438 +35ED9BB38CF811E39CF8F154684E3D8B12E3B673152B82DD9B15A2A68A6163D2CCD72D3117F7C24C +F1575671832ECC4AC6B912882C0231050D60ACDC7D6F36C6BB4E32B6019B32D1DA08C7ACFC1DF451 +3A338FACC16D297C56C6138FB02FC7FB5BF7B9DE4C61B63C6B37B0F9CB42E6FF86693BAE1CEBB60C +9C15CB6222F547E0D0776BC5545A73A2ED3C7799F0CA3C2D5EE17849DFB15C6ECF7C2846AD10870A +00E0810F35A57770EEE9D49D05B54500DF164A02FFC324CCFDB5F828AF7307DDFBBE647E98909C73 +A3CC6BFB360042823C678EE6ACA0D658C12776F2A573656073E4F40505F5CB4A3D340B034B0FBC29 +C3B6B055D23F0BD47E44B441D430B8703883AF8EBA79081D528AF5646340A27291472B8E1F19C8BB +B4AD17C7EA1FCFCC7B52E6EBAE0BC8204AA52C08B3A63B2F07FBFF20092139143A24130191C41D2D +69077D71FC204C5FD41275DCEBEF7E5701BFF6C0A4217F6F60C2E37697C7F1C35D2451B040BA0D28 +0C9D23AAEF592BFFB436165C314C3CB75223D15694B6EF312CBCE8035A1A9172365FCCD119CD5DCC +569B84C6BBC5AC9CAB6942096034523671156FAE2012F6A24A001DCB2F35A8A031A2366CA98F1E52 +944B58FBF1852710CEE0116BD2C7D68DB956B15FB6AAA147155E9F179E67357F231F8252D728AF12 +49DAFCF6AB4EBC8637E1BA10D27555D2FAAC9EEE5E51C8BBC793ECC6011C4FEDDF7116E719147927 +0BC11D5EDD9215A4E8087A6A16A591BF7ADFDB69C4A03361C0DD078017DC5851BBF60E06A86C6C0B +E08087C99F4F9002ED5206534913353AE16C4F358BC1564A442CAE506A107D1FBC0B9ED99AFC6633 +209BEDBBB681CCE475645E92155285C00FD6985216CCE60064946F94778F7AA85ED87F5762C20FE9 +DC007954281BD6FCA8554D2A0CA5B76A3ED42EE5F44F3B276E574F64B20E1AD489753903ECD20B4B +EC88ABD1E1CF5D06AA1815AB771E350EB6D04078EC04616B977CDA8CE88C483DDEE9F28D58366D3C +224C41D19E550B5ECB9775F569C2D391F61C4667A9BD11C69A88473AA7884B823F762195CC403823 +690A32087893C29C63100A5935842F6B612C95EC9B5F07459608786310C8AE65DBBDCFED21B43191 +874E20D08F12E1384DCE1A990CA5F07DE36BD012DC9EC558FE7CC44494F48CA3BCBF1F1F11BB98BA +EBBF8691F8590F84AD849923E656860EFC0EC27496FE6D6C185791E3261027DEEE4C57032547F94E +7D593F7885526AAF054BB850C02E863D831ECFEE61A781B89867139889A362F95F48A837ADB955C9 +939F609D2EBBD56775151D2EA4B09D38FC1D824A952EE7E52849260DE61F07333076CF887A3AC2F4 +CD088C29E47715C5242E2366CB493B2FEB38C19FC159EC50EAE88409CF0A30C0A6823C45D0532C2A +72E45A17916F6CEEB475C4D4A19372AE271326697CCCAFBB43D92C25A052797186FE8314FB41FD94 +2A4B24548866BAC19A83BCD2E6979FB3C7B70B075DCDAB3EF6B6181A00A98AE73B6D968EE2DDEF07 +503A92E4FE1E0B67A90A2F351D600DF960101A15808AA99B3C680A8F50D5CBBC96D98A3A2AD5F14C +43857F4CB9DF9372FE74B5D6CA9CD801667399E778B56EF702A56F0F393137B20BF11BCFF9DFC0F0 +4E67649D07A2A5F4C42C9A929231D5BACFB6B53210E9FE311642D8BAA7358E6A7370B6CE921765B3 +68A354B42C8877E59227146409DA83E407657BEB475329F228CDFDD11BC73123234649AEF0E2E9DA +76C12AF91713828368FB778905A6B7150258695D4D9DF6111E1B28A9462002D7C476FE44A9B13F32 +9FF84930D213787932A1BA01EA608ABE7054FFDAAE2176EB960005E5407D7C1D39AEF8EAD8683A50 +D93398C584312EE4A12C07E9D55AE9981D7EF57D66499CA93EA653EDDA1BECB494662E54CD7ED8A3 +0B2A8522CAB12B2751F7E9B3B66CA0B5C8905E0F3A51F68C96E9C02F10FE515FD6133D3ED298D15E +8B1649F3D13341BDADDA7FCB838720E8D0D9DD92D0A241A0CD8E25D7B313DDAA2F25B543BB0E7965 +402ED0F24AD146E49919ABD9604ECFEA5A7A49E58664DCEFDC893F5D722CF24A44D26369F5D86569 +9632141348086A80AE528EDAFFF9D9986D5665DBCD375DF221D5EF1757D79361E5A5BCBE333B6C9C +1CF46794A7C7C776477023BB298C970F6DFF9AE6C7ADDA8CEBE73B07117BFED6CED2A36548081C79 +BDA9A70C8FC5FA16D0EFD3E9F869DD5DBDF6E70CDA4217935FEFE577B64DA1DDBADB9A092B9D1E3C +E1FD3B6E0117DDED155313A5DB7D8742C3409FC18B9743F09ADEC49BFF2FDBBEF9D5FA7110266287 +7C65E3A1421BA56E258A49D76C436C97AE2116F772CA5EEA5726BF17AD5D5CE37DC5F45235730E90 +A1A1E3087132C820EB9E0E311500F2CA193C72E2ACCB4D77ADFA34E268B014FFAB5DD1BE7187251A +A69B7BB3A517796BACEFB9ACE0114FAAEFB0D9BCA028A52F5037291232A04C2353B9663BFCBEC2D9 +30CB007954B8C6396003214262AA9CA9FA876B4191313FD1831D664863C4A19946AEB8F4E21C468B +9C94B8CAA407A74AF418DFB60D46BEE5029C884950D3BE8A58023DA864E9AB34005337D335B0B35A +91DDEB53A54C3233150E27225B0800C841E489E9EA6C12F5D95112BB723C3E88AAEDA3A942D06242 +2CD80B04DDF024ED79224E166086C5770EB10C389D0BF0327B2F753358D9BD552C04EA52BA154EEE +5A84C51FBBFC79055AD0F1243E489F82FD3D1A3D2CCB24A02DB3F306767564A2451DA405BAFFED98 +20241A82C6F4F6E1D2A36FD954540EF3C091247120F5E1B362D1EBCBF80951A3158B1547E7A0CCFE +40C03F992BAD00496C32BBE9B0A06EE9554EA3FAA247A7399A37D05329CADB006F679B58F2E5969B +13A5516A9F6949505A64B7B3FE5E748B9B9662C05E6C5CB79D64FD72C54F557ADE72880B3F9D8718 +58D5EA3FEF2BD3FCFD670794AEA6144D13782BA89648C98E7CF5160089DB9798E49BE2DEBC2669C1 +0C42AE1AD7C1FF4921EF465300977E057C0AEE61579460F56E51B6EAFC1F7F41D669ADE9610EE777 +055F927444F971121CD76A55A273CEBB481B89D6B78D0066ECE31BE3EEF65CE9FD22D22EFD6E3ED4 +536E6439782AD53550CEF1A903125F228A3E2F1AA3342B0551C59924149358F8E941969125F6E54C +8B2C068F57FE0DF91912CD71BE9492768191F4F6B70BA45C72409D4AC6E9619E8A34398E6A66E984 +55F1793D8A2C0DF30EABE2B19679318AE09014262DFBB9AED5D637653FBA1C10821908ED6E088910 +AA033E2E0798A630966D29F84845A9C937C27A268CAB8BA1AE9B32E12B52EE4B64BE5388AD32035C +7F98904F052B31D7C4819E27BCBC597E8503A5614E0DC2BBE5C51922980E3F492B61BEEB169C3EB6 +AB9F1DA82EA6BCFBCC16199923C8399B0495EE9A9E1749DAD9FB289FF3FCCF34F55DF7D94F91EF31 +B3B4C8074C567C4FAB337A337BCC2F459075244F665B079C159AAB83781E465F5259D41313183EC5 +F5F53BFB4797942F93EAD6CBF9255F9B4DD0748A3BC6BF36ACBE0127AF68EA6566B65430D0595FD2 +A7A2FF74055BF2E70BFFF2B79131F1871CEDEFB495FF914B88770654F9E5556AF535E1B9AF812004 +99288AA39D1388ACCB5B11B13596B550F2746B2A83B076F4F606F7580156E54FBF6976AA4CFC9581 +0A7FAC41A1635FDCFD6F7CD6BF738190D7F9FEAAB8C0B7CE38DD1459E2465C3B75625C8BAFE3B60A +F66ED183965AB9681A8174C44311D8EE36E468A8FFE2035B7C5AB6A372FE37FF627F4697C0D19F7C +8A1E356F829BBF2B8F4A95A49F85CA16ADDCA5C0817D6A4F7C4EAEE908DA13E0C89C9AEFF6D1D7B5 +BCD1BBF672B46C4960720CD1C74E70C78784CBFA8930576AD4067D406A0FCE9248DCE6D610D7EF0E +97F3F9CEA1D08DDA90DFF0C484DD32265FD13A2571513F361DE3061F2EA76886112AC7589B290220 +E34610277CF81535CD628CA688CA812275D7B9909757DD519F1FC89FE4D0AFE5FDB999323A470C6A +A0D9BA9CC92BAC24514E7CB3A3EAD2A70271EA8B02B2DDFEC6CE803F1B761D9BB7099FFCBF918D8E +B602946FB0DC14F0FD1289037B15A4A96A4605BBE53BCEA9112BF9746F3BDDC06CCCD808C62F2B41 +D4F508EEFB03AD22E2E154888DC63C6BEC6A21D1851A5C82397FA49BD163BA8A72527827E4B6F50F +585325219FAA9B3A2CE14A0134C19DAE50373A0F9E6DDC8205467242D11A3989D17730C8169964B9 +CC9549BE20A84FD9137DD9C9F7DEBAC3A41345DFEB0AEDCD7ED408A909000FE8D9CAC85D93052256 +7A2C9312769DC85F902D4A5F5766EA3C561549F1F2B4C5269276946809ECA26B6F51DB4CBDA9E668 +BE1023D8962C0DBCACD5BBC9E5F61C459B825036E1322737C0F196C0DF93DA76011DF2CA06F7B383 +8C672A802EA24A474ACF7A51F2DE867ECAF5674B1053CD5419F5FAC20584A3F7565D7CE584CD395B +1968B622FE3C68DAD2F0E33274DFDF03B5A8EA047B077DA1316DF487C91ECEA84E9B417EA25EC9CF +1F1CEDD7A1C2CB0D51A58BD8F9772FAED8D553141ADAB148AFDA200479CD04C0FFD1478EBC618303 +5437A5BC1AEE3218E9B27D21656EB9F31BD4E7D3186C89DF251207E8B67265585083111BC1AFD4D6 +A2773629147A29CBB4BBC3935B83392487858E0D18FFC96E57C83C4C6744C8E0DBF001DFD64B660C +CC8064907AA4BECC12376A1EB55EDF655CBBB4744C1A6166590B9572073A2AB577EF446B80567241 +389F990AE6C90B286EB48454166BBF264422FF2A387FA0B413F2295B6B188A64927DF702C232CCAE +59A2CCD1D109840A464BAC74A45B865A6667C901D86F771C4A36421308551F532497990BC15DA648 +6E322566C210C517DAADDBC24DACB39CA41611B9F961E4696D1FEC46E71C608DFDEEF19ECDD88724 +24E1BEF7176B0AFA4888BDB4C56C8690AFD03428F32740DFBC4BE22B7583DC47BBA42DF4E83C14AA +29F4E79397BBDCD2EC43338BA1F0D2CA9DA6E64A065FCB0073ACD0D86E46EEBF1454C9C172C6BAA5 +0F37FA19756B05405763387237794E9FF74E6749BF6CE5EE9145413950D342DBE5B0EFDE57163127 +2A8D060E935547E1FB0FB1FD60400FF856D027671CCAECFF7E215BE61E8A77E9BF5C72C2C1E4501A +A11B2F8574BD823574EC9598D579A9C5C525867F4BAAAA78DDA0E5BD7AC832DEA41328A507874A85 +90B7F133E743471D4FA27DFAC39C6F99E233C913183B2A039CBEB5CB3BF8825A92D83D266246D5B3 +CC7B11469E611E260C6ED16D17C9693B13B78E3F0DE2F0ABBE73FD6AE1ED25F57B4894254FFDB332 +0E65ADA53669B95CA28BB4BD166507E9D8F12727E46D2B9593186C090764FE8A95F1594291FD551C +A96E4CBE1D6FD42852B2B65E7F10C4F17707F930DD934E2A2ACBDFA40E04EABC1E54632D67AB7D5C +00DC103A3D11DCF78B6771D98ABD5CCA0B253283C67B0863C80D1A78FE6D5422568207509FDB1946 +92706C7A211B29955F6354092C9732DB2ABA8CDAF407FF25B40BF9D73317D5985E19E6C12B6AB5F5 +DD59328905F822E1EBEB87C4E386EF20CEB7EAF842700F09BDC50D7AE6442D1C93804D56FE194785 +968373A154E1F426BF76692CE3E4474360ACB9FEBD3191B8EE909E7C224EF90EEF36CD660AC9A642 +4DB5BB20B8835D365D35A442ECB2444F30466C3323001A087639B73848E3EE275406CFFB495ECDB4 +52F2A357F45D2D32BB22CAF9F9C2C44741A5341B29BB6C4322287A2A3891E1E853B976E17DD9306F +D98BDE8A0C97ABDCC7C708BD1BF49B524CD0DCECB40DDC557E9C90A1EDDEE57BBABC4C338F08A625 +E03C1C1A20DEA709051EB1A3264D7002D6DD2BB7B622AF93C7DF54F49EFA5FBA58F115A049A91875 +A2476C19F9133D0BE1B07C8102746163CEA0A98F9B62EA59C9CED7AD808DD52DDF8687933AE52A7D +9D7081150A812801D3FA78F90C4933E5DF09C991325324DEC6ED364ADF73DFC7B78953B51FBA2F81 +F264C2A94139289CEBF7ECD6D8FEE9E579BE231FE1ABBCC07B4BF1884CABDCEBFC56610A5E2B7510 +37FE804511FB5443F7B21290E38855F6DE5EE38960E02481DC57F83CFCB87C8FF1CB196CDC19B2B4 +6C7C72B64D7D45C678851ED7E2860DE7DB772BE7B33C84ADF7E6F9B2063D1263CDF55E6220753ACD +67C4701EBDC0799AF47AC58A0CCE796384A50DE6A814DD994B372630E64E5FDFF57393522F689DF7 +81C44599B21AB1C214D4ACF94F07CFEE79C628C85378B96D9007ACD4225E14DAD8C70984DC596DF1 +96BC814E7346217C94F172FA40B3DFC73C30E6E530DDE4A91F6A5166AE19EAA3D2CC3D9D6917BFD3 +1EFAC19C236463AF8D7FB7D9B3A4D6FB6D962C59B296E988561957A3C3D11F33ACF69F5768A2DA30 +462BA9AFE67D3C41A1E8368099361F50D4F6368963A48C75F1590804E7918A02EB9DA5F60F49828C +95AC60E9D86DEB7699C35037669C02D408269D6BF481EC745E4EC630F68FF5168BE5E0BDF875EEC1 +5EB9CAA9CBED1CE2A0EB4CE97C14E10114087DCBE5DE1DB6A070CBC6B68EE141B7D62ED320F7B74C +07D9762E0F5C8AF0779D38B7677ED65B5DD2F83ED06F041B5701228EFEAA0FFC031E44D6D68B274A +45FF9EB62FDA007CDC98B86831E9A668098364B421E1FE2A45D85824612A521777C6177316F0C6DB +D9847A2F61326E74A3114EAC3ABAEE456156DF125E4EDAD6BDB66E7DA6370C90ADC4AF4EC1958170 +C32D5AAF3B45BF749EE1DFAD8A5D0C03973B7C1589EAE4E67F6C87213C721E4A33466F3F54253545 +5014417CFFC9AF461CF695F5E1D7A37E4C7680A5624AA11EEA65C59BB38A96734E4A4A238C636638 +9EB8083D5AD753FA7F74EDF10054315EDD53DE61D48846F70AA9272626E23BA3DAB7CCEE819B0A88 +9CE775AA7F5205C95A19AAF475729D2C0BCAD9AB5459380A5F034C108A64B80621820D779582A198 +14058DFDCC6C2D6461AC6F98C64AABA53A2EF77CEF612EA766FE15498F56F54106D70D0F8DD4134C +8C64B282E79A96F15D4BBE0297885ACF775BA5009006315637D60AD86D25A3CA6AC5BF5B4DB95B1A +672A8C589D89101809D838236560F7690E329784C1D335203BFED3A258CD79467D16B6DA06A282BC +4B9669986CB50C54DADE3D5B20FD5CD9FDDE60AF4B5D8D9C50E77CF50DF3B36F37F381FE7E28A719 +91C8199D18258C09BD0440F5E258A5D2E22C6CFBFDC5F91228992E86532F4C0A50067BCB2D4387B2 +9FEFB8043D7BC44A322FD3629D7D92B6EEBA21B035DAC884110BDC6B22CF59B08C195BBA1C682E5F +8CF32F479937D329FE8EC6E9A3A3AA63AE95EEB766E0EACEEAD4DCF46617888AE687009112732A7A +8FA8419A4652075EDDF83E291613B66BA793D3858CEA093C79B89B0C93E11CAAB86AD1980C401713 +0B6A02545DE484FDA6E4DFBCEBBC848751B2A8821CB3F7AC96D2EA9B83B575AB60CECA41EA567EE0 +8ADAC8F1060255057527485D6B12A26D2E7EF6892865FD65FEF28F8B46249C4F19341089D59DAF82 +582CAD1A3B53C763A0E57134A57C4A24AB4FD358592ADB01BE8F1D9FAE9CA97A3BC3A0E2939913E0 +62960CC1D29ECEF9E61BAC20E92F13C49078E7F1562C9D7C01C2B300ECD6ADCAC9AA0C1F1BA30401 +0573F79D158C66D91A8D987C5239C9D4FEED1DC5C9586C4129C33E7C737C624EE9BD1EDE38E9F72A +78DC19E144B5AABF3B0FB72216815E371A5D6452684965CB7208B87CE5A27E444820E03F80AD2DF4 +E280D25D9BDE41E719BB2C939E25B7965DB162CA8665CBFE65DFDC7992A508D54FE4EE8454C11482 +7186A280D3D9EDF397640A809E3646675AB6621B9CDF42808E2F19859AC975CF70F41D2B3D22F8E0 +C180126F4A12122B207A150BC8E1819962DAEBB821DE59B7EEF8FF9219474597AF859E353B4B713F +5F1350F4049DCE99627492E396038B10A2F8E82AB679F65BE14A166E4502921BA44CCFE04E5DD3FE +B04BA47C3F7A3E7DBF7D55328AD1E96092C73EDB16C3325537295DF768E2633D01304EA0B01379AA +DAFE6CCA2AB3E246768C5AC841A458A2F69141A9A9D716D129B26643F1BBC95D8CD0ED44148F7C6A +F370201E454F4AFF7C11A8080508FEDE242343E7ADDD5A8AAF079C50765A79BD25FBBDC59EDF980F +FE0B60435692C49D20AC22F8BD1FCA3F3D8200CA26D61A30212558B54671E63BC09776A3AE7A906F +CA63281F080A00500C42C02B8F82357A3EBF572CFC4BEBA3FE86537DDDF23A861C240A31778A131E +F638C1DBB7E87A45487C092D23FD9FB2C162C3253636ED263B141178FCA9F2D5BE2D81F99B2872C4 +B3C08749CE18C9C00E75DA6B3E4C6A6DD79EBA731618EF5B5E767BB40429D8FEEC6ABFE9975746FB +3A82A03A07B7D0DA8BAECFD1F72AFE17EB0C0CF100A2D48F4449A42C482BB1D64C19F8FEA73F5BA1 +8FC9BFEE3F640C39C081669A9B0EE3FF6B2BDC0A726301EF0E2259E65BE59275C76E7A3F1C76C3A3 +3AD0E2F79198DCA39C482B4E1196238DBA937692CF319436830BBF441C0A8A04161DC0814B5DF049 +83099439EF421B6D37D351D4A988A8DFE93D796B2E8B2564C602F79E6162F56F933196A0DCE91051 +600BAFFD9D439A91F17C20CD6A9104B553D823A0317E3C8344E9F0B2560AD583BF876AE307892500 +2177AF61BDBD745CEC2B2A7931D27423D2667584AE0D6CAA2B281EB9271ADE86C493791AC9A2265B +8324F764D20DE65F6267439F1574DB02C600E771D3C743D420FA6F8BF01A977B91AF035E0D5412AB +0B85D6ED3ED9D0345ED434F9B95FD911A9D3828CD162686E6ECCD2B6A5B1104F0E6838AAC7FB3FC7 +09F08F8E82B4BFCB55D8984771F9F4D339EE7FF391448C7807A436A78B6A487D3A5A86F314B302FA +C5BE1C1DB9844F975619D615D1C7A20EC2B144797E0648CF5C044C8DD1699EEA3AC9D7E3BAA54A85 +11A932623D5ED3F0A9C3028CA439AC395F58DEAF1C0354A169D9AC7F380900D9828C3ECBB975F6FE +6BD79EA4BEA9B71F3A9B1D2EAF8F1E475B4FB99758192EBCDA21B47EA33F57C58E904DA260C801AE +CF457C84592B81CFA96C10E448D705D24871F3D1AD1FE004406C8070A54FA3747788C5B55E9462E6 +F51378BF3F848360542CAD2D5FF9EDAC84C164DFEC115A2F3D873760EAE58CC8F361B37E6CE076EF +325A1C54DFF84DB95DB7DB5B56C48ED15C5426E6153D8A94B1A35B22EAB1D7B871097DF12C093BA5 +538957AB7D0AB2E39A2D1AECB91F0A693B8F6A00601929B6C55AAE8A227CAB6404FEBCC8BC4EEEE0 +ADD4CFD7DEC225170A0063457918B9875AD7F022A8F9932ADE316E4126EAB75C1D0B2B9F44E85F1C +54755D1301345665EA630C8E885A8D9AB069DEA2CC6D12E4A0BF6E80C3AE12BC7ABE507422A5D92B +65F70B4A472DD945EBB960880EC4C34E4C206C260FDF86A997D7D399A0219A51E6F8BC1189EC0ED0 +B8686F0243BFAFD829979747442AB1DA8282F10F4C37C4E6C88D231460DA3BF34C23A4755E2F8F21 +F6D138C86B6091CB3BCE5ED170242C4CC87C010FE63DA9A2ECBDD85311DA8572AD67E40F744D2EC9 +6607701D9790574F051C859EF29291AFA301C809E0A5513DF9A7FC2DB1776DBD4A2CB622BD17811B +C0E584A8D89D4BEB17C2D0AA8BF31CD7E2B36C0E1888409788E30EAC2E46AFA1CFE457D3A19C28C5 +80669CB82CF36BF40DFAFED78F99A5D5DF189CE293413FFBA9E8D2457986C68483242AD767D5F026 +1CE303153C35D5DEB1789448088B51C48A9B8E903BBC69ED7FB0129CCE33A0337198F11DBA94FDAE +AD74BBC5E519F559094E15B03AF3EFA89DC38B4278C68D2AEEEBE4B28B351D7B72F52D94480D8CBC +06544645F20800B8284A4E7C8FAD59D0AA8EC8EC0551912CBC62694A5DD15C469BBE614FB187C084 +838D676E5A1CC8DFA8676758EB7D53FD2BA8F1C434F4F70AED8F2CE27EED2F6A82E20E30EACF9BF0 +CE28B4BF7D556815A887D777B7D75F60D2C410AA2D378B7BFAA6C3A5BF0A5EF80D3E35A0CF393A5E +BC27A895D712C6F004F1A03F9FCF493973F33E6FB106407DFD992AB0E9E36EF395663A6EEFFD71BB +2DAD9DFE6AA4685F307B8FD69BB2F74E672666414A71FA479B37D3DEF6AEAF80FA14821C65F7ABBE +5CC6DD49002216E23B3D22C282F3DE0077EA81017E79D8C3BDB61EC031CFB13FA981120A6C518F4B +4C39C9DD5AC6EDA29ABA527C9948BC3162EF65E8251C9D4E3494C6A51A41D489F8BC89250128EBD7 +0EFD01B9F03C40B6E0384364F2571F7D969016A959E4243D7AB728F0F030B8E2FB456D8E47A85898 +313B0E91C68C7A8A7226603427AC6DCB5C69924FD026E90CB0EE89AC42D874BB15AFC981079E9DC7 +1271C3E461265AF327D4AAB4AEFAB6A9DC3F84DCE7C7868D4719006344B9055C8BA952A2250B8041 +E5EF519BB788201124BE90AECAC2293A310D9565E1EA4AEB99F7ED7106649169E8EBF3DCB210706A +F2FA65158D3059DFBD7AB5B00DBB06B016F73FEDB3AE1C1CC03BC2D94B143BFBD759338EB181B1F6 +841F7B539D8D126FC5B31B244A3A00ABBB121A98F73314D1F1683F513C3CCC9A0C2180824ACE5D45 +E454062CE1217F7AFBDC60A6022B0134EDA6C5D787DE8A5925BEB374F64D612329B9AC17B6EC03F8 +56CFEE502C1FC3AAC7B6F251E8DE4147DCB1580185C406F38E6B729307EADFF6D7A0E824C9830093 +F0C4FECEEFC7FF839306FEFEB780FFBD8879F22AFDADCA4C0767B8EEAE5DB869D7BD91355F0255E1 +CAC68A81A276967F233624DE0D2082B89E2C2D1DB0F8BE0B2BD7051C4DE9B1564AA6CBA03E50386F +D685943D1EDDF5BE0EB34418C48E5EA645CBA552A0444B254E82E9E84A5B7BD0064D5EA87B1716C1 +4EF7FD14E2187FCE16FF6ECBC0A6588B30B4329626F024BDBA65457028668BC6750A4DD668D017CC +EF35CD9CF45EC73885FC91B8AD78920919B4747A710D25BB850B558C5CCF7170C20012DD0F2874A6 +699DF53EDC6C75716A5B6B06C98F4904BAD22B18C77B3EEF32DE3F45A8AC12383DA16DF6F06CD923 +D344CFC2253ECF97A52F2DC6A22539231E29A324F851265BA82BE566E42167247CEBD914EBE42EAB +E189E74FE1FF17ADA8A5F47DC9866CF86694DEE28EED333B0A8AE380557820E3F1D32B05AE27FFE1 +68267FADD310C4EE550F71704EF71F4C669952F30E485AF37561884066C0E3425C748BBAC77CC7A9 +87EC336B8BEBA44358BEA4ECFEA32494A8006E9852C4BFCB1C0F00ACC247F1F8E3F3B34539C67638 +64527F9F885132DCACBFCB1DC80EE3B2A711145DDA1D3B5F83144E2CD698D43A02B0DEB706C78D75 +A24FA39B9374E1155969FAD7D5FB477F4C3C7440DBFE1BD1DC877360E8690A6D3A64EF0A1313A520 +C49320295302227CE5625E134EA442A537505D206EE46E7E218BDA38F7B6BA124ACA0E949725CEEB +961EF8B0727E3A78287E61E65A84AC14D1014A213259A48F04C634C1F04C73B4110D0DB491250CCD +7DAE323CC8BD29BEF605DD92AA5480009DEAD1256E5C9FFB18362D63B022C2A378D15ADE2C6AE603 +1DF385CF090226F0AF889464584A9EA63B884453DE231058C5661C4F7DCF7505D7B1E48C78AA5799 +8FF39084FFF1CD18CB2989D867F2E085B291FE52367B4E8667E5CDA4F41DDBC7129DA5BDC39549E0 +8B67D9C9E4C332D91FE261728521245CBF51870F24C42F5814F393719A9E126C5693C0E776E4D8F3 +0F0288537B8E9A7C5DA4F931FC83F96A1950B1DF26C0448B646D3BF42C0429E2BD0164703FC71833 +19DDEA75CE65D9DFFE8369787FE664306D843421C2C0C3A5B210E823F8366161A96CA88D95FD6486 +5F9E5B0D53DCEE4A98AC10A8FF1BC34A46DB6016E973C9FA298D3F58228E3D9FCCCF9632556CE2E7 +F9C81E05FDB0FC1071E69A4C158E742D0B8939CA3DE33A4BE8D01D679C81C6BA9CF396A6768CA626 +05D796AA5C323E3A6208F2DA5000FC4462ED81AD0044D9959E40E4764562ADA76E33EC63271966F1 +7A13AD376A54FFA40FA247896A22C1C4A4CE51094CA1892A0B9904EB762839AAE1EDB5A0A5A69343 +CCA45F88F61926B23E6C85688862B1D10F1ED827FCCE2A427E49D960DBEE0D39EDE08D2565C6F5AD +66CA83DAC1467C38F742132F715DA7E9757A5D067FDB4A8859D148473F302BF6ECB9B7EA5C0DF851 +8F58D4D076625B92BC8201028064950D70CD40492DA83B98FFD79BD3DCD9CEE95178B6DEA9AB10B3 +4C61A74B49C3697CF714F56A9B1E366B04500C9A67704AF6F721851EA4566B96E220E7B526873F94 +E6013EA8C489639D291167BCAEFB661C96C27DC7BCD5B317C1F101CD8DF2FB29F6162B5ECE903C0D +5570C8C6325F05218850E4A5B5F94C7CE334BADDFC5DB98EF5333B4A92129F41D046D0F5EB6F342C +F362BC8E45511FCF328E2CE2D4C579516307AEC400BA207071C887F5185AF536A6827307D7698095 +E8A6F8C19812A11C32AD207FBC353172C8DF626C5C05CD24C171B3C8DAB270A74AC48E35F0A8849C +E0934B93D56C93599F5235EC441B5232257C4C6767C7B5A35830010E60E56AFBE9199B42E725A216 +C49D45A3C554767DF192F04D1A192D5CE196831EB7A56201A4A96CD44DD084729774334F698E1902 +614405347A37A4C753C0211D7F24D5265CF033E3C4D30E61886CF781D2AC30385E6021A892AB116D +1F5505635BF5C915D0300ACA8AC32CAA0F3639550C4AEB390DA7B86E53B6BB0E4819626757BA9986 +BCA4B4B18F6BF3706577A3BE146F2585025D5A36EB14E587C5469EEEB622D31FD32EBFA812B9CCDC +CFF7D17D00D9F21BF8871661F20E89A25A4543068EC509D0DF7DEA2A24FF68957943BE0FC7BD319D +92A575E12E6FB8112D72A100FEF3EE85972304BBE07FC1026CCC068E8A414F641116E51F76009C5A +F9E664BCCF93C943CFF2115235BB3290F935AA3FAB4F2E73FCDB9FA5E1866EFE278FFE7EA9DD6F89 +38CD5E5F4F31F6C659AF7EFF5B291AFB86521678B42C99275024EDAD04E5929D201A36F4A6C4DCD7 +2E349D6B9A1F90B123DEDE3DA50EDF929F9285747CD504C8F4A73D522F312D623BD65A35419C0729 +30976129B2D99B32D1DEDD81D64F7879CEDDFF04BBBF1286E07BCE651E2DEC190DFB9D25F3C1EEC8 +D7277752BB7CCC313A0FEEBF0EFFA2A189861AD4EA13AD4FDBD5160DC273F12525815F0D693C1413 +AFE4FBE09BCBD71E16C33E6CF8D18732447FFDCC2E4AF9E270D725C1D262D96E72538CA3309BCAF2 +35A3FAFAB27D265C583E6F6F062B555C97DFD89AC46E6154670ADCA13FD99F7A4B9B8BB224D43764 +8CC9DB76E177F562862C912D8CBC248A4C628FF2D0C9688611CED3DFB89064B988F16655633B7CC7 +AFAE34B2937528EA0814ED245D6B0D2AEF87A5B63E2BD7E4F7D9CFE372C295A0891F97C3855C97EC +C03C7C2A231704BED419612255F8B2C9262D48CB4FB46D63CC9697A210FA9D7BFC3BAB7E46172D3F +52A4AF9C4114BC72A5C7CBA042FA633AFD5E404F29408D4485837E55E6F9702F04C7AB410C351039 +6A78C8672F8EAD53BB9CBCED63FB9E72E7238CC88FB4E7C48C3DE3E4B80E277B952727916A4127A2 +5CC1413C390F4DAFD5253B07BC96DAE8CF4DA08330DEE580CBC1E12B75A661819E96B018D47A8B71 +B6BFAFC5E3CBFE68E5193417B6E730E6A2820838D22049BE6BB64B74AA13779D46519965ED80D5FC +30B0A6F73D26DEBD8150B3D27B3F135F608D59BB1632AD9C2E11177FAF54CBD1C4E58D58C395BAE8 +AD7C7AEEB0E1C3F0C0B7A5E7142E9A1DCCC8B4EF56C319D4A4F750857D2FD180F871772B9CC69FB9 +B222F83C3ACCB66125AF6848B2EFDCE3D2284FA5844641FB32F701FBF32F1D2F2E2233B66E36CCD1 +49FCB3FCDB6EA04367D11624717D73D9128EA7D9AABB8658BE9E9986E532 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark{restore}if + +%%EndFont +%%EndResource +11.52 /Mathematica1 Msf +0.75 10.5 m +(p) N +P +[1 0 0 1 -61.69 -151.566 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 105.842 m +73.497 105.842 L +s +P +p +np 52 98 m +52 113 L +69 113 L +69 98 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 53.44 99.467 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -2.467 m +-2.44 14.533 L +16.56 14.533 L +16.56 -2.467 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(2) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -53.44 -99.467 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +70.6 53.742 m +73.497 53.742 L +s +P +p +np 52 46 m +52 61 L +69 61 L +69 46 L +cp +clip np +p +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 53.44 47.367 ] concat +1 w +[ ] 0 setdash +p +np -2.44 -2.367 m +-2.44 14.633 L +16.56 14.633 L +16.56 -2.367 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +0 10.5 m +(3) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -53.44 -47.367 ] concat +1 w +[ ] 0 setdash +P +P +1 g +[ ] 0 setdash +p +0 setlinecap +78.19 2.952 m +78.19 5.849 L +s +P +p +0 setlinecap +92.764 2.952 m +92.764 4.69 L +s +P +p +0 setlinecap +107.338 2.952 m +107.338 4.69 L +s +P +p +0 setlinecap +121.911 2.952 m +121.911 4.69 L +s +P +p +0 setlinecap +136.485 2.952 m +136.485 4.69 L +s +P +p +0 setlinecap +151.058 2.952 m +151.058 5.849 L +s +P +p +0 setlinecap +165.632 2.952 m +165.632 4.69 L +s +P +p +0 setlinecap +180.205 2.952 m +180.205 4.69 L +s +P +p +0 setlinecap +194.779 2.952 m +194.779 4.69 L +s +P +p +0 setlinecap +209.353 2.952 m +209.353 4.69 L +s +P +p +0 setlinecap +223.926 2.952 m +223.926 5.849 L +s +P +p +0 setlinecap +238.5 2.952 m +238.5 4.69 L +s +P +p +0 setlinecap +253.073 2.952 m +253.073 4.69 L +s +P +p +0 setlinecap +267.647 2.952 m +267.647 4.69 L +s +P +p +0 setlinecap +282.22 2.952 m +282.22 4.69 L +s +P +p +0 setlinecap +296.794 2.952 m +296.794 5.849 L +s +P +p +0 setlinecap +311.367 2.952 m +311.367 4.69 L +s +P +p +0 setlinecap +325.941 2.952 m +325.941 4.69 L +s +P +p +0 setlinecap +340.515 2.952 m +340.515 4.69 L +s +P +p +0 setlinecap +355.088 2.952 m +355.088 4.69 L +s +P +p +0 setlinecap +369.662 2.952 m +369.662 5.849 L +s +P +p +0 setlinecap +384.235 2.952 m +384.235 4.69 L +s +P +p +0 setlinecap +398.809 2.952 m +398.809 4.69 L +s +P +p +0 setlinecap +413.382 2.952 m +413.382 4.69 L +s +P +p +0 setlinecap +427.956 2.952 m +427.956 4.69 L +s +P +p +0 setlinecap +442.53 2.952 m +442.53 5.849 L +s +P +p +0 setlinecap +450.12 210.041 m +447.223 210.041 L +s +P +p +np 451 203 m +451 217 L +459 217 L +459 203 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 203.666 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -1.666 m +-2.28 14.334 L +7.72 14.334 L +7.72 -1.666 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(0) N +P +[1 0 0 1 -452.28 -203.666 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 157.941 m +447.223 157.941 L +s +P +p +np 451 151 m +451 165 L +460 165 L +460 151 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 151.566 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -1.566 m +-2.28 14.434 L +8.72 14.434 L +8.72 -1.566 L +cp +clip np +11.52 /Mathematica1 Msf +1 g +0.75 10.5 m +(p) N +P +[1 0 0 1 -452.28 -151.566 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 105.842 m +447.223 105.842 L +s +P +p +np 451 98 m +451 113 L +468 113 L +468 98 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 99.467 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -2.467 m +-2.28 14.533 L +16.72 14.533 L +16.72 -2.467 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(2) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -452.28 -99.467 ] concat +1 w +[ ] 0 setdash +P +P +[ ] 0 setdash +p +0 setlinecap +450.12 53.742 m +447.223 53.742 L +s +P +p +np 451 46 m +451 61 L +468 61 L +468 46 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 452.28 47.367 ] concat +1 w +[ ] 0 setdash +p +np -2.28 -2.367 m +-2.28 14.633 L +16.72 14.633 L +16.72 -2.367 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +11.52 /Times-Roman-MISO Msf +1 g +0 10.5 m +(3) N +11.52 /Mathematica1 Msf +9 10.5 m +(p) N +P +[1 0 0 1 -452.28 -47.367 ] concat +1 w +[ ] 0 setdash +P +P +p +np 210 257 m +210 272 L +310 272 L +310 257 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 211.235 257.578 ] concat +1 w +[ ] 0 setdash +p +np -2.235 -1.578 m +-2.235 15.422 L +99.765 15.422 L +99.765 -1.578 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +p +0 9 m +(I) N +P +p +3.75 9 m +(m) N +P +p +12.75 9 m +(p) N +P +p +19.5 9 m +(a) N +P +p +24.75 9 m +(c) N +P +p +30 9 m +(t) N +P +p +36.75 9 m +(p) N +P +p +43.5 9 m +(a) N +P +p +48.75 9 m +(r) N +P +p +53.25 9 m +(a) N +P +p +58.5 9 m +(m) N +P +p +67.5 9 m +(e) N +P +p +72.75 9 m +(t) N +P +p +76.5 9 m +(e) N +P +p +81.75 9 m +(r) N +P +86.25 9 m +(,) N +92.25 9 m +(b) N +P +[1 0 0 1 -211.235 -257.578 ] concat +1 w +[ ] 0 setdash +P +P +p +np 34 75 m +34 165 L +49 165 L +49 75 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[0 -1 1 0 35.28 164.105 ] concat +1 w +[ ] 0 setdash +p +np -1.895 -2.28 m +-1.895 14.72 L +90.105 14.72 L +90.105 -2.28 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +p +0 9 m +(S) N +P +p +6 9 m +(p) N +P +p +12.75 9 m +(a) N +P +p +18 9 m +(t) N +P +p +21.75 9 m +(i) N +P +p +24.75 9 m +(a) N +P +p +30 9 m +(l) N +P +p +36 9 m +(r) N +P +p +40.5 9 m +(o) N +P +p +46.5 9 m +(t) N +P +p +50.25 9 m +(a) N +P +p +55.5 9 m +(t) N +P +p +59.25 9 m +(i) N +P +p +62.25 9 m +(o) N +P +p +68.25 9 m +(n) N +P +75 9 m +(,) N +10.08 /Mathematica1 Msf +81.75 9 m +(c) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +10.08 /Times-Roman-MISO Msf +P +[0 1 -1 0 164.105 -35.28 ] concat +1 w +[ ] 0 setdash +P +P +p +np 70 3 m +70 238 L +450 238 L +450 3 L +cp +clip np +p +np 117 136 m +117 152 L +155 152 L +155 136 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 118.485 136.705 ] concat +1 w +[ ] 0 setdash +p +np -2.485 -1.705 m +-2.485 16.295 L +37.515 16.295 L +37.515 -1.705 L +cp +clip np +p +np -0.485 0.295 m +-0.485 13.295 L +35.515 13.295 L +35.515 0.295 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -2.485 -1.705 m +-2.485 15.295 L +37.515 15.295 L +37.515 -1.705 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +%%BeginResource: font Mathematica2 +%%BeginFont: Mathematica2 +%!PS-AdobeFont-1.0: Mathematica2 001.000 +%%CreationDate: 8/28/01 at 12:01 AM +%%VMusage: 1024 29061 +% Mathematica typeface design by Andre Kuzniarek. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00] +% ADL: 800 200 0 +%%EndComments +FontDirectory/Mathematica2 known{/Mathematica2 findfont dup/UniqueID known{dup +/UniqueID get 5095653 eq exch/FontType get 1 eq and}{pop false}ifelse +{save true}{false}ifelse}{false}ifelse +20 dict begin +/FontInfo 16 dict dup begin + /version (001.000) readonly def + /FullName (Mathematica2) readonly def + /FamilyName (Mathematica2) readonly def + /Weight (Medium) readonly def + /ItalicAngle 0 def + /isFixedPitch false def + /UnderlinePosition -133 def + /UnderlineThickness 20 def + /Notice (Mathematica typeface design by Andre Kuzniarek. Copyright \(c\) 1996-2001 Wolfram Research, Inc. [http://www.wolfram.com]. All rights reserved. [Font version 2.00]) readonly def + /em 1000 def + /ascent 800 def + /descent 200 def +end readonly def +/FontName /Mathematica2 def +/Encoding 256 array +dup 0/NUL put +dup 1/Eth put +dup 2/eth put +dup 3/Lslash put +dup 4/lslash put +dup 5/Scaron put +dup 6/scaron put +dup 7/Yacute put +dup 8/yacute put +dup 9/HT put +dup 10/LF put +dup 11/Thorn put +dup 12/thorn put +dup 13/CR put +dup 14/Zcaron put +dup 15/zcaron put +dup 16/DLE put +dup 17/DC1 put +dup 18/DC2 put +dup 19/DC3 put +dup 20/DC4 put +dup 21/onehalf put +dup 22/onequarter put +dup 23/onesuperior put +dup 24/threequarters put +dup 25/threesuperior put +dup 26/twosuperior put +dup 27/brokenbar put +dup 28/minus put +dup 29/multiply put +dup 30/RS put +dup 31/US put +dup 32/Space put +dup 33/Radical1Extens put +dup 34/Radical2 put +dup 35/Radical2Extens put +dup 36/Radical3 put +dup 37/Radical3Extens put +dup 38/Radical4 put +dup 39/Radical4Extens put +dup 40/Radical5 put +dup 41/Radical5VertExtens put +dup 42/Radical5Top put +dup 43/Radical5Extens put +dup 44/FixedFreeRadical1 put +dup 45/FixedFreeRadical2 put +dup 46/FixedFreeRadical3 put +dup 47/FixedFreeRadical4 put +dup 48/TexRad1 put +dup 49/TexRad2 put +dup 50/TexRad3 put +dup 51/TexRad4 put +dup 52/TexRad5 put +dup 53/TexRad5VertExt put +dup 54/TexRad5Top put +dup 55/TexRadExtens put +dup 56/LBrace1 put +dup 57/LBrace2 put +dup 58/LBrace3 put +dup 59/LBrace4 put +dup 60/RBrace1 put +dup 61/RBrace2 put +dup 62/RBrace3 put +dup 63/RBrace4 put +dup 64/LBracket1 put +dup 65/LBracket2 put +dup 66/LBracket3 put +dup 67/LBracket4 put +dup 68/RBracket1 put +dup 69/RBracket2 put +dup 70/RBracket3 put +dup 71/RBracket4 put +dup 72/LParen1 put +dup 73/LParen2 put +dup 74/LParen3 put +dup 75/LParen4 put +dup 76/RParen1 put +dup 77/RParen2 put +dup 78/RParen3 put +dup 79/RParen4 put +dup 80/DblLBracket1 put +dup 81/DblLBracket2 put +dup 82/DblLBracket3 put +dup 83/DblLBracket4 put +dup 84/DblRBracket1 put +dup 85/DblRBracket2 put +dup 86/DblRBracket3 put +dup 87/DblRBracket4 put +dup 88/LAngleBracket1 put +dup 89/LAngleBracket2 put +dup 90/LAngleBracket3 put +dup 91/LAngleBracket4 put +dup 92/RAngleBracket1 put +dup 93/RAngleBracket2 put +dup 94/RAngleBracket3 put +dup 95/RAngleBracket4 put +dup 96/LCeiling1 put +dup 97/LCeiling2 put +dup 98/LCeiling3 put +dup 99/LCeiling4 put +dup 100/LFloor1 put +dup 101/LFloor2 put +dup 102/LFloor3 put +dup 103/LFloor4 put +dup 104/LFlrClngExtens put +dup 105/LParenTop put +dup 106/LParenExtens put +dup 107/LParenBottom put +dup 108/LBraceTop put +dup 109/LBraceMiddle put +dup 110/LBraceBottom put +dup 111/BraceExtens put +dup 112/RCeiling1 put +dup 113/RCeiling2 put +dup 114/RCeiling3 put +dup 115/RCeiling4 put +dup 116/RFloor1 put +dup 117/RFloor2 put +dup 118/RFloor3 put +dup 119/RFloor4 put +dup 120/RFlrClngExtens put +dup 121/RParenTop put +dup 122/RParenExtens put +dup 123/RParenBottom put +dup 124/RBraceTop put +dup 125/RBraceMiddle put +dup 126/RBraceBottom put +dup 127/DEL put +dup 128/LBracketTop put +dup 129/LBracketExtens put +dup 130/LBracketBottom put +dup 131/RBracketTop put +dup 132/RBracketExtens put +dup 133/RBracketBottom put +dup 134/DblLBracketBottom put +dup 135/DblLBracketExtens put +dup 136/DblLBracketTop put +dup 137/DblRBracketBottom put +dup 138/DblRBracketExtens put +dup 139/DblRBracketTop put +dup 140/LeftHook put +dup 141/HookExt put +dup 142/RightHook put +dup 143/Radical1 put +dup 144/Slash1 put +dup 145/Slash2 put +dup 146/Slash3 put +dup 147/Slash4 put +dup 148/BackSlash1 put +dup 149/BackSlash2 put +dup 150/BackSlash3 put +dup 151/BackSlash4 put +dup 152/ContourIntegral put +dup 153/DblContInteg put +dup 154/CntrClckwContInteg put +dup 155/ClckwContInteg put +dup 156/SquareContInteg put +dup 157/UnionPlus put +dup 158/SquareIntersection put +dup 159/SquareUnion put +dup 160/LBracketBar1 put +dup 161/LBracketBar2 put +dup 162/LBracketBar3 put +dup 163/LBracketBar4 put +dup 164/RBracketBar1 put +dup 165/RBracketBar2 put +dup 166/RBracketBar3 put +dup 167/RBracketBar4 put +dup 168/ContourIntegral2 put +dup 169/DblContInteg2 put +dup 170/CntrClckwContInteg2 put +dup 171/ClckwContInteg2 put +dup 172/SquareContInteg2 put +dup 173/UnionPlus2 put +dup 174/SquareIntersection2 put +dup 175/SquareUnion2 put +dup 176/DblLBracketBar1 put +dup 177/DblLBracketBar2 put +dup 178/DblLBracketBar3 put +dup 179/DblLBracketBar4 put +dup 180/DblRBracketBar1 put +dup 181/DblRBracketBar2 put +dup 182/DblRBracketBar3 put +dup 183/DblRBracketBar4 put +dup 184/ContourIntegral3 put +dup 185/DblContInteg3 put +dup 186/CntrClckwContInteg3 put +dup 187/ClckwContInteg3 put +dup 188/SquareContInteg3 put +dup 189/UnionPlus3 put +dup 190/SquareIntersection3 put +dup 191/SquareUnion3 put +dup 192/DblBar1 put +dup 193/DblBar2 put +dup 194/DblBar3 put +dup 195/DblBar4 put +dup 196/BarExt put +dup 197/DblBarExt put +dup 198/OverCircle put +dup 199/Hacek put +dup 200/VertBar1 put +dup 201/VertBar2 put +dup 202/Nbspace put +dup 203/VertBar3 put +dup 204/VertBar4 put +dup 205/FIntegral put +dup 206/FIntegral2 put +dup 207/FIntegral3 put +dup 208/OverDoubleDot put +dup 209/OverTripleDot put +dup 210/OverLVector put +dup 211/OverRVector put +dup 212/OverLRVector put +dup 213/OverLArrow put +dup 214/OverArrowVectExt put +dup 215/OverRArrow put +dup 216/OverLRArrow put +dup 217/Integral put +dup 218/Summation put +dup 219/Product put +dup 220/Intersection put +dup 221/Union put +dup 222/LogicalOr put +dup 223/LogicalAnd put +dup 224/Integral1 put +dup 225/Integral2 put +dup 226/Sum1 put +dup 227/Sum2 put +dup 228/Product1 put +dup 229/Product2 put +dup 230/Union1 put +dup 231/Union2 put +dup 232/Intersect1 put +dup 233/Intersect2 put +dup 234/Or1 put +dup 235/Or2 put +dup 236/And1 put +dup 237/And2 put +dup 238/SmallVee put +dup 239/SmallWedge put +dup 240/DoubleGrave put +dup 241/Breve put +dup 242/DownBreve put +dup 243/OverTilde put +dup 244/Tilde2 put +dup 245/Tilde3 put +dup 246/Tilde4 put +dup 247/BackQuote put +dup 248/DblBackQuote put +dup 249/Quote put +dup 250/DblQuote put +dup 251/VertBar put +dup 252/DblVertBar put +dup 253/VertBarExten put +dup 254/DblVertBarExten put +dup 255/Coproduct put + readonly def +/PaintType 0 def +/FontType 1 def +/StrokeWidth 0 def +/FontMatrix[0.001 0 0 0.001 0 0]readonly def +/UniqueID 5095653 def +/FontBBox{-13 -4075 2499 2436}readonly def +currentdict end +currentfile eexec +D8061D93A8246509E76A3EC656E953B7C22E43117F5A3BC2421790057C314DAE3EFBFF49F45DD7CD +91B890E4155C4895C5126A36B01A58FDB2004471266DA05A0931953736AD8B3DEB3BCB2A24BC816A +C1C90A1577C96B9096D6F51F9E21E625ADF6C3A49867A632A605C117E6325C820121799F412E226B +EFE61F2813676F172CBD7EC10FF1EFBB92DF3A88E9378921BBD00E6024CC08EF057CECD09B824E0A +CCDAA4644296DE34D19D779A21C30666026829D38FB35A2284CAED23C8B913E7B28BB3DA7C8CE390 +4C0BAE30B0287680CCCAB2E6D4CAB2E7D786ABF54068028FD7D94FAC236094761B62B7E76F68D2BE +58C23AF85001950EFC1A9C1BB71520B78DDF6AA0058D25D041E86D01878DF56A5C48D74DCB2BBD68 +D75C94A3CE878484D28049331CE3D4364B40FAA2C754E8F443D244C5BC44B1C7868E36EAF4F7EF1F +6CB81E6CF63FABD65C29A991EB7D724DA06535AE43F3D0E2D04F6113B493C15463B4CEBFB72AB879 +7E4645F9CC0BB17E02A9626BEA4B4259F798B53B18DF2ACCF2B86BF2209CF0265DE0A46869333F98 +CCF70BF2C9239A0ABD2E97923AA5695BAFEA31E27B8F532BAA45F2980A11D069265A5312A260A627 +A765C4A08897E5C500990AE6FDA4CD6D575905468417297380EB6400CB2CF001C4B8EB79811CD8D7 +C173A337922B99DAB1048D5D03C78F78F36FEE31673D8C5FF8AD689A63AEA055CA705DB47D3AF965 +73571985E62F63866018C96EC4CA7E735C9294D8C81C03806D23CB87C0C08F86F5FA68CFC9AE48F6 +958AE016DCE4D60EB64AEAAD59D8A2592BC398BCA479FBC2F0C20C3E7F730481494C88781A6A9E0E +4F47A94619A3841FAC76A4FB252EB6FB43628AE1A4944539B1DFF672940AA5E93FFACFAC04624EF6 +7ED9C691788F0004DB7FFD1C995F2C52C0042F02F5C789F85D9E51716F3B4EDB21D4B9E660E4A892 +B747201EEC6DD6A8881FA3039664061094D1892108A2AD068D7F0251BFA72D874ECB2F42D27CC343 +052156F6A3A66D2CA6DAEF046A433FD54FEB4410315690971D0F43363EC0119B21F4BE3DFDC8C28D +BF5D68F145D3AC932EE62C32DFDEB1C48C2455392193268C893093BF911645986607DD13D8285895 +1854A7FF81FC98ADD44742907818B3C8E3187371BD9FE6EF5B4315E82C359BF2EA91D2894EE7FD9A +734BF2745D7FE8D08D29DA2A03176C992E11F1AADCE219D5E33F325BCFF4521D6D04E61B913B6C41 +740AF8BD9EA83F3AE7C4E5402366A812B1724431EE78D2028317B975E91941B591CC6C97740C453C +C7D7AB3CE97AE2F4DFAB6B9C8810A52A276BEAABD687BDA0971EE623A40B7AD6A8E69ED8BE63959D +3DCF8799E2505AC7F7B0E2FFECB0B08027A6266B7F96311B0AD78B7B6C78392AA062A73FDF179FEC +7F748929D060289F899FE417D4028FF332204FE04146BB130EF05FB57AF4BF9337EF71094DC5922E +3EF2D6A9F8257AF242019C349B65C2A3972ACA842D14EAB6A8D287456C9D7D295198F4AB7632EE43 +7D006B8124A80BBF26C1B902379D7F90080B51F982630E27B6031CE20930C273EA5D7FF0FC7E996E +67B072796CD96A7739444D21FE10DE7B57D143E4155EEE1B89F32EBCBF4C64D6D3FA1C46E06C9E9F +F99BC9BBCC61A400C7DBF812C42AAED863FE9EE3B02E731D1809F3CAB941252DE486BFE641F60F60 +C788F68519A6B04A4D6F50F02F93C6B8774B960C1FE893373D2AC2D865C487CFFE7669E1F1E73630 +7D34C15835F0453C8B0C1AE352CE1F27065F1082E6C84F86330F76B39C246315D6996AB69F81A020 +30D92CCB4C2A5AA4F4B3CAC88B2C8C9C621294A5EAB6AC3778DB99BD2F411735DC1421861417B4FD +399B365AEA45185A3D9D687546E36BB73994FB7FA3EE890AE3734BD9381B0E7AE514E8C517B87268 +7364C38D0A8379039F33336799205F2F54BBF7C2E9B30B27BCFB9FF2CD64F5D700F2455EE66B6252 +6E79ED2B0E5FF9732281CA50D27A93F259B6D4B5C7F856BB7D4F2E0F7741FA2419BBAF86C372E34D +59BC7AABC4CEF4F47EE40E118AB95A4863E16C55824D34002D59844D1F51B5DC6FB6BB5D595C4404 +1E05A84FD453A129279F894D726F6CD53BA3E234518324C5F715DAE6E7B5695326FC0C9B6CA2B53D +B25EC76BE9662388058629E70DC7BD861E188F1FF32A754A455195163CB4754D116D24E6A6300B67 +1862625E260442DEA2505E36D5F7AA4AD1FEB3B42632E382959C7E84569B2A790A0D0A208C2D4816 +AD28046B42C7A4797D424277AD9425C04DB87DCF112AE431CFFF6E4FFA979E947502AE5E1755C112 +0AE2361888B956F3F513A5975680E6E8374D8BF26C32AADC826D729E9026B14A68BC3D065E11C697 +D4D474CF963AFE083DD7D9278A0C27447E25AD70DD40B2EBAB8040164E11CD75AE3125C29806DEF4 +AD1B989F7001E227685DEF6EBE3287DE43BBA5FE2123A0EC835AECF879C13F7CFDC409901F291E89 +06C26E42B189862AFAE029F03D86D35E44E318FE16537E2B73E4F39F1E6945A3A6432438DCB6D2B2 +09814B5228D08165568617C279499ECA1B88C90300F1C88C45D4BE3DC726A59E24B46C5B2FF228C6 +E6645819C6F1D5B05737BE7353F4787EE52A21DC47A44F3C44406E79BBFDDC164682B02F4C39300D +12EF37A58E317FC1B8CE58E04BE666ED5DA75DBF752BEDDA4C7491E4D6922BCCA9CF421CE6751002 +8638EF643119841F423626D6B19A5D2CFB193B093D7646913F64614C098E5F5FF9422EBA75FA9AA8 +4F8ED114AEAB011E6F0727FB96F26BECBBAFE3AA8D0ABC5A8E9906B6CBB9E03F8CC4FCA97C884B83 +7CC33C619CD3195C55633B72D3F2D45561CD226F42B859B8099812D591886FA851107A185169FA7C +944248DE28642FA3043FF3B60236BFD781257C6FE4D56174AD16ABBF9659C05F08673A70496A0787 +C187D4367CB0CF48BD9A4FE0E481273E4909A1092626A13917DCBDE920028B06E094F5B28887B990 +32521E1720B75EB284AA6FFE53FA5CD5B903F951FCF7B33CC981FE7BCC4BDF4907ACC3AA60B69969 +A9AF204C84EC8C0F5DCB8A85E39EA9F2D4B67095A44CA0C8B072D7A61F3015D502B1A0F660C22221 +3231986D5E8C04AECBAFE999D1735A80051C06CA279D0FF6215633FB7A706454DA7236DB17AD72EE +1F6044A26A0EB77AB3BCE823E3F5E0DD31ACB029A1D17665FF16E5D1ACDDFD83CAEE1D48666D7BC6 +DADC34D317C335855D118892CBD32412F5870C3D2E599A46AA997A5E2BBDD3001C2957D81345DBED +583B72C4FB356F0C872A31A35087775EF18601930C0874EEA1ACB3ED3690EF447926439CC383087C +C9D5C6EB21EDF5941CB4E99FDA434A91676D76DC1A1BD801EECA6B0A88370B7005D41A1253CF8217 +1285986DC302B51123DBA9733BDEF0361AE0580FE6FBA5F29CF1438801586559D7436436CFE33E6A +D6EFA850BB8C9382E1A068246F370388186DC278F31F770C2C96881AC6E40823B24D6D536514A2C7 +AF3D159621080442592CAC03D6967BCBDB38FCA1A45F76F1A10027F1DCC076533C6AFC097FBCF0DA +A0078BE0828247F938AF76A87EFC14D9F5444CBCDCE637E2325D86B9D99D1ED70F44194E19F6C7A9 +9E415DC8A6E484DAAE52AAC1307A5353E8F649A35214B3F43DB5F3DB3ED06580A570E60B3E52679F +F90A3B00D4EB4DFBCF0D2F9C5545A6DE10BCC849A0BA9E18F29C6F09ED0F0DD48AD37B7925654433 +A6D02965A3813BA2EAB2E6C2004ADD216DAE99471EE518BD0DA0F534F1512A8E26286B07FEDE71E6 +0A5A057A22AEF095A8B4752F54C04CB8BC170F3D6B725B83A6780176194B21BA906E37B7923E2548 +604F8DB18E0A3E1B5FF04D00898C29C6033DAC54637CF5B068291559D8526D5201F3503FBA4EE12D +D7A6CF6271618F41FE08384521CD771FA80364D747430A071EE3D3ABDB5400DD36A0285430D537FA +F6EF8ACAF85C250D6652F327B2BD3B29E1C64E6E66C788FF1D9C3AC6DD38691CDECD0F3FF4079BAD +A2BC0CBE14AA3FCC38E3F31B3298A6995C87B34A7245ABA2C968F908D8337860507569C370654635 +570615F987531551414B5CCAF7F4D0B38F701619C553E746BD90655294270560046A925A021C98F9 +3EA8FF5B5B8A0D05AD483E6DDC5257635308C6C0FE9182D0E4FB011A00981A7B95DB5BF5A82F8B1E +B68E8822F8B1B7CF01AF11302B44307F3A71D5EB3465F793CAEB1E72D2C63E3D264380A75FF1DDA5 +00B5F82B04179EA9DAC10731FDEDF5913EFDEDDF5799D2A86EF2D16C0B52D99FCEAD392E9226AA6D +3D11D29054130C8602F703CB1EBDAAA089A02C0EBD53343A7B297836CB63E4B2C33C8760ECEB15E5 +6B59D4B296B8B724244D37651C3CB862C7135D62B2692F5A27B9374C5C3C5399E5C4DCCD76572294 +F742B1F545B736BF4C82D8F4E2941CD52C0B944261DD4CCF8A968B646662F3D187557206FF165F3C +0D3D5CA1E428D61D7936E1D00C5377A047EE80E0A5612F7FDEBB8B224270ED23A031A049E8676516 +BF66EBAFCF3F9D4975B0F212FB7A914EE45640972B61AE8E60E602DC7C20758BC07A159B08862F16 +6302D8CBEF03C4B0C73BD8504EB5B14DBC64FBDDC867FE51F76099769A7BD4FA4CF4096EAAAFD55F +9A20F6D4B84D8FD139112A43204026F15F9FF5AB6729537CCDA60369C24D7EFF4B6B971EBF0BD277 +A9AD1BF1066508A0A7DD9A8D45447A39B92D6B0F7DA4BEC2689D25F35C8C490891F053F90DEE5E2D +8C9D7FD1E23D0F1C5F57921BDB13BC9B8C3CED4FC42C4DDBF0706985A0DDABCC683FF5EA8416C225 +ABD219024432E3972C3314C30A672FD21523C83D93B2AC8D1DF023EEB1BD74E825FCD19873E63A45 +F6935E6685CF5EF472191B976F9EED2A4765E1B21B46EE1C4CB90AE89DA48E52BC4EDBAC2C855A67 +CB0BE9160F3126151CD171081192D0D6CB27E4EB2D725F31AE95FB283149F53F22BD8E414354D4BB +56724057601FE4BF34A5B188C00B0E550639CD796CC66EF895AA5315BEAD49B5639EF0878CDF2CA4 +271027678693EA212D0C11A6EA52F748AD0F62A0336BEC8497EE933EEC461E461CCD2F5291B980E2 +8B7D66690B10EEBE22B092179396EEF5D00E42E6CB73BAD4485F2063AEA6B6207E136ABB925332C2 +60F12D8B60B211A9BB15F37F42F53AC2559F5A8397DDD491D314B6DB0B50E41F0AA0A42FFDD5B9F3 +FBD8EFB7E343C06F793DA6BBEE4FAAFB233C31EAA3AD701B1F1C4F2FB05A7647439D19CC856C7D98 +EB3375B3ED2255FA33D9ACB87C1937E0B7F34C9B299C8D2A7F85D41141C598F9505C72B5AC2DE9BD +E24CDAE1DEE416786B93D4EE713E437D2C4A3251A296B785C81A8232F98ADD04B3D2B41394F8BDEA +7B602A555EDBD51A088C2277D8E86B08A0D05CB6142E33E973BB3F2CE841D323ABE6FBBF83B84272 +220F569DE23264AB672C2590D4527A29510E7F289DC2193E66FF23D83703E27E9E81572E7534B1DA +510BB846576E8A39D9CF44483F297C92317ED8E46237B3D73844B3B19D37831B44EC116CBAC3F75B +B67928C4D4E741EC95E96FAD74D852220F4F1A8FDCD273E0F6E77F09EFD5723CCA1398A021FAE947 +9CAC5922DAC8E2F46704BC216C7BCC1575A455CCE2C2A080B9FDCD0758D9D921EEB6DF96C64A31D1 +C9BEA80F48461857ED7DB635A3BABB3BB6155E835E605B09E06A2AAF6BF3EA70B8C0E098CD1A818E +30B9A4AADC284EE2B87E945B717FA73AFF5FB788E51827F6FBE319ADDD059614B493ECCE718789A2 +EB0F117EC811EC38A3F4EDEACA660612BD247425A3FB2E6022CC14FDF69B6660B37FCD4359F1BA54 +D12B1F478D76CF898824762C25A026B01C94752F6F3724C31AE788CFE992D9CA152902EEBC4AD8B7 +A5F0E68A5A0408A2F0BA71CE0D94B4A905B35F2781D7E7A2712DC62E87518BFE72A5BC4A9F36A4B3 +B1494B0C4C14705203762E0CD0B28BE31234449C7655B5D6165D7CC4A16431F7A8ECA58D25711E98 +4FF2CE123C05AF9A65D478B73739715DE62A199D47BAC65785EE1DD25AF91868F91D037C0AD754BA +CE3DC4B67F1FDCA8FD9FA39796EFA9C975DBFAA99DB70624B3563408D0048E3AAC6B4F228DC0AC08 +B9C2B63657EEDB53B46D157426A3B4B4B8CC5B4F30BC24CF9BED442DB51F3C7A0656DFBEFA401E1E +0823065499C69D51C477360FD13ACA8896A8117789C4561F3D85F3A80D18E39F1D6BF76C7876922A +1038ADAFD53F2D092369B356D0CA3FE6A27D7B9BD3985C78424C21E60F6BB46408013DFD7A30D320 +EAD9AC6E5FD36655AC6706666A76F1E426640C4B0BE692E4879991EA9EDF4596C0DDF43D4E370360 +D91E8B2839D368DA2A910AA6092337E2E20DEECF43D583CF164881079ED5A492B5EFCC1CAF91512E +0FEA8140CA3E2553733D6F743728ACAC3E643394015967DAC8839D5A804503A45DBC539FB8656D75 +2F00EECF73E7EC8746CB13F438CAFD554C01150048F758573903B0B3260AEDD78BC2EE87D201E219 +486315A4C01D95DAAB54335A4F2CAFC3F43F12A9574CD2DECCBC1858406C701EE62C281A55B729DC +EBBE74FDFF275A0A7B8B22C7490187D1839F4FF271723C140095F28221C4145C9061F9A5B3EDF8D2 +9E0DA04D9A8AF6ECD42DB897DD5C9318D636FAB698554BD9EF9B0902BFD8C96CB958773A3C4A5FCE +8A334C673206C39277E45AB50DA2661F89D621AF057CF1A7ECDE344DC7658514B4C655937E7BE010 +B0694B069FF64D5582E3A4B7F6AF6C96D056ABB20CC883AB25A9BEABB18A84F0258CA3E4F33FFB77 +9841F5970DB447969FE9C6BFDB066ACBC040648D74F553EE434BADC353450A3792EEF9CFDB2FBCD6 +07153F2EF73C1BCCE3784609F26C80193BAEF766E7CC7C33A4CAB862E6E01FC1CDF11E2FBF25FE1D +308CFF9CD924893861BABF78F762F3CADD3E0BEB38F6157CD08F1B8767606427C4A631AFC9488E6D +4D1A8F4B51ED48582BCD3089BE037ECFF18DF6175EC317EA56D4FDE37288F089532C13F7B3C1EF7D +333E7FAF8B49D95F535F60889CD7245E5CB0BEBFDAE8F7A0AC1AB7DA18F2BC06267B27403F1BAD9F +DF5F13254E96C294C4568EC7154479570E514A55208D19A4538959F7C5B2A0C9CFE4B4A40300F248 +5943C6AAB753F3F0E551727B8DA8E75305A9CE623757B36FB7D34D13CB14EE561C404CDB2D88F375 +2BBFD9FDBCC92CF110D6C35629E3040D995CD25B90BED2CE79BBDC846EAA321B1BC46DFF7845380F +BF08782D6A31EC7D41F251786FDE403A39626D6052D5189CFBB3DCFF09E81C09D0CE7D571F979509 +79B26AA4F6D07F97A33522306AD692D4D960CEF1CEA3D251A841E2A23C7AE3EA44B3F31E79808F22 +B6ED20DEE4186927394624E22D37E873B660BB8DE6FFAE058DD5817A3BBD68963D055D406F253701 +D940B5E58DAB01FDFF1C626D0D35E8E7995D37057DD07F6D4D45F152A141269A7FB81433652209B2 +B23D69BB5D8D367D95D4A94B2C2924FB83F50F651458CABCB65009912F00F485A924A61A0008DB44 +D478CAFDB7D9524204947703B822755A1301FE163A8248C8AED7073362D918D097597B24A3B579DF +FE00F47776428D2B992E1F5FAD6190ADD1C789BB9A913BB1B938EDE357BB436F734477F7BF332E36 +7C380D049AED8687B4C2AB6EB813E719718C9CE62296C24F783B16E9635A7E4402840BD26D3A0DA5 +78050C49E6D8239EBE9E053719BE94CF6D8C7942AE1660F28B21B3C1E6E15459C1FEEA4FAAE64AA7 +165824F7B939E28E51009FB778E370C6001B3F546EBB76454912D730A3F8C7F6F9EC01B9F90A2E5E +58EFF9EA99BE57A0154147C2B7A7C7E493E37C9BD0ECDEAD4AA4DBFF95D7E451246C9D4C30E71F4D +76441A297A1223FD2F8E017B10C108B0F0F67385060E9C18133A6264AB2B445F6DBCE86CA803357D +749244A6FFD8FF8AD37EBAF3787F3702764C5EE2CA7547D7A9FED5AECDD2064F7C767078579DE13C +F135CB05561B15BD9803646611422353774984D386BAD640C5EED157569356A17BB6233EB298960B +8E34209562AE170A08D15F3A29967DE067F6AD183BA1EB49A99F4899031A01410D7311BB9B7A984E +BD6A303D44CF42B40F12769D44160583BCD68C68F823DDC0D73150083404B12AAA68E97206053C6D +23FF0620231D3570A089520E928E980F56A273092DF94EB3A99FBFD877B58860231B2A761DC91A41 +A427778B06F07D4F78924FF986F837C1437B44EAD5E7C56B9CE9CCFC0F6ABDBFDBDE4A32E3FFF336 +7F7194DA20D038CC44C4413B2CAC15C05B22758569D1008EA057DCDCF4A324C924021B35B10ED632 +BBE921BE2E34795951DDA394FABF3EDCEB99B3CA15D6579559F0BBECF8DF6E6DAE427DF75657AEDC +FE180A88DDA445A5A5E239B364B8884714B0ECE259F32F1742DBAC0BFA9A1052E2B14E632B56A474 +F2C9DCA9B5FD8D62A39227CA8C208DC69E5F543A745A913641950AE0DCCE02D20D374B652E2CC41B +F0417F99C2EFCE1C23204261FD1BCED5A1E8AD4736C5F23E14482D766390B1C62A320F751CA13454 +8DBA0B08E4BA0A0CA5F6DC765F9520D15D895792BE580133B92EF3691B95331DC76A551C4AE9AB10 +24D7EFC4A02B5654057224C3433A2AD6859E3E4C40F8F0B089C846002C75ABD667C68606D7300B7D +0569753AC074BE6943AD20018835A6EA28B99C983BE3BEA9B742A76F2D7A2A26B35664D24FFBF656 +EA28D525A78298C898C0BC2DDB56FA37151AF5A08B28226CE6BF09726C37F1B0BD39DB144CBB5494 +5DC48C374BA8716F6C9A4626C95B6993DB2CCD59A7430E3A3B2E6CCAB9A801760B8548C8447D129A +01EDF187435162EC13A65C943CE9EA547C3694F88F9706AF29F5357EE59500EC080E7FB844E8021D +907EE02C344DDCB78559AD7FDA31A3031D5CA80C004DBC04BE54B38752D49DFD19F1124C059ED68F +6E85E1A3A848F98707D7413ED3DEEEA823D958CCE720B85303CF208AEBB9B799653EBE4DD16186CB +F8C0301AAC6271EF9E2CF6732A6CB8548B7CAF2399287D6AEBD5ACC7C9D0DEB85BE38150072A0184 +51D3F1A8ECD784AF8149BF932E0422EDFC608B20B46D80D3EB68D746E1EF40423CD6FA218C9F569A +3442B0E70A2D464DC59CAEBC9607333D7B8FB90349676207AACEEE5ACE8E0E9F9C5896751ED4DA00 +95D68C421D8754D665D3D58C682AAB1DD72EF9050894EB680110C3E7F02C3551D73C63CDE8B45E5C +453BC9AC1FB3145CB6F0141B8E4928351FCE66F2A5AD534E5DD8BD901CEBFEB963DE2787095D7755 +81E588D3A165BD55B51F039992567B85FD3AE92C7526E33B44B8149E57BF7E57579E37611AA29DC5 +9EC94F583181201638BD4BBEEA23BB9EF067CFEC2E436D726F401EBA897480AEF7E38B9798C6CD42 +182C43B2BFCA7D8B6B696544F6B00C7B7D0D2C70D955304A4FC8D97E317C01113404129D480AF8E8 +EC0075A94859D5A79DF5F3FDC2EEF4F0BC1113D2C92DAB9859E9944DFAF557DF43AAF42B4FADE1BB +F5AD6728F884F0D4E7671787F1A3500B00E62929147C72FED37CC222EE991630EC9AF614160872D1 +BF4666DF3B682373AB1CE36FB87C42597FF1F96D3D7B07DC8755C2304AE69B955FD2547D316E16C0 +458BEEAD37B904BC27DE764240E67A68ED5FB57BA1F9C7C4C8F2BFF745F3E6FC732FD5E37CC1DED3 +6EDE20B06FD832673AC78DFB7655D7358CA03332A18241D96BB0A45D16BF2A2B80D0A68C74C8DAB3 +F18936EF73085EEACA9B24B18EB7DFFA75C707C947E17736EB7B608C4AB90ABB53A53F44D8661485 +5D60E36CA31704053CC453F2A50B878AFCE0361EC30444F5D6009ACB5D9673E5536B11A02B659252 +A64923E738F494D6264824392234FCED4D66E0342D03189778D41AEFD907272A919AAF40066A304C +6D7831F90B347CB8EACCAC6A726B40BE4671D6A0A591DC36A30ABBF91F9E780137D80EAD43BD49AF +690A3789F39D3EBFEA9CC64B82D601B88805B6FDAC3C84C61638DFF1E391DC74FE4D08A0683BC5D4 +E6634F82F4DA357742764FFB2B8D264275F82052921F7647BD8709857BB1C98C205D13EE51C14E9A +DAD1324562267D1931B5143A2ABD173C745B7272A6FECD532B5F189C8749DE0ECD3A6B1799C1834A +414554EA6972309C48DAB44A9DC41D8B28361E89CCE4DE8AD6058469D2F603E7AA62631E80C01535 +539580E124A24E9387E3E0E58A63AFB29944207BE5929455A150AA58E27EC885CCF019CABE1B8769 +0AA7FD1F4166DF820A324FA0FE3B59F8B767BFE029A7E3ECED513A6CC622AA8CE96563219EE328CE +BD649EE99E5F108FD57646926CBA30BE3AA8E00EB4CCA282AA35C0742410477E2E5771DAB74E4181 +D91DBCF25DF36BDBDFC5AB6C73A982A390416A23C8DA10655906878AF714C3478C8A0C7534F6022B +80925A069F63834539B18D9CBE67844520A195019C15F8F858E91CC50DE001EDB52C89F06035473A +022A718893BF7F6FC0B2E6CD4C1CB26E98C1A191EA5429BAE831F464971180F5EC2CC6E6F8F9EDB8 +2E2A7CA8C5656BFBDD618F7D31635A23C73F330EC76F4543C9795600F8EA45DF62BF4E071FFE3758 +2DADBF252F2A3EB399F61BEAE10BE0FEA537C272CE850026872F4BDFE5921245147B71DAFDC8EE88 +C509B6D8AC249557725FC5D14198A2DC889A4A9ED045870F767906A226826AC93FF1E09D75B4DF79 +8FD690C5146175EF2CBED8F03C9DEEBD95AABA69E98E25A98CC96820CF1C684F02E7739F525B12C2 +72613012143FC26919B800342903624AB29219E6266716E451C9D60A4FA010B8D26B56A4C91AE1C2 +ED778E35E66B49C4DE64021894C2B39E7C883518B06E04D198B7D056A24C3E65BC9E30BF2F73F2DE +21E676A37E2AFD625220831F598E93BCBE098AD73FB6EA5CBD9D17EFBE6EE35FE4EE93BD3A75A2F7 +118EACBCCB82216DF70F56C2E82F77F072093824C6ADB800C66F0F26BF7AE513A490AC3DCF648DF8 +2E54567ECB9D6FE66E908748B7D5090910EC99EB9B222E92B058F3EF34A11918D6FCDDBE2B9C27D7 +DB19AD215E7B327689E0597963E6EC907A23A0EBFCDF86ACDC349CD8983EE83144B5B8489545AE2D +ACCDC8E553FF4A1601F42CF82A90D571E36193BDF4A7893B2637DDC0C73EC0C21BDC4BE1D02BD257 +F4C240DD6AC53A321E13FD2EF4343B11B9C8152EC02EA6C4DBF7F66C9356647A948CA9E26672BD7F +9B828FE109600B1638806DBB95DA6AD0F78050FB13AA580139C7198B1673D4AF9BB610A83A709D3B +7D7B01AFFC0A740F03A5E2E3EB7AF145E3338D46D03A83FB82DD6487A52F9494A89717FB500A26AB +C949C51FE25DEE8F78E199AA53EC1DDF9D767D8FDA77FA32F19200BDC539F00A23DEF166D97F0DF6 +64489D932D115523CED7460212BB35D887FC3507C2592ECF622FEA49AE2F04299A547ACEF75EB0C8 +8ABDFA7A042D5EE4C88D716B00E79C40173A3F7044546D093B988463623DC1649FC6CD645D01E102 +1AAD11911190D0B07C0A66AE7F9F9CDCD0D841A976A8298C26A0850FF4FD42EDECC37B97A19F7240 +3413098A81025E4451850EAF180299667A392B7D2E96C4068CE110CC3CE15C6E58CBB11CE21A3E9D +FDC88ECF14A8F2D422E1CFCDDEA320DF5CAF93E6F9AFACBADCAEFBF542775D07EBF09A96F0162438 +62662AB782A464DC7A96BAC2B0F0F043E83690C3B24432F61293A1C5B3699605EEE8339AB079BA1B +A7C65ED392B6E94FF817CC25AD32E89C95A0667F124F26B11AF5B45A9AEDE4F443429ED30130D7C4 +68C940A7C538ACBDEEF77BC084F8A24FD0060BB9CC12A710DB9DF03CD381FB6E76F79D3DE40DEA4D +FEC56ECAADEAD68DF4492DBAE69EF1663E2CF90614871094BF6F0E1C9FA0EBB2D34923A19A557BE9 +54D914F35BA044FC800D822D88B5E70CAC27D6D56C66AD6CC3C7647DC679C8D3E1D39AA8282BCD27 +982428F5FAAB76EB16BCD26A1685C044E3C7B87B3A1685279DED690D76C0F1C52B76FD13C419165E +754BDD7FEA75E26DFE2B916DD0CD40301CCC945683C8E1F49A03A0DCE1974A76B754BF04D36C2693 +969FE4C6C39D60D995738F1DE0ED6A7E0B80B40BC14B440B6B8F1085E83995E224BFF4EEC6F67EAB +103B4BB6D21F9741932DFFBE85C0BA3D2AF925D670318D1157FACAE9C09B3AAB5B1FCFC889348207 +8D5A3F7787C699C420C9BF0883D3B8B3D7753E9A146175245CA9E2EE04FBE258B6E42334EF141A41 +D68ABA286864E72F0E4ADF41C1C96E60E69320E79211984A734392C870D72B8C236AD631672AB9F0 +FE48EF2611740799DF5B3339BD49697C4DFC0557C1022AAF15C24FDC54FBDEE2129EC70473A17EEF +D202EE43A1B5C7B78A299B6EC8BC7595FDA6BD0BD22E025E8FFD89115448D99FD27BAEB680A22C59 +7295E33201199E9E1E38AF099926344D1B7CA626166CFFBA2F3D1C15AD63F0C6035A5B9BC5AD644B +3D5636C2FF3B9403AFFC3AF0460B328C390D3B416C7880A0DFF10BF512BBB933081FAF4B2E06C093 +E80950F08BDEF07D56BD042433CB0A0C72E1F17692C9F5C7AA97C31AFEFA39233C62D5430F11DD31 +478E86A36A8AD3817B0AB397C8D6935960D905994ECD2AA8299D248AA274AE0FD1F1377F0443B37E +67DE63151184DB5EDDB4DEB9CCAC76896BEBE32E30E0F3C2557429FBD4B85ADE7829428C5CC95CBE +018C17BF38FE4B26B0AB736FEF35F6E3DACF0BEBB3B795D7982075B75D87324AC28D7E5B446F04F1 +0A001FF191A5FDD10B4729E57578FC42C26D473F22C70D5629AE05FC33E50A4EBA2C5D4D63B1D147 +9ED7B8FD7A0D824413D06437118C788543A21520653572608F9172CB1D1AC529280AADAEBB5A4E30 +AF99A58EDF2952BEEA29F366FB6FE7A804DFB1D116B73B45033E9E7E9767A9F41F2FAA76F97411D6 +420FB211B4BECF6C785FFEEBD90AB932E82EB0AEC7ABFA4A7AEE53E2482617576EB28BB2A3C7044E +15F0B6521F3B073021C3CE55890951E041EFA38937F2C0683BAD1AF255CF3747AF2F0B8A92BBE44D +88F7768D35B8F4EAEF0AADA3A42E26E3E3EC25E286C40808665B80C6265716DEEFAE3A86C9B54D34 +74285F3BA2946302A065B800EC7A9694B0B715BC86D3EEB649FAB8A47D170550D9213E8B8E9367CD +FC8527955263AB2AA55FB7ADB7DA9A4E727E3E69D9C7946369CC078DD7751DCEA1C0601C57F4B5E4 +48BAD7F5F8A919632178C77B7B5F95E402DD808AD59EDC020D82399DBD3A9D9F3FD089B0909C171A +940673E361F5728A89DB3E2CD0AE2009A6D64FD85ACEF62F8B42F009BBE11EA0AC14525C2ED72E03 +0DDF4F670D18B94C07C027509F281F7B59D854A5E46F4DC544BB320C427642D84B914A2E5043B1B6 +FC94802BE925FF260775F6D17A5C55C5A5D5931F88E3D7E8485D8026545CDED1DC96D1ED7E0088CA +ECBFEB99F110A5CCDF7EF3B4190F3DA6ADCD5A46DB27C0F076B558667A1B9ED967363799307E77B0 +9859909B4E86A27639DF3A8C0B193502FD22C0298AE0398E7D33C3039D7878922AA9997C93F29655 +5F5D7BF3A34D46BA5592FE2DAC5A32DD9945852962457B81DE4B6B9050660EEE1A0D95785D824A5B +DEABACAC8A8A90E7303093F0DFE57ACDF0EF2843DD7497B1B80AE7E883735D9BD689C84A61DE8127 +3E2DCA2F64B00B62F0FA3D3B5359241525434847763059927565F4D98CB8AD1842E31488E4C1DC58 +4BEEAFFE1D3E76AA2E6C81CE2DA9F00DD32841412695C8EE17EA60499E99B97D30C32DDB0B9E473C +E65C259949129A4682DDE5DEAC0611464650236934D7C57D1EF7E8B5D9E5D7458F0FCA9795853710 +F37B5C24E39D7EE92B2D4066D533A662AE2B063B741559B24AACF24DAB6FB6786F639ABD8B34C7E7 +AF20E5FC999BA74AD93CD821B545C2531C506719605A64FC06DA8907550087A4599EFA621DDFEC17 +B904B6115BF94AAFDC56F3570065D75DADA1AB177F4C333A04A0119A89BD209DB0CDBC5DA0C8B99F +EFF54B2F4FB4BF95AC573EBE6D5CC8110E6387365CCECA5630F5105C887DD5803DC1376986456634 +C3B3BBC235A72AF168CD5B350E0A8BBC303A2CFC37FF627F4697C0D19BEAE28FC3996E967CEAC4FC +8D9D309E2FA65172E899285BAD3F8B45B38C9C2BCE94C31911079850A040C08789EE377B9E652A10 +01EE4F44420757358E183D48EED67C3008E6F05C3971C732B98ABC24271527B267D8B10735CB1FBE +773E33FA51B5B472E402676E3590C7BE28BFFDE77AC34544718A20833C9891A176AA3A62D686E072 +7AB2150A1E77FAD5012D0299593B0222CA38CED2B9953B1E5893F176132F1197609D04F2F1D647B6 +F44B2EB0AD94211733F226B570E9D963AF9A6DF04FDFA26C0BDF31EDC41DA521F9D0090A9FA5DD13 +B9D977329F6412815A8C52C3290DD42EDBD312592DACBE0BFDEA209F389DE8E4B5D8ED51B46F1557 +C2B50098C2262D3DB298E12C0AC3E03B82CD2807CE04E109ADD00EB181D701E4BC3622DE8A86E977 +3D6C4AEB830F3283BCCEA663AFAB740B546C3577E9182EFE660AB380F0504590CEEC89313A608A29 +9F9DFFE22DA6296EA3E39857D7229885C78F097E7E7845E6C908A0570D4ED0AE320DFADB7AF87E5D +F85AFCD1B271B672813C0A0E2EFBAC5275807ACD3A2F09EAB95DE6F571E3F3C827FB3EA9DE89DEB5 +4B8B14770305B23EDE569571D0BB8BAF4811E12A0DD5BA4809818D2FE088DC1CD4BE72EECB36C819 +AC25B41BADFA27D5839D548CEED7DD18F5F2BF69EFCAC0ECD4FD942995E930C147E1A7AD93628180 +E62F20F3779824324C5A1C35ECEF68DE30BF5DFDB5DDEBF66CBC459B2C7FBCF0ADC0274D137BE67E +B22FA117C85CF7D52BBBB4CA5F9A6F73AFC23BF2E23B4B1EEACD33DAA3187F1D104843876EB44322 +67EDDDED02B6A507D13E3B9F012FCB8C9F0D14D951415BCFB20B3211B5B9860B0C6826BE597F2F9C +94DA2788E65107C5CC77CE1265E33DDE9378AF9019E7E68522997470A52088253FDCA09AF9F071C2 +988CEBDB46B7F7C8D08B5467A1B3FA0EFC552C5E20D4B9D565AFEF9B520FA2F544E802EB8E8E0C76 +21FF63F4A5C0C59F94E9E1731D8F3C80C772805DE264C7501E705BB21EC37A1379BEF8B8A9E50EB5 +6FE9CF9C10C9D25CBDC68124D2133B2DB6348175537EF872CCB8B1787330A5ACFEA87E2BE6615DFE +442EC74BBED30021A0437ED3E9DBA6EC49A824F0374B446271DE6E1B16AC74816F6216BAB7329725 +8CFBA83C178F5EC009C57404391898BCC3314411F129F12D68218AB6D0BCCE2E5AA9AA1D5FEE1E2F +0AFAA4BCC3D226C5512B456CA8F28DE54858F18DE2B30AB4FA02840859988BDE7ECECB4EB0002523 +C6EC40BAC2E7ABC411329F803DE2DCE1EEB354E4E6771E4328ADEE4E713AD248BF4F91108C52B169 +140F33D5C56F1EA2240E7E182C060187E29020139C373B4A6CC4D2156F7C15590D3C07C98535853F +4DF901EA9F2C66510C190D9456EE037DCDAA428D433CC2231B2B627CE2B5304B6A4630576BC48984 +66D7A8BB75D53ECA10C74D434A4E50B1A706ED6C7BA274A9CAD5D929B9BB8A631825A9C32A8F468D +578507DF2291DEAB6338ECC92CE8664D4B1C211A4CCE186679B6C71ACD5655B97ED8E552B09C1C85 +387749406C549057DEFC059CC85639203160B8FF05A48F7D5C4F452B111891846A521674C0E2734D +50B8C7E7B5D9F438C58DB139A6509DE3495388E0D7AD24F64FE73707C7BFB8CB06FA0E0C41346B98 +220E007E28515428C1874AC996819F16CB152C16F89CCDB3F9C83070AD90337F1823AC0A48B72749 +C6C29A8FDE1EC2E76B0D29FF711891EC81D0ED0B3349E9FDC413047731D70C33E57D2C4B637C8FCA +B027CADCB3E11F94F61CF3A56E4D90E8550F456BB90638DB6118229C9B74C9533508F343C0EB422F +87627EFB562C7730E1A804E3E4DC80FC0F199CE210045ECB1E3313C3364F78A500A8ACBFCAE0F7D6 +56FDC8B1BB95262A32ED7562A62EE5CF989235D1E641D726F34D215242D44A946662EE94E765A3C8 +75557732FB4DE1CC2699202802D4A5D99C621478C1C6D583FEE8CBDAF54C73C8C17BC73F1B414EEA +BD901409B83E98D62749F9E742FCA7C632C176D323E72FAB241D143950B87AFCA5B7B75936FC6638 +1FD0E537C30D744E970A08636D27AE7A277F3838BEB7D1BF821F331D483FCEE4EF9FF98F350B5B3E +CF2D6A5BBE95862FD5DEA6D3A5322DE6723B5201FF81EB1D1BC5BD0F77CC1737837655BE23E0662E +AFDAB16BC0635F40DA516BEBA8128C414D6EB5F5AF5F98B8C0129606FCF536181E7A8ECA563BBFDF +0AC65F1932F1DF20DDD6739F7B1EFEFFE282FB6DF35222E8148FB5968BC7E40A582D7B146E374270 +D3D014849E898E91997288BE44220B6B6E431B7AE3F2F7C5BF3E3444F5088F9F42B7F676EA14671B +C70A84B77BC275E82516040F7B9DDC582C0FE335852A83C31BE3B3F716F17253AE57372D14951A2B +58F69C2DF7B93052823311E4208A72C10D0625869BC5F3808D231E86CD259824D7E6C7669013CC55 +B61E4C20C0041C35BBD7F1C291EE7A3CAE016A8C90C56F22C515375252FC3E188B80940816EA5117 +88A2FC7AEEEEDAB9E0A33F2C82D886F9BE029BFA1348DAD16874751460DC149CAB5189D061E7C804 +1939D80A03BB529E3580A235F8C37EE25C503BECB9C31CB91904BFF4460837A1165A7474C83B5773 +5945BE04F3FAC3381310E4BEF8D8D4A7343B75D23BEFC58060C48BCEB52566A021C197ADCE5FA025 +1AD31CF2F90CF6A72122C38FEEACE6BE36176B1A990DBC42A33F0BC0090799C2F6C8AE6990725562 +B07725B3DD15C9011205C28700DF82AE4F00F9842DDEA3BB5C15D3A4CDCD18E692427505D7B24CEB +40CD7AE0D81A4C83A0F9ED579F924FCB19D9D017E60C6500CC64572E0161EBA57EBC11A5932F24FE +9F1AF444B3C657AD162BD39682D23D6355EF5D84440A124138CEAC85C926BDF484AD7B976D9A44AC +6015C25354DCD72A889474F31B8BD3CB7371B95A9729FF0E46EA3644477AA4C11FF5913D917B7742 +065F0593482B6E6EEC9EE63633A6A963819F3E6A2920136B17C982353F1F071B3D585DD24B22DE9E +EFB2B34F776DA85F85E9E107D16960AD67046476ADEC509FCFC64E8AAA6427935FC231C817A21C71 +6DCCE088EA4186DFF0A3A7A1C367615B8E7462DA176C9F9EA893DD04E29DFBF88679991AAB79A172 +48C06E2BCF3B91C108E09331FB57D94BE85EDCC66DA8225FF4B38E12F8563398E62622EBD2EAB098 +691EDED4D5D7AFC360AD6F263C043DAF483DA05CF70DD8BA8F96D2A5A87043DFACFBBBB3F8A378E2 +A896897DD48D8B13888A023AE6AD778DE5FA991907E731E5C5B0A3DABDC360D77CC71C59C34506C6 +39C21BA9FF7A9CF322C21B457D62F20448BA19CB765C4C0AE7CAD9A972F60ED60A21C92AB665537F +EAC8738AF8A27D3946F759162731F62C0A657CED40C66B8A9941EC2559074279CE0F6E10BE3F44C1 +D517E10D85EDA6BB6D097F4DF55A3DB7D50679675A781FA1FFFD6F1A8349B2870C8114A05F5F7645 +3B38446D57ED63FF8731661F0FEA79033E4C8B5CFA29CEC43355780C5E2EE86CCD449577EBDC0140 +47AA5CEC980CEA8200867212DDDAC234BEFC9FDFFDB43DD32F44883EC6F2963D4D28171E19E144FE +1BE2B8FCE99016691A00E4EA594F94E973E899D14DC44486BA6B4278DCB292FA5C7E6D73068A3BA5 +1557C3F072547E7F2F869D4E9AC03514276EBDB0920FA04E67E2934A250B1A502A8D06A25037CE59 +920D0E136C02D5DDEE2EDBE31A38BA32C4122AF89F295ADDF579FBFC72391283DC1914E9322A63E9 +44A280ABE7CEFA54ED2EF42B79FA97EF21EE83EF20CE34850FB66C378EC7C08B2BAB924F58FB8123 +FEFB43A385BC1EB922AD8360750FC1B0D8A303AD19286308B7A39A5086A50A8FDF7D60188342A9F6 +42286540945790524174800F8C44ED71306ACC3437FB49D8FC43FBF8E88103A76B4A92D95DB9B45A +FA067E31EAEFE6BC818D11D7CB8566BAB418B596A7494FC7326AB3CC029D010917B305CE585B194B +C5415088BC7AC7852A6D52EABD223E2634DCB29080217F6755B023C591F08C7E5D72267664136639 +9766EDF511FA744675D473AB37BED0ABD92E04049AA9008014C5EEC1B0443C6C86302CF6A3C38BDC +B8D9E0538E349BD930707DFB002700B5EB427192AD6105E01B8C2FE488CF617E9EACE73EFE3BAFD0 +F87CB57E02AD31627E6439188006655D6B992B393F4858254C2106FE9A3F0EF7347347C66C94A999 +D49527A2A177EE20A08BF594FF1E08CB091D22A8C1569320EB5145FE4A2674151750620B5EEC822F +A48C5F8B565DE0E18AD05F99827FF24D3BE03B007774F25284844D0F1F8F95643646960F303A831D +4C2C4ED9E0664C1D705137C157671D62179B47FA4A6441DAE99C2F7C8765C4C931EF345CD8F92D11 +E290B2FDEB1B0FAB4BA661A4511F75768808AC1DF2FE79BC285B976D364ED25C64EDEE62E8E035A4 +B79344D55B1E7E2B43AF1CF94ABFC8D0CA5E89240EEA231464449B831F1B3F9EF01AD07B38F0B402 +712A0346892A1DBBBCDBCC827220CE7F492CD7471FAD35B43E71CA14F1F1A9CEA740C4E1A98337B1 +5EB97B10AD57DAA9E9ED134CBB4614321D548C6A71D8ED95900E7947A7E4331D7DF3EF367F6DE8F3 +113A7DDBEA0F741F9C189B8B586B83671475A492AAF9994D884FEF3A646ED4F272668DDDB05EA230 +399223AE63088D636AE6AC7CE2DA06CA6AADE9272FECE86D0EEA8290B927B17450DA6F34A3D566E2 +096300CD8D5A34139489228ECFEB104714FF907A6E1D3DE35BC0FCCF45A2781AFC5562CCDB627E06 +F23DDBEBD4212F36C332C4A5A9498032213DA7C3FD03FC4832D1F2AC9EFEE3B840BF8356A16E14CE +989C37E6234CAC7A215DAE7C4DE2E2B6D9A876F709422113B503556A4BBD0ADC107B6B639F1BAC9C +6FAC7F4092D23C04EB8684C9D0A5F184160CB660CF6E8672ABE1AFA596EA86890DD22D0A406B2118 +9BB626943F378227132475A25710B75E5B3BB2ED7DC0412A0A079E2AC311ED55AD8E7B2A1A55FC84 +E62D61398511B70877F3DDDFEE5D033A9ACB66899021951378EAFB9E9A799AC9686FBC2E9E9B3A51 +6DA9DECFDE87FA4BC042A5D26C2117E29AEE8840B18361B7B38B21493401E931B431EAB1A3371628 +EF13BE5A0C64A3B9F8B6A29D209884706D2A9AC85B86E3839706269366EC7E53F31BA4219F741E55 +21E42F0A10B7B29E839B924AD90861FDFE3A3D446D1E32C06E66A5D7187A63E590000D1356718648 +7C99BC31088356C9ED29BD80C61E442B81E15657AB191E90EE77A6804C762C45D3C1937EA17454CB +D76D781A0C96A0914B9DE3D3984BAF6075D3A25AC69BEB34CC413D26C39824B83F853DF864C269F6 +8443970933FEF93208BAE4DB3BBE90DD3CABF6EAAD2F6CE664CFA05999FB1CC406A3502DAA6C8145 +3C69CD218B18BF0B9654FC3637EBAB8007A2EEA6ABDD11FB338E89FEC84B344857804D028F849E44 +5982E294384A442390AD36D7C4182EDD7A05BE6744FCBD3BBE6FAF4796063CF42499F2DEC302AEA7 +B64FEF6DD74F278FB5C2897CFBB87FE03538B2739E25EDEB806142E0030F7413018EC1833840BF37 +269E21DDFF67B8059BDC83DE9C6461F3EC2BC2224D2585AFA2AABEFE7D3B2A899C3E08F00AF2A707 +55C145C6BFFAC8F96B54EE682C3C8CE01BB44EB574E1C721974F236E8A2AA28DF0A4AD285EC4DE58 +DBD3A2FAC8A20173AE84CBF877559ECCEC64D9448F8CA0FE5272BEA6543738D5EDD54B73AED3C4CD +172F91C4F70616B36D37B1252D355820DA88536C829B1542C1EF4F76375360E7123525744F55001E +71F5AB1E5B39A5248CCC6789647F1FAE5E989A8A8ECAA2A9116ED69DC9B9AC25A743F70AC4B62D72 +5F120D94431528EF8C74611F7529BE325AF84663989C219B70274584D1EC4485E11AEFFE4A29F534 +6912577C010FC753DB47204BFD9E504027B335EEBD952FD299E5562407234C5DBF0B839D1010AE10 +5D56766F87910E44AC7968842833A302882AB481FE0AD444911F7EB8555DBB9F3D062E9E9FF3B529 +4E9A3B3AED4AECE34C816BE7FBCDAEBA33FDC491C725F13801A6BE162F7F8E99491E8BF77F2D2010 +D9FDD78BA2606A28810CB5D88CE00218D49609C62E96BBD36110DD611C3573EA341FD2AF6FE0C932 +58565D4A6C88CDA3951E287168A478F5EE074BAA9676EF5911D8EDF8D66A7E8F3BED1BB786F480F4 +423C3D3CDED2E80AB76CF5CE5A53B092A44FE52806D8EA72033BD3E7BCF1DA9C870108F32D8987FC +25E7D873985C3C4882A5CDFE7A8B51F3754842FD6F1FF6235DBB88F04989BB2F2816A476B513C10F +AB6F81CF115EF5044D3366A4E192EF68D385B6B6F4BDA7D14D9627F40843035CA80DE49F9DB52CD8 +714F769DF97F73E3152E8DE34D8C37163D485750F8F4E37AE8A3C3BF54D97BC3B2978985B557F9E2 +9F0537CB743EBFA0B7864BBEA9C126F13C02CECFDA50A8019F90900F409B6D700CBB9EEE18FD1952 +D496DBFDBAC800FB83B37C705367F182D91B21C7F6C242D8735A75343C84DBFFE583337DE2A95890 +660584B513C5BEB7A0926BCA7B7DC3ED4D080CC3F1264A4215ACD35DCC62D896B4354F2F7254444A +7235E0E3D53D02583710DBFF2CD55AE5E61D25ED1B3C3B6708E5BB308A3D658F043C26B881C949C1 +0940AF6BDFD2DB0D544DE119BC7F7B03451E61FE18845000D350AD6D04A09D8E3E999E6DAC6AB73F +818A11EAD345EAED03BA083A6EEE7E9CA8CFB760FCBC88B8DBE0887F79AB430913604F15272F9C73 +DAD19D591B40CB7863414A8FAB21C41F80A4BB0A3AFD9D4B1322487429149470DE62F305906F1244 +2AE20521BE034F159A7E7EA211A2FC6193AF59CBDD4B43207BCDE8697DD515459F80F8EDB982C97C +05ED3996E03891DD7EEAD505F6A71A924CC1CEF29053ABC8F0B5F56D0DA1249F317406822C225863 +ECEED46BE0072EDDFDF5F63DC8E94FC119087A66E394A653D5AC774407B006B35C406E7EE4385565 +78290F8CB8B131B88BD78CB87A11CDA44C5A199BC71388A81F2F30E2C003094E793969673E8D0906 +1F4A3FAB9B14C52EC89BACA1C52703F000A967EEC445E5423D3BCB9253D91AA64BB26727C8461FA7 +FF61022B4C6A9E793901D3407487B4962A16B564CAC93D7AA28A22C28318F69770E12DF9D6CFFF17 +09EE0604890191217696AA52630231FE11153761310A72D60E6925AD6B9D63A66047F32B9425C91E +57505CFFE42A90185451297C2CAD408B0CA4F8E923EC26A3D5D66448550AFA3CE3BE9296C8149878 +F853F2A7C3B2E98899C8F9B47B1405F96D2B22E9F1CD62A2945AB62F67AB0297982809A829826BC0 +F24A7777508EE0A71BA7588663D8118E3BAC936A61FF4A628EE96C0B9AE05072A5A4307E68EF2C3E +97A46EB31A2A7D4A33CE9C44E1111D73D9D3C6A0F22F50D8C22153C2CBB5BE187C0F2F37708237D6 +FEBC445843CF88F4C3A249B39DC971CA003A78028F8A2CB3BFBD2C26CFE457A9561350AF93E60295 +D21E1C2024312B1F2F76F2EAEB822CA72412F860F5A87DF705C2F82E681AB9AF45A19023E02538B6 +9E0F273BB4811F07D153029BF988D3EB66358D987895B96B5EC4C24F3409C1EE1B5978B1EC8F3E2E +75FDF2BEDB0AF0CC66481FA98ADBA8DF4D8C8EB800F88C7776BACEE6F61095FC2CA891574F309E1C +0C27E37D8B03472DB3FB8D6668E286501118F7DADBF1106687AEC6DC4C162D22E3BD0BD42DBED782 +BA4707ED5181DAA9ACED58FB358DA0AA9B63AB4394D42360C3F5082A6F168CF41D381073A1D99CA3 +3BD97F62446D62059BF4A8616A8809147B739400362748EDCF39FFD2BF2C16D4632C06A5D43B48D5 +0D0B98E4449B4F1D39659DCCE8AC72A2E8F32487FF8686E550299F37A6353A8E558D4A8B074C2F5B +864DBC8FA3391E3B5135FC738E4C13868D67234489B6AE382792FD5214D5F9E5249AD65C433C22A4 +D63FA7A36A48A339F443AB39C23D2025741186C8B18AAE9952D41E25E930D6905B29F85A4589E9D3 +95FF040E3F72FBF29650D4DA2B6FA27F60DAC4169DC9764021F4E7E094FA25F6B5AF25602C593266 +ADED528EAE6C967E66F0BA05F258E34CF6AA5488D3F7406C2AA9D9FD9C533E827F26862D348B5223 +A690E40953F337352C8245F1861A19E5326490F2A9742917B5546884E8036DE0874363F812CF6FF9 +574DFD5BA77A1DD7C5778B2B9A2E22F2482744E2CE9323E590C60FF02412CC7AB0EC30D0EC857D27 +A4E3D8B35FD69FE28287B2160AC0BCF645A0654403D26B05041654B17D82928044BBAC40871BC3FD +D8EFC3207928BDE5D66926ED199017B223AAEBA563F2723AFFAB737F6482DD269F44ECCA3B32FA03 +FFCE3ED882B449BEE196F59E6616EF1A2F08B42B1A184727D5BD96DF83972BE1B5F8CF098F61B84A +B5BBCBF231E099CFC07D4748D43F129D123FD8051628564931E43A70BC09BF20AE2C0ABB009014AF +89753F91ACB574C5A218A47A02DA3AE44D3F688F9D96076AED9EDE7388B2935C01FD400BA7EC9574 +96E317C6931E3ED7078A53CB4CEE4E56311F4D8A368AE981606AD7E9DB0EA2A10E079476D9881596 +8C9675D9CF15E29B9328600FECCC02B484D8749B4154D69CF90997AD650D881735AA71289AE93FB5 +8C56686C1F9FB0E696CC0285A1A870373E5DCAC285A1B4FE903CC30B8ACAD2E4613873ED77D813D2 +9FA4984A7530F5B046A71011D97CDE4BAE9648AD54537A1D87A5AF1B92560DD7064F3EDD3CCFA7A4 +FBD6166D067945F103C13B3019540C3FE706804A7A00E1D28A0C26A40AC5A8845E39631A00099CE9 +88C3F12C8954EA84BF268D99E2E13726BF9C63474A5FC874CBF723BD48A5461177789E11B4F1CC63 +0FF4D60DE4F01422C1029E664782232A8BEFC38CD058135C79E015A55BA933AD8D446A81051D956E +429779863DBA60F4258DFC62D1DC3CC3862C5D310579417FEF4D7642ACBA8BA5284DC5833F581150 +ACF91709D6B3A41395960197AB43E63B8C6C2F745DEB4647E298341F3E3023D7DA22509340E28140 +97A0C193493711E60C5DA99C464D8DFC83A61291BD0E13AB6A42C81253C0F1B37E71E7706BF2D662 +76C60A3A7DECCC58E56524A200F8C6C0512FAD2FEB51F5C24109E284FADFFD8484A7A053E8EA544F +AFC1C7BA21E4824866090E542B8BB0BE603060A36D8A8CD64EC92D6037B438EA6A7F3A23E9608F29 +02E8DB10F2854D89E2CE6F093E6804299305BFF3D1601C2830F793B92CC2542A6CB0E9DA602E00FF +DEF234702D0DEFCD270F2984642F13B818F65BD407B61227A94AB11E7ACE8D40849808AB3A7E6EFE +B2D4F3B9FCD233B8497B35299DF28F651B0B2960B4545BF2C05952229EB1CF676DA761995413051E +885A529957538A8B2C6BBDCEDB2A3F4104004B880624C2F55B544EEE8DD29231386492598C2BE995 +6E7BA78FA75FAEBED43807FAF072839BAA02333D38BF4A59B1F3ACEA5C7BC188C0BE8A8BCF2069BB +E36BDB2203EAC19D249C4ACFBF8F9717111B7F4637D631D058EAE0CD4E4E8A9F0ACE1BE19B3A241C +9C6DCE2513690473D8F4A59E1A7FCC926885CC324086981FE0C6AADB8F48116822C59B1E93F53829 +77CC88B82FEEDD2B4CC6095691FFA1419D5A6850D3576C4E705C676850D0BCAB84D52791355DCC7D +70A7F5CF603B4D0D7F0CF4F25AEF44F21A2D627D45D306DE0BE1C794AB90DD1EECAB2D61A115D3D4 +AFAD1914E808B16BC868FD48208E1F915B0E8ECFD59AA5895CD7ACF95E0DCE87DD8B12F683C8EFD9 +625CD388337C262013626A06ED60556FA70A8D425D36B48E430758AB4CAD34A656F457A60E4DF933 +07C4D5B2698501E6D1F270CFF70E47DE38A5FCA2B04D7BEC3A2945EB93C49F1D366835581B42F43C +C99D71F13F45A9FF8D12E26C43E1B994BE5AB44E5309B0F936D78C93169D666DDE6D18E33A5E016F +32278897FD7E76BAFE39498C6A849F4A6D882D109C40B42488059554CC95530FBBAB2591DCFADED7 +3EE5F2F2BBF17F4A131B8126F9E0AEB4B379CEE4EAD92A1BB29E3789EC19671E77558B4DE961629F +28B49DB4D8FAAF541D23205844EAA801FEA468D26F32BF9CAC30BCA244246A55F600BFBB61C5E8C4 +10CA07319DD094770DFFC1CB700DF67097F61C46036353C8AB3A5E5198445A194BF189E20490E970 +7D2C03C1A003BC782A66841AE5DDEED2297BB6DD019C98A66A8F279748DA39C85CE2082D09210EAF +CA995591A1A3DCA52EFC9A752DAE0CFB125127DA2932AAAAD7E9850AD78D48304260B4C270EFF12B +160DEF2C2B8E30E8C137975AD20046F37A72F1355F31D878334D96313D330C24EE7350D4042AAE2F +A345EDCEB133121D4B39645B1D6114D0597C3301B4CA56DA2B4A457D7A51BD13B7AC61FE6E1CD451 +6253F606FD4B57E9F4895CAC93691EEBC2AC992CC5D122DD3FC6A9EC8FD337CD402F03F901CE7986 +2F4F4E4500617DA0F913E357BED3ED04F49FD61FD1C66606CB231C3B7A5042C7C07EAB2E02BE8CF6 +AEE5E16B4AB725B5FB5D01EAD4887E365BC2ACA579BD80E0AA686E4A08DFFC70F99132353E3D5898 +5205807271753BA3DB7264C4567DF5FE999514F28E1DD6D3E966D8810978B140F8DD9BB259078A47 +013BCF247C37F543C0899B532F34843CA56F18F688B42A12DDE2A90CA457860540B6FA138F753DAB +E7331188ED6F535480FBFE934F68EFB1C9C16D4F11EDB35F944EAE63751101928EDD0E7AFE64D7C8 +5E9CAFCBA88450DEF9122A245FC1ECCF9EF8DD94EE6A70CF16ECAB39B52597AB1E8C47B6DA4FF0DE +C7D0FABC84DDCC8C652DD7C941DB3FDCC5F0542A8F433AF9FDF4D393E123884E1DD5D359D46DAC61 +0694020BAE84B3BD4C068E3BC871BE21DA13571BB61387E207926769236776B5B31A4A462902966C +DC3D92BE171F10EF8395D2402F0C492A3FE55979CB903CFF2CD2319CE4B46481489E798A131635CB +2E70147193FE3C8F4570FD01BE10E004B17341C4DB8B029BCBBC45D31227A684E5F38F5D6F0821C3 +ED13D31DBCFF51BD759C84A98145FC86D82F871D2D83F43C3DD7FE9A064120338A7BC63A2C60667E +25B50EA1C267174B334F4437856295A6B826F54C3EA9ED39CA6909A0F6D9669F1E75A7A05CFBE7B4 +2C330668E311872177F0BE3A9B3EAE611EB48721AAC2F10C3CCD897CEA8A136E5E10C2337BC5EEB2 +FBC1A3646A6B6792CA3946BA5D4D135D929547042A2F0D0A202A4D86E3F7098C823E7AE4331CEC6B +607F6AA434180B4153F6B10DCC914A92D6D0934551BC9DDB3C78065B2177B264216AF5524D798AE6 +2A90D00A70CB36C5B9950499163C2C1B04339FA76D28E03A4D0C80FCF7BF29B8A188C67EAB6A4BE7 +8C07713C7EE09043301B5BFD60222DD0D0943180AFB286D2953A8A12661986A4812E2C0DE5B3E703 +DAB34AF0E9306A5711D286AD09A3C6AB80841491BD0E5A1D1ABE1D600CB494BC17EC4F74B45870F6 +AC41EFE16BB6C87F382DFA4B2B8DDC9C2E912FD139A2FFD5C92D836F3D749EEB985C62A376849751 +A6AC56B1F679A85FDAD9448DC7A4CFE323AFB540408547C53297DB6FE9A0B08901BC285997934A2A +1772090FBA175CF4660764B87A21A738519D5B619840D15F45DF3589E8B80A6DDF1F395B65345869 +58C7060DC131700DFF6E25962494583085E6F8BAEA557A5666E6634E4704D0C07BB0A2EB228A7BEF +EB890B4EEC638303B8005BCCE922CF3A7AE627206F2946A142B0095FF960BB8B8F9C975B6FF07479 +2D5C3DFA125B7BE7A8356D8B44E264AF6AA582DB84BA09D2FFDC2213903FE8FE16DE5EF61E518DE0 +6A29D98E217038A4CA4D219E4F114858CA493AEF0CD6495A7C5EF1ADA06AB543051B1A5213952D46 +648BE06D15B1728768BB853CE32943AB0988D172227780CE82D1A1D297D0D6ECC51B290E156645B6 +9BD54699940AB17EBE10EDF258BBA6BFEA39F4F0B066FF6B3FA16C7C72F2565CC028F249BAA4B488 +B48A2513DCC5D1E205FB874BCFE45612DF4EDAF815CDD53CBB80842B429B1AFA32D35EA58E17F4E7 +252C2C9A737AAECFC25FD8CA5520D3EA38AF71C61B88F31FB53A7F5369305D63495ADABA455C3C4C +35D9FE423A00CFBB278CD482D3FD33BEEAC1F359AD9B6AA17D60CC46FAB670ABFB3B2B4161B9EB9D +949A06CC3B734F63DE821FE0B8EA065B6F79C0601E46E8CEB6E2DE0C052771E0EC7012063F1AC46C +3FC454767469225AA266784DD77256C6FDE25D7D857FCEF2562AEBE38A9A47063AEF91449723E680 +B36D824BB95BE95C9802F7AD4DF1AF37B4777A1E116A7EEF770F0A499BC9F9675A775503091D4EBD +F55118782E3E54AEBC67A0545E8D75BA5A482C6CEC50595D45AF041664689AA4338C1428870CDC15 +BFEEF788C1D1E87B3389103225E2619120B129FDF048DF3F5DDAFF1CE87428E6BFA591B91D82720C +E4A72FBB952403435248657ECD5456CA814FA0ECBE70BC3D391AEA0A195AE8FBE92D054AD0F3E549 +841ACDCE59DCAA9DAC20348A1D05DF7B4127C75F4C6607EF8501F741CBC96AD8D429A4CA8034CA52 +9AB673EE706F1FF51DD93F45802031EEBF8F3AD29D2A74888B1CB344F88ACEB9B4DEA13ECAE0A3B7 +1FF5CAA38D0DC484B96E90251D56732B85DB8A8DBF10E0A5921121CFDFDB6EA9CA45B98584401C3F +346BD026DA2A17E75CEEB74E48667F9BF5C538623E88021F0C24EC7D082C3E1FFDE5A6F68F0F3F6D +2B61547C4C14614118F7942851B090D6C8D80BA06D9139CB5AEB0EF77DEF2376C0BF602FB1083178 +27E86DDCC00622438FD7A268C0252CA2E20DA887A9D5ED251FD5CCE811E514D702206E413DEE7528 +3A6AEF793F5E3E3B0B10E59226102DF2A7347A95B96E39C6B6ABD1FB4B7CDC0813D7390E2819EE50 +7E5458E06D43CC3BF16E40CEC4E64909615CBFCD233B5D316BC8B8B46243CDB7F2FBECA2F208D0A7 +985048E88A5E685A9EE8C3A351FFCFB522C1EE41A8E4EF0A080C0D110D2A0D8A980AF1F604D1CD52 +3CF41D08A45EB809AF11F530C138F256FFD49BC20F77005D004AC85FD563E8E8BA5F79CC60FDE714 +1416F4C14FFF281ECE2BE1D167B7B1ECE17790454F795B056CE4DF1D08F25F0E5E2A16CFA066AF97 +F3A80015068FA2A1D65F329A5CF7856114A77EE7295C7929CA27796D8C51B31347CBC7C4EEF1E75D +12237C558BE5B65DCAA4CDDCB419E543B1785DF8B225C2E9778839123E6F85E53D925C6D2FC0327A +EF9CA13D1F2F9B3E510A1A4190D3057227621788AFB6567132238BE86559BCA006B85AFBB8AFE5D7 +A8CE58AF731F2550108C6B8CD63632184B1E218F01881CB94CEBFFC310F263565A39C83C874AE474 +AA213F7DD9CB3B8446A3E47E81BACF7B16E4A80899BF2D30C28F9DE0678DF364589DA6A454398E30 +21A92AD1D8B0DC494D96A06C7568E34547F0E68829E5D15F6500ECD7403B40B5CC2A479D0F8CE3E8 +709861608AED046339BE055BAED1A6BD2311CF918E74378D893710D42A769671326E947F108A38C2 +3F92EF5C6A50EFE36E858671F17151C719B9C7D60E8E429F088616D616080DC676760BE83A3F5229 +6C4B13CA3818E3FEB56D51DB5979F28063F7D537C15493D57CAA1E55438C01C794336D9F21520484 +FE5B5BAEAD7121E3CCC5086E2F2191DBEE9527CA61CE85CC0A4D99EA29169B17A10B3D4372EA493D +48CF572E570CF48B3B7512268A9AC4CC161F97695D19412C36F1D67522E272B1C18B6122A355B43C +37636E71462503C43C1AEF5ACE34A6442075B59892BA54E9B5A99B9733BAF64161450F8ECF4FB2B2 +DA6F9AAEBC827CD9AEF94BB463935C26E1DD5DDCA5BA87E0EFB62D9D3226DC28427EC097B68C9EE1 +E47D125F884F8656C8AD55D625A8F7F1A306383A5036A12E63B6FAC4A215AC88725611EBA74B6770 +B20F45356879DB807E9C9105D14C83104B2DCB4F274A2F37152425935B3F2D1633E4444540F7411B +24C17055850C9528D912F9DD40984CA1B3BE277F82198BC99B80233B8AAB674A44B42B9123E886B7 +B3161D48B4AA24970BB75A983569690BFD4B92E6ECB410DDCA14A3BF59057397FF1AF159BE230216 +FF2F7009C3A3DDD81F2BD1F5C10C2C2F30A8095D7371427AC9D527AC6794CF8593303F786F21CCBE +36DFBE01B0ADD78DB3C9483D9DB09A9E2577A3A4296E7BD09C41F5C91B29C9D7E69EF06B5BDB66E0 +853C3DB133769BC8858121A350CADC1D61C77C1A313833D077938AB2E10EF970590256479B1549B0 +401A9C5E97F12A269A305B372F0EFC035B06AE3E2187D6E3A62DE3B4E8075F0ECF5BE32A5B97A4AC +23A7335C4AB29917129C08250DBD1D5BC123C92E821BBB1A7598E32DDDF1876C6FBB7B858FD317D7 +6022955595338AAD2BFB3B17DA11380E7473A5F566C6329E0F38D9A67E68FD4B8CFCC82FC0BD7BA3 +9A25D5BD0952CC71CF0BD7B5EE184A8E62FBB366D95AB051BC3A80E9F6C27EC100E00C4A002291B0 +4D758A9683657FCB07A2AA8B07DF585CD4205B4DC55330E8A2A367CBE966E84618ACFCAAF81C8A9D +3A3165FE5A623720A0C11CBDD5CA200100DD2C970A09B124D98987D6A2CA7CBB40DA5B585B86FCB3 +8E96F54C64A921D7A5D2F805C67339DC4FC026FB2EE533BB9700F66F4AD58949B3DB75D770C4D7C5 +C23DC720D78124A7ABF30CDFF10C708219903431C7B1A19317341CEA22AA11A588F8AF9BFA4AAC40 +31EF22F13BA597BD1E5958CEA741274197113CB0B7975424C17F73733ED30BDC0170CD0084BB7DC1 +8F031BEF2A6C44D0EA0EBB84E31D5B4C50B63DF0AF96AAF33E3C986D4B82D161E2F9049DE9710EF8 +A11BA0E08266A3547F20567E582EDB47633772D37FCCD119AF5B11F338098F185D5B055975F56092 +CC6979FF49487D69E08A4E8754FD51D29410F56E0F3EFB9C3B86253B3B0A39E9D217604F664CC696 +634D216524F976D066034DDE8EDF5E8F9644337C50C7D8E2230B38E0EE88C53DEEDB3363477E32CB +88B3ACB6931B9717FC93F8D1A9DDDA3A7D8A56783F2B651A1DE216493C34DCD17A7635D21CEC0770 +751424B599B00CE950E60D8A8B3444F1629220DF073137E2ADE1B6243C1C69BD5E10450B948BFCBA +46A841B98BCE7C86E120789C73C87E581868021E68F78CB903AA6D494331174CBF944221DEA9E567 +A2542BD399C5E471D972FAB7E8950AEFF3D91A1296E685B0BFF91367C2703DCAB10AB236DE603E3E +993C4C9020EEBFE89726AB9FB92AF9D371D7188448CAB9FD2704506C39F0842E36EB8B9AAFB1F26F +CD366E66725AD28B19FA57537757EC71B04B451F056C3AEBFA04D1C0CABC9492B6B8D239EEC36776 +D6B515ED43A66AB4342BE4C8B2027F2D008EA231A24B472DA8352A05DDEE31CBC3577E5814DD2D4E +C17A216A7C8FFB27012346C9ED12F7819B96A5B135B6196E888C9AC73D7D4B7DC370E2FBFA17FD74 +0BF516CFF69294493900BFD63721A537BEFB31C5262567103F9CEA4E8DB02964B983E1AF190ECCEF +41F552C45E9B94E29AE3F129687EC35719F591C1987A08DCED3B822344DF81A70AE78E14A81EF1A1 +A77FD5D19F7D35B7C12A473EE11C655E15DC5D3C94F226CBDA85377338BFCEA18359176CA7EED622 +84F9015F2D592E27F1037C95570CFDCA3B9B90D35DB8C341434BBF04C0E692D4C2C3F59EF386CD1C +5A8C783198EBE42C89E3B64C662F7875325982E9299C18DBD1FD2257DD964F9F9DDEEBFE56E4AE9E +8163E8C58581BFD5818BF396CCDE3A66F58E17BDD262D63E5D9AE0D0E43C304CCAC4776B87D40F6B +5AD7E625B2065FAABA81AAF2E2D32E0ACAB12DB2A9FE9C6160FBA7DE5AF019810AD9C64B2E6567B0 +F0108E8EDEC635F4BA88BDEBD3561CDBBB9063B4D19C493E0CA4F551255A7DF6BCE96C17A5DD4877 +7F654F4F114CF29ECDB9779F63EECDC04E1FA06E48701547A8F96483B4195C0DE90B0FD1B95B5F12 +5C43BB973D0B6414A994595359289B5FFEE4A08D684C2E7E3F417585581318D54DF2EEAE7CF36E2C +A1A70EA76154B88C1E4FFDF5EBC28ABE264B40583F2DE1F761DC25DF242396AE502CC63EFD6EE284 +DB37B0DF36A8833ED3EA5A1DB372C8E1F3203E6E945A53D83B87250CD72C851F68A602E7BCF92DDC +E600D518069F8900F3BCA434D21694229572D55F307E0FAFA3DAEA864BAA19F227097963CE83882D +22FB7F1AA3FE558A3669BB7FA37C0A591B4BF3E2A2FF85BFEDE5E424E0AE337EABDFD67BBF38E160 +09A687D2E7C1882DB79304D722EACA233EFA489B10E8D4D404D12A5A4683FDE377A9A952CCAB257E +ABB408D75FF130206DC3447F26E74D1D00354313E340878917177DBD2AA5A2A70E2087D551DA5181 +6FAD07F6E826DB0BF21560BB26906D3100E12AA3801D4A8BDCBF1A0A8D58F1054EE2A0DB04ADDF3F +EAF1FD6E322BBEE274A4995EFFEAAD0F24D9FB45730F6BA0F42D88BE4AEBE9777F8AF508DED61024 +EF955B26ACEC51AF5C21BAD7AD93CC0C9DBE03C0FE9637A3099E5EF329051C87FFF70042D788F21F +4DE645FEBF58374F7E38A9AFE3DE4D2888DD807A09169DA8516DFE37591C122A4798343C1A8121C7 +430F244EE5B19A21897A4423E21CE0492E75C9320E37BD65F1EACA7FC6FE032842E4D985E666E633 +AE5CC62B3449404672B284EA5C6A01E927787104ECCE1354D1C0E5AA2452B7B12937B946B74EFA98 +8C5C79EEE5ACDCFC994CAA853AFA08EB9E180C5E898FDDF8903EB0863E98A4FA537207C154CE5B98 +EF4E97FB6A1CBB07C45D34221EED26998EC864475D231FF76E830A7C0900C4B5FCB980E3F67F4EAE +3DD8086CFA1874E65C424646C7A8F84272DAB04A5984FCE3F4B89380B3A5C6F04EA3B683CC224913 +9209136336AC9349AA89690B5DE0D93E92F996BA7E346B043EBD45A35FA297DB70E9A6250B138674 +FDDFA609BE11EBA8106EEE3923B4E2657CF7A82443C25022A5DED862E8E3A5BFA7B8AC1C1B804B23 +C08A7D8FE594140E8432C1A5E183FE361B6B8C763054458798B756A6CD2535FF4667C634DF78AFBC +C87D40DCD56FFFA12CFC6EDC6A8AF41AF657E0137320624F1895D9AA84AABAC5F3653E977B054C52 +92B06FEAAAC0E5C7FD45B6654D0D9CCDF292A8489E1E6CD929F0AF25B9604CE33BD25662133CEE99 +CDBBD00F865431193767480700CA019D694B38D695CC29F732B5B39B6CE654F1AA18AC3223B60E55 +3FC3C3E114E6A2E59617BFD58AD8E429200B59E035DB7D67C8DC6C72001D005924670ECF7A06FF5F +7B3DD5556DDDE1BE5163F7CD6FC969033EBBAD85856522ECA91F2A200A75BDDA69070240D86C2D7D +959F1DBE45D488A96F308269E8262327A410D057446F596418DF4A12267A7A3FF5ADEA0846896D3A +EFE3D5C72387F7EEEE809BCCD23D1126B86C45A0F66404FAF617D03379A2E44865051E92B4C835F7 +21492147DE3DCCE47EAD192D3F10A5DD459634D6C253F4D09DD98E7DDC836EED8DE08A78A6F6FB93 +84565CF6DA8D4C948144272D5F8787643B9B550F96E42A8CFC0F1C1F2B14D83201199E9E1F5BC0E6 +51E39E16A744930F4409C61CDF9F456C7EAC5C62BD22CE0DDCCDF3755DC670FDC8FEBF09F241BF95 +1A1694BD70B9695A15653F719DF4374AFC6C27F58DD144253BDF86FC1BB3D4FEAEEB9196BCEC7168 +AF1488AB7072751FA24C6642B87DCADF4B2427631E7D7FA39D126410189CD8E24CF0A490FDC7045A +A9A83D02CAB57B9C27211FE1805080FBCEF23B86CDB19885A574C589BDB15C4CB3651FA3D2D6B619 +4449B16D927047E376A84F9423562CD548D192AD0A57E0D5B41010D1C7D87929DB6F456B0A073DF9 +A8220BEB3D4DA01518B5C85C82ABCC821BEEC1965283DABF6AF3AC55ABDDAEF1B53E6119DBB4A114 +44FB1769F9891B5DC931ADFEC8ECEFFA179427FCB08C1B149EDA70717C5E9322F3CE9405438481F3 +90968A38061EDAA2223147D6143877E4C4E645EC0A9803A933116D610491FDC639DF69B418156772 +E14A428864982D3230EFFAA1D2D3388B0F70D8FE202CDD6CB0E97B3290ECCF4A1A69D93C11D3D735 +FF0B5A9A161A86CA4F16B7C231B8BF71D5083697B2F24C0BDAA6E83E5DD2222DA248D4ED3B5509CC +7CA40D8D5A2507A1D40FC1AE9486343CA2B2C65B6CED9B1F4C0535ADF7BECCE45E1DB74C7417B06D +897C6ECDD05F068857D931B3D960103628675BE2FB82DA774ED3786670D8C2BA4ECC36C203C7F7E0 +9C97A09E3B2E2C3EC30B53A8825519B664D09F149AB73F183A4A22C8F20E2052F76649AB881BA28C +6C3C3950EA9B3961F87F6AEFF24DD7B4E10DE28C15DCB27F593F98CBC9CDE19360C5531B2A0C6DC1 +8FAE2832AF49BF9522C068F3036516F711AADEE496EE4E3EFA2F55EA4891DEC6D8A868E0B0017076 +3040CA4D42A73A71057DFE315FF8625BBD74F41E5CA77B7DF096C2F4F0D51B1184CA9F2C8517FCB3 +956C44873D630E94EAA1D2D1451B332770172B2D7A21ECAC864C612235C0B39ADF55EF074F2073FA +D3B54A66B07AEA1FB495A9F7269BD1C07692B0B762DA6881EE6B265ACED0BF0794C0397F8D8B13FF +275BE77561067E2C1FA536131184682BFD32384C6488012D29E8E55838E5A5AA7C40C20AB6B03BFD +BF4EFD2001A612100D585BAC1C77C1CB1ADAF2B000E09686091AB7D1AB6ADE395A03580BD78E961D +14D052E7D4A61227534B55FACAFC4E8F9326DE35BA53A463F7D94B705698300771185DB19E78FABC +D0AE4FCE005B79C795B692F2D9C00B6A61D4B343C35E417BBA169EE82A2E4AE204A126B08A94191C +6E5B5E8328A147BD6ED5A5AD5C9143A50C47789DCFA699720336AFFBD6B1646D8C35139B0DC34340 +C76C7E4FD72DABF80BF64BA04742D07B380E20A678EBCB19057F2346FEC653F1302992279DCCD2E8 +902B2B8F78435AF3400DAE319E94E3148BA88C056701D524DD67FF368C85EC6366F31689A62FFC03 +4BE436588ECBB8B41E8C43112F3B65F50D20A5EC51A26FE899E74731B01C7771F75B76070DC0B223 +1845BE9C09670E65C4DF54C0FB36511B735251AEEBD13FDC0FCFC3134D8DA826E4100521CFEAB202 +B83267EB3F69AEEA25FDE1E9C86407E38AB4CA2D1B91607EFC96DDC5BB10FCD46DEAA5FFED196959 +12302ED55DFACFDC6F22C1528AE3A9902B75F46A489352C87B0F5056FD149DDE67219A3C3D1DD875 +BAD81AD443E0F90DC79D979E10B4262DD20B255B04BBBE5C988B23667135CC61D988928F5F07753A +C76807203942F6B62A178A1E5CBBFBBAEBE735D09B747BF00C360F2E2B6C9F2261F7DB018EDF40B1 +3DB4B7962D4D2F858E1FA6376F21ED9CEA4BEE07FFE27F136BF60352AFB71CDBFF7E65F6B4624E16 +C6E5AB7FF60B4711A178FEE691DD8D362B14175A69070FBC5B9EB15248E4DFDA3C9453335D0E1EB2 +7714E4624C7215A73B53402B02CF7AA05A14CE1E5AE161B75F218BF0EE1215D3CD925D5ACB7DBD2E +BD8E4405433F432C491999794C814D5B384B7A1C0AB28A11CBD0A6EA12678508CF86232309B0BBF7 +EEA929FB7951F5A6B367C5AC57DD7D3B3C1E2188DD4664D14FBC32B1A1EB53D7EE7F55F4D5C2D014 +528EBB7F0E595F7618721451EE6B83EB240A6C4D33377893D4EF542F47EB2845A09759D5554C74DC +5E9109FFC8C929CF1AC446D8149957720EE4FB670D3CA61378549DB992126B23618CF49361D6D4B1 +C73C3D37E4A4465ABB349CFA34E9351D1192E366267EDF02DE432ABC80792B0CFD41FFD0AAA42E63 +3F5B2177A351D33477C636CA573CB02C07F7F7A41C9F1BC4C112BD6459DD130757D2BD6F47495C3F +92E99522871DAC2865 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +cleartomark{restore}if + +%%EndFont +%%EndResource +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(2) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -118.485 -136.705 ] concat +1 w +[ ] 0 setdash +P +P +p +np 181 96 m +181 112 L +219 112 L +219 96 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 181.505 96.904 ] concat +1 w +[ ] 0 setdash +p +np -1.505 -1.904 m +-1.505 16.096 L +38.495 16.096 L +38.495 -1.904 L +cp +clip np +p +np 0.495 0.0956 m +0.495 13.096 L +37.495 13.096 L +37.495 0.0956 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.505 -0.904 m +-1.505 15.096 L +38.495 15.096 L +38.495 -0.904 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(5) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -181.505 -96.904 ] concat +1 w +[ ] 0 setdash +P +P +p +np 267 3 m +267 19 L +305 19 L +305 3 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 267.864 4.035 ] concat +1 w +[ ] 0 setdash +p +np -1.864 -2.035 m +-1.864 15.965 L +38.136 15.965 L +38.136 -2.035 L +cp +clip np +p +np 0.136 -0.0353 m +0.136 12.965 L +36.136 12.965 L +36.136 -0.0353 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.864 -1.035 m +-1.864 14.965 L +38.136 14.965 L +38.136 -1.035 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(0) N +P +p +31.5 9 m +(7) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -267.864 -4.035 ] concat +1 w +[ ] 0 setdash +P +P +p +np 295 86 m +295 102 L +328 102 L +328 86 L +cp +clip np +p +1 w +1 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +[1 0 0 1 295.617 86.954 ] concat +1 w +[ ] 0 setdash +p +np -1.617 -1.954 m +-1.617 16.046 L +33.383 16.046 L +33.383 -1.954 L +cp +clip np +p +np 0.383 0.0458 m +0.383 13.046 L +32.383 13.046 L +32.383 0.0458 L +cp +clip np +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +p +np -1.617 -0.954 m +-1.617 15.046 L +33.383 15.046 L +33.383 -0.954 L +cp +clip np +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +9.72 /Mathematica2 Msf +0 9 m +(\310) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +2.25 9 m +(q) N +9.72 /Mathematica2 Msf +7.5 9 m +(\310) N +9.72 /Mathematica1 Msf +12 9 m +(=) N +%%IncludeResource: font Times-Roman-MISO +%%IncludeFont: Times-Roman-MISO +9.72 /Times-Roman-MISO Msf +p +20.25 9 m +(0) N +P +p +24.75 9 m +(.) N +P +p +27 9 m +(1) N +P +P +[1 0 0 1 0 0 ] concat +1 w +[ ] 0 setdash +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +P +P +[1 0 0 1 -295.617 -86.954 ] concat +1 w +[ ] 0 setdash +P +P +P +P +P +1 w +0 g +0 g +[ ] 0 setdash +2 setlinecap +0 setlinejoin +10 setmiterlimit +%Trailer +%EOF diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex new file mode 100644 index 0000000000..76fd8e5f4c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/main.tex @@ -0,0 +1,9 @@ +\documentclass[a4paper]{article} + +\usepackage{graphicx} + +\begin{document} + +\includegraphics[width=\textwidth]{image.eps} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/options.json b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/options.json new file mode 100644 index 0000000000..a280541cfe --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "latex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.dvi b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.dvi new file mode 100644 index 0000000000..84888d7dd6 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.dvi differ diff --git a/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf new file mode 100644 index 0000000000..78e2440fa9 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/latex_compiler/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/main.tex b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/main.tex new file mode 100644 index 0000000000..28da61efd3 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/main.tex @@ -0,0 +1,8 @@ +\documentclass{article} +\usepackage{luacode} + +\begin{document} +\begin{luacode} +tex.print("Hello world") +\end{luacode} +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/options.json b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/options.json new file mode 100644 index 0000000000..96a05433b3 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "lualatex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/output.pdf b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/output.pdf new file mode 100644 index 0000000000..3dae1ad270 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/lualatex_compiler/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/main.tex b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/main.tex new file mode 100644 index 0000000000..fce282624c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/main.tex @@ -0,0 +1,12 @@ +\documentclass{article} + +\usepackage{makeidx} +\makeindex + +\begin{document} + +To solve various problems in Physics \index{Physics} it can useful to express any arbitrary piecewise-smooth function as a Fourier Series \index{Fourier Series} composed of multiple sine and cosine funcions. + +\printindex + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/output.pdf b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/output.pdf new file mode 100644 index 0000000000..d7c812de82 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/style.ist b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/style.ist new file mode 100644 index 0000000000..bdae874015 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/makeindex-custom-style/style.ist @@ -0,0 +1,7 @@ +heading_prefix "{\\bfseries\\hfil " +heading_suffix "\\hfil}\\nopagebreak\n" +headings_flag 1 +delim_0 "\\dotfill" +delim_1 "\\dotfill" +delim_2 "\\dotfill" + diff --git a/services/clsi/test/acceptance/fixtures/examples/makeindex/main.tex b/services/clsi/test/acceptance/fixtures/examples/makeindex/main.tex new file mode 100644 index 0000000000..fce282624c --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/makeindex/main.tex @@ -0,0 +1,12 @@ +\documentclass{article} + +\usepackage{makeidx} +\makeindex + +\begin{document} + +To solve various problems in Physics \index{Physics} it can useful to express any arbitrary piecewise-smooth function as a Fourier Series \index{Fourier Series} composed of multiple sine and cosine funcions. + +\printindex + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/makeindex/output.pdf b/services/clsi/test/acceptance/fixtures/examples/makeindex/output.pdf new file mode 100644 index 0000000000..451f166af8 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/makeindex/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/minted/main.tex b/services/clsi/test/acceptance/fixtures/examples/minted/main.tex new file mode 100644 index 0000000000..633abf72a5 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/minted/main.tex @@ -0,0 +1,10 @@ +\documentclass{article} +\usepackage{minted} +\begin{document} +\begin{minted}{c} +int main() { + printf("hello, world"); + return 0; +} +\end{minted} +\end{document} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/minted/output.pdf b/services/clsi/test/acceptance/fixtures/examples/minted/output.pdf new file mode 100644 index 0000000000..968569f004 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/minted/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/bibliography.bib b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/bibliography.bib new file mode 100644 index 0000000000..29728ba8ae --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/bibliography.bib @@ -0,0 +1,15 @@ +@book{DouglasAdams, + title={The Hitchhiker's Guide to the Galaxy}, + author={Adams, Douglas}, + isbn={9781417642595}, + url={http://books.google.com/books?id=W-xMPgAACAAJ}, + year={1995}, + publisher={San Val} +} + +@book{Tolkien, + title={The Hobbit}, + author={Tolkien, J. R. R.}, + year={1904?} +} + diff --git a/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/main.tex b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/main.tex new file mode 100644 index 0000000000..ff93b69371 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/main.tex @@ -0,0 +1,23 @@ +\documentclass{report} + +\usepackage{multibib} +\newcites{one}{First references} + +\begin{document} + +\chapter{First chapter} + +The answer to life the universe and everything is 42 \citeone{DouglasAdams} + +\bibliographystyleone{plain} +\bibliographyone{bibliography} + +\chapter{Second chapter} + +All that glitters is not gold \cite{Tolkien} + +\bibliographystyle{plain} +\bibliography{bibliography} + +\end{document} + diff --git a/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/one.bbl b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/one.bbl new file mode 100644 index 0000000000..3c63a37c7b --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/one.bbl @@ -0,0 +1,8 @@ +\begin{thebibliography}{1} + +\bibitem{DouglasAdams} +Douglas Adams. +\newblock {\em The Hitchhiker's Guide to the Galaxy}. +\newblock San Val, 1995. + +\end{thebibliography} diff --git a/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/output.bbl b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/output.bbl new file mode 100644 index 0000000000..df4ff8751b --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/output.bbl @@ -0,0 +1,8 @@ +\begin{thebibliography}{1} + +\bibitem{Tolkien} +J.~R.~R. Tolkien. +\newblock {\em The Hobbit}. +\newblock 1904? + +\end{thebibliography} diff --git a/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/output.pdf b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/output.pdf new file mode 100644 index 0000000000..e666964ea0 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/multibib_bibliography/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/nomenclature/main.tex b/services/clsi/test/acceptance/fixtures/examples/nomenclature/main.tex new file mode 100644 index 0000000000..3fb928cc89 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/nomenclature/main.tex @@ -0,0 +1,25 @@ +\documentclass{article} + +\usepackage{nomencl} +\makenomenclature + +\begin{document} + +\section*{Main equations} + +\begin{equation} +a=\frac{N}{A} +\end{equation}% + +\nomenclature{$a$}{The number of angels per unit area}% +\nomenclature{$N$}{The number of angels per needle point}% +\nomenclature{$A$}{The area of the needle point}% + +The equation $\sigma = m a$% +\nomenclature{$\sigma$}{The total mass of angels per unit area}% +\nomenclature{$m$}{The mass of one angel} +follows easily. + +\printnomenclature + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/nomenclature/output.pdf b/services/clsi/test/acceptance/fixtures/examples/nomenclature/output.pdf new file mode 100644 index 0000000000..828349615d Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/nomenclature/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/references_in_include/chapter1.tex b/services/clsi/test/acceptance/fixtures/examples/references_in_include/chapter1.tex new file mode 100644 index 0000000000..ded9bb4864 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/references_in_include/chapter1.tex @@ -0,0 +1 @@ +\ref{two} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/references_in_include/chapter2.tex b/services/clsi/test/acceptance/fixtures/examples/references_in_include/chapter2.tex new file mode 100644 index 0000000000..993a780a38 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/references_in_include/chapter2.tex @@ -0,0 +1,2 @@ +\section{Two} +\label{two} \ No newline at end of file diff --git a/services/clsi/test/acceptance/fixtures/examples/references_in_include/main.tex b/services/clsi/test/acceptance/fixtures/examples/references_in_include/main.tex new file mode 100644 index 0000000000..0956f98e27 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/references_in_include/main.tex @@ -0,0 +1,8 @@ +\documentclass{article} + +\begin{document} + +\include{chapter1} +\include{chapter2} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/references_in_include/output.pdf b/services/clsi/test/acceptance/fixtures/examples/references_in_include/output.pdf new file mode 100644 index 0000000000..bc3c8db14c Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/references_in_include/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/bibliography.bib b/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/bibliography.bib new file mode 100644 index 0000000000..5e796e057f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/bibliography.bib @@ -0,0 +1,9 @@ +@book{DouglasAdams, + title={The Hitchhiker's Guide to the Galaxy}, + author={Adams, Douglas}, + isbn={9781417642595}, + url={http://books.google.com/books?id=W-xMPgAACAAJ}, + year={1995}, + publisher={San Val} +} + diff --git a/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/main.tex b/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/main.tex new file mode 100644 index 0000000000..33030bd667 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/main.tex @@ -0,0 +1,10 @@ +\documentclass{article} + +\begin{document} + +The meaning of life, the universe and everything is 42 \cite{DouglasAdams} + +\bibliographystyle{plain} +\bibliography{bibliography} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/output.bbl b/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/output.bbl new file mode 100644 index 0000000000..3c63a37c7b --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/output.bbl @@ -0,0 +1,8 @@ +\begin{thebibliography}{1} + +\bibitem{DouglasAdams} +Douglas Adams. +\newblock {\em The Hitchhiker's Guide to the Galaxy}. +\newblock San Val, 1995. + +\end{thebibliography} diff --git a/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/output.pdf b/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/output.pdf new file mode 100644 index 0000000000..1a5e1f8a82 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/simple_bibliography/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/chapter2.tex b/services/clsi/test/acceptance/fixtures/examples/subdirectories/chapter2.tex new file mode 100644 index 0000000000..13a22d2b72 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/subdirectories/chapter2.tex @@ -0,0 +1 @@ +This is chapter2.tex, included from main.tex. It's not in the same directory but can still be found. diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/output.pdf b/services/clsi/test/acceptance/fixtures/examples/subdirectories/output.pdf new file mode 100644 index 0000000000..d8f5cf5a71 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/subdirectories/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/bibliography.bib b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/bibliography.bib new file mode 100644 index 0000000000..5654dbd22f --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/bibliography.bib @@ -0,0 +1,10 @@ +@book{DouglasAdams, + title={The Hitchhiker's Guide to the Galaxy}, + author={Adams, Douglas}, + isbn={9781417642595}, + url={http://books.google.com/books?id=W-xMPgAACAAJ}, + year={1995}, + publisher={San Val} +} + + diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/chapter1.tex b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/chapter1.tex new file mode 100644 index 0000000000..3056c071e2 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/chapter1.tex @@ -0,0 +1 @@ +This is chapter1.tex, included from main.tex diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/image.png b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/image.png new file mode 100644 index 0000000000..8660218b7b Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/image.png differ diff --git a/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/main.tex b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/main.tex new file mode 100644 index 0000000000..9972cde543 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/subdirectories/subdirectory/main.tex @@ -0,0 +1,19 @@ +\documentclass{article} + +\usepackage{graphicx} + +\begin{document} + +Hello world, I'm in a subdirectory \cite{DouglasAdams} + +\input{chapter1.tex} +\input{chapter2.tex} + +\begin{centering} +\includegraphics[width=0.5\textwidth]{image.png} +\end{centering} + +\bibliographystyle{plain} +\bibliography{bibliography} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex new file mode 100644 index 0000000000..6071bc26f0 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/main.tex @@ -0,0 +1,66 @@ +\RequirePackage{luatex85} +\documentclass[tikz]{standalone} + +\usepackage[compat=1.1.0]{tikz-feynman} + +\begin{document} +\feynmandiagram [horizontal=a to b] { + i1 -- [fermion] a -- [fermion] i2, + a -- [photon] b, + f1 -- [fermion] b -- [fermion] f2, +}; + +\feynmandiagram [horizontal=a to b] { + i1 [particle=\(e^{-}\)] -- [fermion] a -- [fermion] i2 [particle=\(e^{+}\)], + a -- [photon, edge label=\(\gamma\), momentum'=\(k\)] b, + f1 [particle=\(\mu^{+}\)] -- [fermion] b -- [fermion] f2 [particle=\(\mu^{-}\)], +}; + +\feynmandiagram [large, vertical=e to f] { + a -- [fermion] b -- [photon, momentum=\(k\)] c -- [fermion] d, + b -- [fermion, momentum'=\(p_{1}\)] e -- [fermion, momentum'=\(p_{2}\)] c, + e -- [gluon] f, + h -- [fermion] f -- [fermion] i, +}; + +\begin{tikzpicture} + \begin{feynman} + \vertex (a1) {\(\overline b\)}; + \vertex[right=1cm of a1] (a2); + \vertex[right=1cm of a2] (a3); + \vertex[right=1cm of a3] (a4) {\(b\)}; + \vertex[right=1cm of a4] (a5); + \vertex[right=2cm of a5] (a6) {\(u\)}; + + \vertex[below=2em of a1] (b1) {\(d\)}; + \vertex[right=1cm of b1] (b2); + \vertex[right=1cm of b2] (b3); + \vertex[right=1cm of b3] (b4) {\(\overline d\)}; + \vertex[below=2em of a6] (b5) {\(\overline d\)}; + + \vertex[above=of a6] (c1) {\(\overline u\)}; + \vertex[above=2em of c1] (c3) {\(d\)}; + \vertex at ($(c1)!0.5!(c3) - (1cm, 0)$) (c2); + + \diagram* { + {[edges=fermion] + (b1) -- (b2) -- (a2) -- (a1), + (b5) -- (b4) -- (b3) -- (a3) -- (a4) -- (a5) -- (a6), + }, + (a2) -- [boson, edge label=\(W\)] (a3), + (b2) -- [boson, edge label'=\(W\)] (b3), + + (c1) -- [fermion, out=180, in=-45] (c2) -- [fermion, out=45, in=180] (c3), + (a5) -- [boson, bend left, edge label=\(W^{-}\)] (c2), + }; + + \draw [decoration={brace}, decorate] (b1.south west) -- (a1.north west) + node [pos=0.5, left] {\(B^{0}\)}; + \draw [decoration={brace}, decorate] (c3.north east) -- (c1.south east) + node [pos=0.5, right] {\(\pi^{-}\)}; + \draw [decoration={brace}, decorate] (a6.north east) -- (b5.south east) + node [pos=0.5, right] {\(\pi^{+}\)}; + \end{feynman} +\end{tikzpicture} + +\end{document} diff --git a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/options.json b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/options.json new file mode 100644 index 0000000000..96a05433b3 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "lualatex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/output.pdf b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/output.pdf new file mode 100644 index 0000000000..367b0d0cae Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/tikz_feynman/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/Zapfino.ttf b/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/Zapfino.ttf new file mode 100644 index 0000000000..b68328074e Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/Zapfino.ttf differ diff --git a/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/main.tex b/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/main.tex new file mode 100644 index 0000000000..ad9438af53 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/main.tex @@ -0,0 +1,7 @@ +\documentclass[11pt]{article} +\usepackage{fontspec} +\setmainfont[Ligatures=TeX]{Zapfino.ttf} +\begin{document} +The quick brown fox jumps over the lazy dog +\end{document} + diff --git a/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/options.json b/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/options.json new file mode 100644 index 0000000000..a2e0c09897 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/options.json @@ -0,0 +1,3 @@ +{ + "compiler": "xelatex" +} diff --git a/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/output.pdf b/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/output.pdf new file mode 100644 index 0000000000..85e84adf86 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/examples/xelatex_compiler/output.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/lion.png b/services/clsi/test/acceptance/fixtures/lion.png new file mode 100644 index 0000000000..64eb549922 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/lion.png differ diff --git a/services/clsi/test/acceptance/fixtures/minimal.pdf b/services/clsi/test/acceptance/fixtures/minimal.pdf new file mode 100644 index 0000000000..d578e90567 Binary files /dev/null and b/services/clsi/test/acceptance/fixtures/minimal.pdf differ diff --git a/services/clsi/test/acceptance/fixtures/naugty_strings.txt b/services/clsi/test/acceptance/fixtures/naugty_strings.txt new file mode 100644 index 0000000000..92eb1ddce6 --- /dev/null +++ b/services/clsi/test/acceptance/fixtures/naugty_strings.txt @@ -0,0 +1,626 @@ +\documentclass{article} +\usepackage[utf8]{inputenc} + +\title{eee} +\author{henry.oswald } +\date{September 2015} + +\usepackage{natbib} +\usepackage{graphicx} + +\begin{document} + +\maketitle + +\section{Introduction} + +Encoding: utf8 + +# Reserved Strings +# +# Strings which may be used elsewhere in code + +undefined +undef +null +NULL +(null) +nil +NIL +true +false +True +False +None +\ +\\ + +# Numeric Strings +# +# Strings which can be interpreted as numeric + +0 +1 +1.00 +$1.00 +1/2 +1E2 +1E02 +1E+02 +-1 +-1.00 +-$1.00 +-1/2 +-1E2 +-1E02 +-1E+02 +1/0 +0/0 +-2147483648/-1 +-9223372036854775808/-1 +0.00 +0..0 +. +0.0.0 +0,00 +0,,0 +, +0,0,0 +0.0/0 +1.0/0.0 +0.0/0.0 +1,0/0,0 +0,0/0,0 +--1 +- +-. +-, +999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +NaN +Infinity +-Infinity +0x0 +0xffffffff +0xffffffffffffffff +0xabad1dea +123456789012345678901234567890123456789 +1,000.00 +1 000.00 +1'000.00 +1,000,000.00 +1 000 000.00 +1'000'000.00 +1.000,00 +1 000,00 +1'000,00 +1.000.000,00 +1 000 000,00 +1'000'000,00 +01000 +08 +09 +2.2250738585072011e-308 + +# Special Characters +# +# Strings which contain common special ASCII characters (may need to be escaped) + +,./;'[]\-= +<>?:"{}|_+ +!@#$%^&*()`~ + +# Unicode Symbols +# +# Strings which contain common unicode symbols (e.g. smart quotes) + +Ω≈ç√∫˜µ≤≥÷ +åß∂ƒ©˙∆˚¬…æ +œ∑´®†¥¨ˆøπ“‘ +¡™£¢∞§¶•ªº–≠ +¸˛Ç◊ı˜Â¯˘¿ +ÅÍÎÏ˝ÓÔÒÚÆ☃ +Œ„´‰ˇÁ¨ˆØ∏”’ +`⁄€‹›fifl‡°·‚—± +⅛⅜⅝⅞ +ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя +٠١٢٣٤٥٦٧٨٩ + +# Unicode Subscript/Superscript +# +# Strings which contain unicode subscripts/superscripts; can cause rendering issues + +⁰⁴⁵ +₀₁₂ +⁰⁴⁵₀₁₂ + +# Quotation Marks +# +# Strings which contain misplaced quotation marks; can cause encoding errors + +' +" +'' +"" +'"' +"''''"'" +"'"'"''''" + +# Two-Byte Characters +# +# Strings which contain two-byte characters: can cause rendering issues or character-length issues + +田中さんにあげて下さい +パーティーへ行かないか +和製漢語 +部落格 +사회과학원 어학연구소 +찦차를 타고 온 펲시맨과 쑛다리 똠방각하 +社會科學院語學研究所 +울란바토르 +𠜎𠜱𠝹𠱓𠱸𠲖𠳏 + +# Japanese Emoticons +# +# Strings which consists of Japanese-style emoticons which are popular on the web + +ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ +(。◕ ∀ ◕。) +`ィ(´∀`∩ +__ロ(,_,*) +・( ̄∀ ̄)・:*: +゚・✿ヾ╲(。◕‿◕。)╱✿・゚ +,。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’ +(╯°□°)╯︵ ┻━┻) +(ノಥ益ಥ)ノ ┻━┻ +( ͡° ͜ʖ ͡°) + +# Emoji +# +# Strings which contain Emoji; should be the same behavior as two-byte characters, but not always + +😍 +👩🏽 +👾 🙇 💁 🙅 🙆 🙋 🙎 🙍 +🐵 🙈 🙉 🙊 +❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙 +✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿 +🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧 +0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 + +# Unicode Numbers +# +# Strings which contain unicode numbers; if the code is localized, it should see the input as numeric + +123 +١٢٣ + +# Right-To-Left Strings +# +# Strings which contain text that should be rendered RTL if possible (e.g. Arabic, Hebrew) + +ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو. +בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ +הָיְתָהtestالصفحات التّحول +﷽ +ﷺ + +# Unicode Spaces +# +# Strings which contain unicode space characters with special properties (c.f. https://www.cs.tut.fi/~jkorpela/chars/spaces.html) + +​ +  +᠎ +  + +␣ +␢ +␡ + +# Trick Unicode +# +# Strings which contain unicode with unusual properties (e.g. Right-to-left override) (c.f. http://www.unicode.org/charts/PDF/U2000.pdf) + +‪‪test‪ +‫test‫ +
test
 +test⁠test‫ +⁦test⁧ + +# Zalgo Text +# +# Strings which contain "corrupted" text. The corruption will not appear in non-HTML text, however. (via http://www.eeemo.net) + +Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣ +̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰ +̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟ +̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕ +Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮ + +# Unicode Upsidedown +# +# Strings which contain unicode with an "upsidedown" effect (via http://www.upsidedowntext.com) + +˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥ +00˙Ɩ$- + +# Unicode font +# +# Strings which contain bold/italic/etc. versions of normal characters + +The quick brown fox jumps over the lazy dog +𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠 +𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌 +𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈 +𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰 +𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘 +𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐 +⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢ + +# Script Injection +# +# Strings which attempt to invoke a benign script injection; shows vulnerability to XSS + + +<script>alert('123');</script> + + +"> +'> +> + +< / script >< script >alert(123)< / script > + onfocus=JaVaSCript:alert(123) autofocus +" onfocus=JaVaSCript:alert(123) autofocus +' onfocus=JaVaSCript:alert(123) autofocus +<script>alert(123)</script> +ript>alert(123)ript> +--> +";alert(123);t=" +';alert(123);t=' +JavaSCript:alert(123) +;alert(123); +src=JaVaSCript:prompt(132) +">javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +'`"><\x3Cscript>javascript:alert(1) +'`"><\x00script>javascript:alert(1) +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +XXX + + + +<a href=http://foo.bar/#x=`y></a><img alt="`><img src=x:x onerror=javascript:alert(1)></a>"> +<!--[if]><script>javascript:alert(1)</script --> +<!--[if<img src=x onerror=javascript:alert(1)//]> --> +<script src="/\%(jscript)s"></script> +<script src="\\%(jscript)s"></script> +<IMG """><SCRIPT>alert("XSS")</SCRIPT>"> +<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))> +<IMG SRC=# onmouseover="alert('xxs')"> +<IMG SRC= onmouseover="alert('xxs')"> +<IMG onmouseover="alert('xxs')"> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out +<IMG SRC="  javascript:alert('XSS');"> +<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT> +<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")> +<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT> +<<SCRIPT>alert("XSS");//<</SCRIPT> +<SCRIPT SRC=http://ha.ckers.org/xss.js?< B > +<SCRIPT SRC=//ha.ckers.org/.j> +<IMG SRC="javascript:alert('XSS')" +<iframe src=http://ha.ckers.org/scriptlet.html < +\";alert('XSS');// +<plaintext> + +# SQL Injection +# +# Strings which can cause a SQL injection if inputs are not sanitized + +1;DROP TABLE users +1'; DROP TABLE users-- 1 +' OR 1=1 -- 1 +' OR '1'='1 + +# Server Code Injection +# +# Strings which can cause user to run code on server as a privileged user (c.f. https://news.ycombinator.com/item?id=7665153) + +- +-- +--version +--help +$USER +/dev/null; touch /tmp/blns.fail ; echo +`touch /tmp/blns.fail` +$(touch /tmp/blns.fail) +@{[system "touch /tmp/blns.fail"]} + +# Command Injection (Ruby) +# +# Strings which can call system commands within Ruby/Rails applications + +eval("puts 'hello world'") +System("ls -al /") +`ls -al /` +Kernel.exec("ls -al /") +Kernel.exit(1) +%x('ls -al /') + +# XXE Injection (XML) +# +# String which can reveal system files when parsed by a badly configured XML parser + +<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo> + +# Unwanted Interpolation +# +# Strings which can be accidentally expanded into different strings if evaluated in the wrong context, e.g. used as a printf format string or via Perl or shell eval. Might expose sensitive data from the program doing the interpolation, or might just represent the wrong string. + +$HOME +$ENV{'HOME'} +%d +%s +%*.*s + +# File Inclusion +# +# Strings which can cause user to pull in files that should not be a part of a web server + +../../../../../../../../../../../etc/passwd%00 +../../../../../../../../../../../etc/hosts + +# Known CVEs and Vulnerabilities +# +# Strings that test for known vulnerabilities + +() { 0; }; touch /tmp/blns.shellshock1.fail; +() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; } + +# MSDOS/Windows Special Filenames +# +# Strings which are reserved characters in MSDOS/Windows + +CON +PRN +AUX +CLOCK$ +NUL +A: +ZZ: +COM1 +LPT1 +LPT2 +LPT3 +COM2 +COM3 +COM4 + +# Scunthorpe Problem +# +# Innocuous strings which may be blocked by profanity filters (https://en.wikipedia.org/wiki/Scunthorpe_problem) + +Scunthorpe General Hospital +Penistone Community Church +Lightwater Country Park +Jimmy Clitheroe +Horniman Museum +shitake mushrooms +RomansInSussex.co.uk +http://www.cum.qc.ca/ +Craig Cockburn, Software Specialist +Linda Callahan +Dr. Herman I. Libshitz +magna cum laude +Super Bowl XXX +medieval erection of parapets +evaluate +mocha +expression +Arsenal canal +classic +Tyson Gay + +# Human injection +# +# Strings which may cause human to reinterpret worldview + +If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you. + +# Terminal escape codes +# +# Strings which punish the fools who use cat/type on this file + +Roses are red, violets are blue. Hope you enjoy terminal hue +But now...for my greatest trick... +The quick brown fox... [Beeeep] + +# iOS Vulnerability +# +# Strings which crashed iMessage in iOS versions 8.3 and earlier + +Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗 + + +\end{document} diff --git a/services/clsi/test/acceptance/js/AllowedImageNamesTests.js b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js new file mode 100644 index 0000000000..e33ccb46e4 --- /dev/null +++ b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js @@ -0,0 +1,181 @@ +const Client = require('./helpers/Client') +const ClsiApp = require('./helpers/ClsiApp') +const { expect } = require('chai') + +describe('AllowedImageNames', function () { + beforeEach(function (done) { + this.project_id = Client.randomId() + this.request = { + options: { + imageName: undefined, + }, + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +`, + }, + ], + } + ClsiApp.ensureRunning(done) + }) + + describe('with a valid name', function () { + beforeEach(function (done) { + this.request.options.imageName = process.env.TEXLIVE_IMAGE + + Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error + this.res = res + this.body = body + done(error) + }) + }) + it('should return success', function () { + expect(this.res.statusCode).to.equal(200) + }) + + it('should return a PDF', function () { + let pdf + try { + pdf = Client.getOutputFile(this.body, 'pdf') + } catch (e) {} + expect(pdf).to.exist + }) + }) + + describe('with an invalid name', function () { + beforeEach(function (done) { + this.request.options.imageName = 'something/evil:1337' + Client.compile(this.project_id, this.request, (error, res, body) => { + this.error = error + this.res = res + this.body = body + done(error) + }) + }) + it('should return non success', function () { + expect(this.res.statusCode).to.not.equal(200) + }) + + it('should not return a PDF', function () { + let pdf + try { + pdf = Client.getOutputFile(this.body, 'pdf') + } catch (e) {} + expect(pdf).to.not.exist + }) + }) + + describe('syncToCode', function () { + beforeEach(function (done) { + Client.compile(this.project_id, this.request, done) + }) + it('should error out with an invalid imageName', function (done) { + Client.syncFromCodeWithImage( + this.project_id, + 'main.tex', + 3, + 5, + 'something/evil:1337', + (error, body) => { + expect(String(error)).to.include('statusCode=400') + expect(body).to.equal('invalid image') + done() + } + ) + }) + + it('should produce a mapping a valid imageName', function (done) { + Client.syncFromCodeWithImage( + this.project_id, + 'main.tex', + 3, + 5, + process.env.TEXLIVE_IMAGE, + (error, result) => { + expect(error).to.not.exist + expect(result).to.deep.equal({ + pdf: [ + { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 }, + ], + }) + done() + } + ) + }) + }) + + describe('syncToPdf', function () { + beforeEach(function (done) { + Client.compile(this.project_id, this.request, done) + }) + it('should error out with an invalid imageName', function (done) { + Client.syncFromPdfWithImage( + this.project_id, + 'main.tex', + 100, + 200, + 'something/evil:1337', + (error, body) => { + expect(String(error)).to.include('statusCode=400') + expect(body).to.equal('invalid image') + done() + } + ) + }) + + it('should produce a mapping a valid imageName', function (done) { + Client.syncFromPdfWithImage( + this.project_id, + 1, + 100, + 200, + process.env.TEXLIVE_IMAGE, + (error, result) => { + expect(error).to.not.exist + expect(result).to.deep.equal({ + code: [{ file: 'main.tex', line: 3, column: -1 }], + }) + done() + } + ) + }) + }) + + describe('wordcount', function () { + beforeEach(function (done) { + Client.compile(this.project_id, this.request, done) + }) + it('should error out with an invalid imageName', function (done) { + Client.wordcountWithImage( + this.project_id, + 'main.tex', + 'something/evil:1337', + (error, body) => { + expect(String(error)).to.include('statusCode=400') + expect(body).to.equal('invalid image') + done() + } + ) + }) + + it('should produce a texcout a valid imageName', function (done) { + Client.wordcountWithImage( + this.project_id, + 'main.tex', + process.env.TEXLIVE_IMAGE, + (error, result) => { + expect(error).to.not.exist + expect(result).to.exist + expect(result.texcount).to.exist + done() + } + ) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/BrokenLatexFileTests.js b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js new file mode 100644 index 0000000000..71e9956c0d --- /dev/null +++ b/services/clsi/test/acceptance/js/BrokenLatexFileTests.js @@ -0,0 +1,87 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require('./helpers/Client') +const request = require('request') +const ClsiApp = require('./helpers/ClsiApp') + +describe('Broken LaTeX file', function () { + before(function (done) { + this.broken_request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{articl % :( +\\begin{documen % :( +Broken +\\end{documen % :(\ +`, + }, + ], + } + this.correct_request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +`, + }, + ], + } + return ClsiApp.ensureRunning(done) + }) + + describe('on first run', function () { + before(function (done) { + this.project_id = Client.randomId() + return Client.compile( + this.project_id, + this.broken_request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + + return it('should return a failure status', function () { + return this.body.compile.status.should.equal('failure') + }) + }) + + return describe('on second run', function () { + before(function (done) { + this.project_id = Client.randomId() + return Client.compile(this.project_id, this.correct_request, () => { + return Client.compile( + this.project_id, + this.broken_request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) + + return it('should return a failure status', function () { + return this.body.compile.status.should.equal('failure') + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/DeleteOldFilesTest.js b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js new file mode 100644 index 0000000000..09eea1a948 --- /dev/null +++ b/services/clsi/test/acceptance/js/DeleteOldFilesTest.js @@ -0,0 +1,72 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require('./helpers/Client') +const request = require('request') +const ClsiApp = require('./helpers/ClsiApp') + +describe('Deleting Old Files', function () { + before(function (done) { + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +`, + }, + ], + } + return ClsiApp.ensureRunning(done) + }) + + return describe('on first run', function () { + before(function (done) { + this.project_id = Client.randomId() + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + + it('should return a success status', function () { + return this.body.compile.status.should.equal('success') + }) + + return describe('after file has been deleted', function () { + before(function (done) { + this.request.resources = [] + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + + return it('should return a failure status', function () { + return this.body.compile.status.should.equal('failure') + }) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/ExampleDocumentTests.js b/services/clsi/test/acceptance/js/ExampleDocumentTests.js new file mode 100644 index 0000000000..84758c4d65 --- /dev/null +++ b/services/clsi/test/acceptance/js/ExampleDocumentTests.js @@ -0,0 +1,291 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-path-concat, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require('./helpers/Client') +const request = require('request') +const fs = require('fs') +const fsExtra = require('fs-extra') +const ChildProcess = require('child_process') +const ClsiApp = require('./helpers/ClsiApp') +const logger = require('logger-sharelatex') +const Path = require('path') +const fixturePath = path => { + if (path.slice(0, 3) === 'tmp') { + return '/tmp/clsi_acceptance_tests' + path.slice(3) + } + return Path.normalize(__dirname + '/../fixtures/' + path) +} +const process = require('process') +console.log( + process.pid, + process.ppid, + process.getuid(), + process.getgroups(), + 'PID' +) + +const MOCHA_LATEX_TIMEOUT = 60 * 1000 + +const convertToPng = function (pdfPath, pngPath, callback) { + if (callback == null) { + callback = function (error) {} + } + const command = `convert ${fixturePath(pdfPath)} ${fixturePath(pngPath)}` + console.log('COMMAND') + console.log(command) + const convert = ChildProcess.exec(command) + const stdout = '' + convert.stdout.on('data', chunk => console.log('STDOUT', chunk.toString())) + convert.stderr.on('data', chunk => console.log('STDERR', chunk.toString())) + return convert.on('exit', () => callback()) +} + +const compare = function (originalPath, generatedPath, callback) { + if (callback == null) { + callback = function (error, same) {} + } + const diff_file = `${fixturePath(generatedPath)}-diff.png` + const proc = ChildProcess.exec( + `compare -metric mae ${fixturePath(originalPath)} ${fixturePath( + generatedPath + )} ${diff_file}` + ) + let stderr = '' + proc.stderr.on('data', chunk => (stderr += chunk)) + return proc.on('exit', () => { + if (stderr.trim() === '0 (0)') { + // remove output diff if test matches expected image + fs.unlink(diff_file, err => { + if (err) { + throw err + } + }) + return callback(null, true) + } else { + console.log('compare result', stderr) + return callback(null, false) + } + }) +} + +const checkPdfInfo = function (pdfPath, callback) { + if (callback == null) { + callback = function (error, output) {} + } + const proc = ChildProcess.exec(`pdfinfo ${fixturePath(pdfPath)}`) + let stdout = '' + proc.stdout.on('data', chunk => (stdout += chunk)) + proc.stderr.on('data', chunk => console.log('STDERR', chunk.toString())) + return proc.on('exit', () => { + if (stdout.match(/Optimized:\s+yes/)) { + return callback(null, true) + } else { + return callback(null, false) + } + }) +} + +const compareMultiplePages = function (project_id, callback) { + if (callback == null) { + callback = function (error) {} + } + var compareNext = function (page_no, callback) { + const path = `tmp/${project_id}-source-${page_no}.png` + return fs.stat(fixturePath(path), (error, stat) => { + if (error != null) { + return callback() + } else { + return compare( + `tmp/${project_id}-source-${page_no}.png`, + `tmp/${project_id}-generated-${page_no}.png`, + (error, same) => { + if (error != null) { + throw error + } + same.should.equal(true) + return compareNext(page_no + 1, callback) + } + ) + } + }) + } + return compareNext(0, callback) +} + +const comparePdf = function (project_id, example_dir, callback) { + if (callback == null) { + callback = function (error) {} + } + console.log('CONVERT') + console.log(`tmp/${project_id}.pdf`, `tmp/${project_id}-generated.png`) + return convertToPng( + `tmp/${project_id}.pdf`, + `tmp/${project_id}-generated.png`, + error => { + if (error != null) { + throw error + } + return convertToPng( + `examples/${example_dir}/output.pdf`, + `tmp/${project_id}-source.png`, + error => { + if (error != null) { + throw error + } + return fs.stat( + fixturePath(`tmp/${project_id}-source-0.png`), + (error, stat) => { + if (error != null) { + return compare( + `tmp/${project_id}-source.png`, + `tmp/${project_id}-generated.png`, + (error, same) => { + if (error != null) { + throw error + } + same.should.equal(true) + return callback() + } + ) + } else { + return compareMultiplePages(project_id, error => { + if (error != null) { + throw error + } + return callback() + }) + } + } + ) + } + ) + } + ) +} + +const downloadAndComparePdf = function ( + project_id, + example_dir, + url, + callback +) { + if (callback == null) { + callback = function (error) {} + } + const writeStream = fs.createWriteStream(fixturePath(`tmp/${project_id}.pdf`)) + request.get(url).pipe(writeStream) + console.log('writing file out', fixturePath(`tmp/${project_id}.pdf`)) + return writeStream.on('close', () => { + return checkPdfInfo(`tmp/${project_id}.pdf`, (error, optimised) => { + if (error != null) { + throw error + } + optimised.should.equal(true) + return comparePdf(project_id, example_dir, callback) + }) + }) +} + +Client.runServer(4242, fixturePath('examples')) + +describe('Example Documents', function () { + before(function (done) { + ClsiApp.ensureRunning(done) + }) + before(function (done) { + fsExtra.remove(fixturePath('tmp'), done) + }) + before(function (done) { + fs.mkdir(fixturePath('tmp'), done) + }) + after(function (done) { + fsExtra.remove(fixturePath('tmp'), done) + }) + + return Array.from(fs.readdirSync(fixturePath('examples'))).map(example_dir => + (example_dir => + describe(example_dir, function () { + before(function () { + return (this.project_id = Client.randomId() + '_' + example_dir) + }) + + it('should generate the correct pdf', function (done) { + this.timeout(MOCHA_LATEX_TIMEOUT) + return Client.compileDirectory( + this.project_id, + fixturePath('examples'), + example_dir, + 4242, + (error, res, body) => { + if ( + error || + __guard__( + body != null ? body.compile : undefined, + x => x.status + ) === 'failure' + ) { + console.log('DEBUG: error', error, 'body', JSON.stringify(body)) + return done(new Error('Compile failed')) + } + const pdf = Client.getOutputFile(body, 'pdf') + return downloadAndComparePdf( + this.project_id, + example_dir, + pdf.url, + done + ) + } + ) + }) + + return it('should generate the correct pdf on the second run as well', function (done) { + this.timeout(MOCHA_LATEX_TIMEOUT) + return Client.compileDirectory( + this.project_id, + fixturePath('examples'), + example_dir, + 4242, + (error, res, body) => { + if ( + error || + __guard__( + body != null ? body.compile : undefined, + x => x.status + ) === 'failure' + ) { + console.log('DEBUG: error', error, 'body', JSON.stringify(body)) + return done(new Error('Compile failed')) + } + const pdf = Client.getOutputFile(body, 'pdf') + return downloadAndComparePdf( + this.project_id, + example_dir, + pdf.url, + done + ) + } + ) + }) + }))(example_dir) + ) +}) + +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/test/acceptance/js/SimpleLatexFileTests.js b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js new file mode 100644 index 0000000000..b2152f39ed --- /dev/null +++ b/services/clsi/test/acceptance/js/SimpleLatexFileTests.js @@ -0,0 +1,70 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require('./helpers/Client') +const request = require('request') +const ClsiApp = require('./helpers/ClsiApp') + +describe('Simple LaTeX file', function () { + before(function (done) { + this.project_id = Client.randomId() + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +`, + }, + ], + } + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) + + it('should return the PDF', function () { + const pdf = Client.getOutputFile(this.body, 'pdf') + return pdf.type.should.equal('pdf') + }) + + it('should return the log', function () { + const log = Client.getOutputFile(this.body, 'log') + return log.type.should.equal('log') + }) + + it('should provide the pdf for download', function (done) { + const pdf = Client.getOutputFile(this.body, 'pdf') + return request.get(pdf.url, (error, res, body) => { + res.statusCode.should.equal(200) + return done() + }) + }) + + return it('should provide the log for download', function (done) { + const log = Client.getOutputFile(this.body, 'pdf') + return request.get(log.url, (error, res, body) => { + res.statusCode.should.equal(200) + return done() + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/Stats.js b/services/clsi/test/acceptance/js/Stats.js new file mode 100644 index 0000000000..4f071abe5f --- /dev/null +++ b/services/clsi/test/acceptance/js/Stats.js @@ -0,0 +1,16 @@ +const request = require('request') +const Settings = require('@overleaf/settings') +after(function (done) { + request( + { + url: `${Settings.apis.clsi.url}/metrics`, + }, + (err, response, body) => { + if (err) return done(err) + console.error('-- metrics --') + console.error(body) + console.error('-- metrics --') + done() + } + ) +}) diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js new file mode 100644 index 0000000000..3bed29aac7 --- /dev/null +++ b/services/clsi/test/acceptance/js/SynctexTests.js @@ -0,0 +1,182 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require('./helpers/Client') +const request = require('request') +const { expect } = require('chai') +const ClsiApp = require('./helpers/ClsiApp') +const crypto = require('crypto') + +describe('Syncing', function () { + before(function (done) { + const content = `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +` + this.request = { + resources: [ + { + path: 'main.tex', + content, + }, + ], + } + this.project_id = Client.randomId() + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) + + describe('from code to pdf', function () { + return it('should return the correct location', function (done) { + return Client.syncFromCode( + this.project_id, + 'main.tex', + 3, + 5, + (error, pdfPositions) => { + if (error != null) { + throw error + } + expect(pdfPositions).to.deep.equal({ + pdf: [ + { page: 1, h: 133.77, v: 134.76, height: 6.92, width: 343.71 }, + ], + }) + return done() + } + ) + }) + }) + + describe('from pdf to code', function () { + return it('should return the correct location', function (done) { + return Client.syncFromPdf( + this.project_id, + 1, + 100, + 200, + (error, codePositions) => { + if (error != null) { + throw error + } + expect(codePositions).to.deep.equal({ + code: [{ file: 'main.tex', line: 3, column: -1 }], + }) + return done() + } + ) + }) + }) + + describe('when the project directory is not available', function () { + before(function () { + this.other_project_id = Client.randomId() + }) + describe('from code to pdf', function () { + it('should return a 404 response', function (done) { + return Client.syncFromCode( + this.other_project_id, + 'main.tex', + 3, + 5, + (error, body) => { + expect(String(error)).to.include('statusCode=404') + expect(body).to.equal('Not Found') + return done() + } + ) + }) + }) + describe('from pdf to code', function () { + it('should return a 404 response', function (done) { + return Client.syncFromPdf( + this.other_project_id, + 1, + 100, + 200, + (error, body) => { + expect(String(error)).to.include('statusCode=404') + expect(body).to.equal('Not Found') + return done() + } + ) + }) + }) + }) + + describe('when the synctex file is not available', function () { + before(function (done) { + this.broken_project_id = Client.randomId() + const content = 'this is not valid tex' // not a valid tex file + this.request = { + resources: [ + { + path: 'main.tex', + content, + }, + ], + } + Client.compile( + this.broken_project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + + describe('from code to pdf', function () { + it('should return a 404 response', function (done) { + return Client.syncFromCode( + this.broken_project_id, + 'main.tex', + 3, + 5, + (error, body) => { + expect(String(error)).to.include('statusCode=404') + expect(body).to.equal('Not Found') + return done() + } + ) + }) + }) + describe('from pdf to code', function () { + it('should return a 404 response', function (done) { + return Client.syncFromPdf( + this.broken_project_id, + 1, + 100, + 200, + (error, body) => { + expect(String(error)).to.include('statusCode=404') + expect(body).to.equal('Not Found') + return done() + } + ) + }) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/TimeoutTests.js b/services/clsi/test/acceptance/js/TimeoutTests.js new file mode 100644 index 0000000000..bca8ae71d2 --- /dev/null +++ b/services/clsi/test/acceptance/js/TimeoutTests.js @@ -0,0 +1,61 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require('./helpers/Client') +const request = require('request') +const ClsiApp = require('./helpers/ClsiApp') + +describe('Timed out compile', function () { + before(function (done) { + this.request = { + options: { + timeout: 10, + }, // seconds + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\begin{document} +\\def\\x{Hello!\\par\\x} +\\x +\\end{document}\ +`, + }, + ], + } + this.project_id = Client.randomId() + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) + + it('should return a timeout error', function () { + return this.body.compile.error.should.equal('container timed out') + }) + + it('should return a timedout status', function () { + return this.body.compile.status.should.equal('timedout') + }) + + return it('should return the log output file name', function () { + const outputFilePaths = this.body.compile.outputFiles.map(x => x.path) + return outputFilePaths.should.include('output.log') + }) +}) diff --git a/services/clsi/test/acceptance/js/UrlCachingTests.js b/services/clsi/test/acceptance/js/UrlCachingTests.js new file mode 100644 index 0000000000..05a8b26e6f --- /dev/null +++ b/services/clsi/test/acceptance/js/UrlCachingTests.js @@ -0,0 +1,369 @@ +/* eslint-disable + no-path-concat, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require('./helpers/Client') +const sinon = require('sinon') +const ClsiApp = require('./helpers/ClsiApp') + +const host = 'localhost' + +const Server = { + run() { + const express = require('express') + const app = express() + + const staticServer = express.static(__dirname + '/../fixtures/') + app.get('/:random_id/*', (req, res, next) => { + this.getFile(req.url) + req.url = `/${req.params[0]}` + return staticServer(req, res, next) + }) + + return app.listen(31415, host) + }, + + getFile() {}, + + randomId() { + return Math.random().toString(16).slice(2) + }, +} + +Server.run() + +describe('Url Caching', function () { + describe('Downloading an image for the first time', function () { + before(function (done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +`, + }, + { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + }, + ], + } + + sinon.spy(Server, 'getFile') + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) + + afterEach(function () { + return Server.getFile.restore() + }) + + return it('should download the image', function () { + return Server.getFile.calledWith(`/${this.file}`).should.equal(true) + }) + }) + + describe('When an image is in the cache and the last modified date is unchanged', function () { + before(function (done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +`, + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: Date.now(), + }), + ], + } + + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + sinon.spy(Server, 'getFile') + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + } + ) + }) + + after(function () { + return Server.getFile.restore() + }) + + return it('should not download the image again', function () { + return Server.getFile.called.should.equal(false) + }) + }) + + describe('When an image is in the cache and the last modified date is advanced', function () { + before(function (done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +`, + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()), + }), + ], + } + + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + sinon.spy(Server, 'getFile') + this.image_resource.modified = new Date(this.last_modified + 3000) + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + } + ) + }) + + afterEach(function () { + return Server.getFile.restore() + }) + + return it('should download the image again', function () { + return Server.getFile.called.should.equal(true) + }) + }) + + describe('When an image is in the cache and the last modified date is further in the past', function () { + before(function (done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +`, + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()), + }), + ], + } + + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + sinon.spy(Server, 'getFile') + this.image_resource.modified = new Date(this.last_modified - 3000) + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + } + ) + }) + + afterEach(function () { + return Server.getFile.restore() + }) + + return it('should not download the image again', function () { + return Server.getFile.called.should.equal(false) + }) + }) + + describe('When an image is in the cache and the last modified date is not specified', function () { + before(function (done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +`, + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()), + }), + ], + } + + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + sinon.spy(Server, 'getFile') + delete this.image_resource.modified + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + } + ) + }) + + afterEach(function () { + return Server.getFile.restore() + }) + + return it('should download the image again', function () { + return Server.getFile.called.should.equal(true) + }) + }) + + return describe('After clearing the cache', function () { + before(function (done) { + this.project_id = Client.randomId() + this.file = `${Server.randomId()}/lion.png` + this.request = { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\usepackage{graphicx} +\\begin{document} +\\includegraphics{lion.png} +\\end{document}\ +`, + }, + (this.image_resource = { + path: 'lion.png', + url: `http://${host}:31415/${this.file}`, + modified: (this.last_modified = Date.now()), + }), + ], + } + + return Client.compile(this.project_id, this.request, error => { + if (error != null) { + throw error + } + return Client.clearCache(this.project_id, (error, res, body) => { + if (error != null) { + throw error + } + sinon.spy(Server, 'getFile') + return Client.compile( + this.project_id, + this.request, + (error1, res1, body1) => { + this.error = error1 + this.res = res1 + this.body = body1 + return done() + } + ) + }) + }) + }) + + afterEach(function () { + return Server.getFile.restore() + }) + + return it('should download the image again', function () { + return Server.getFile.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/WordcountTests.js b/services/clsi/test/acceptance/js/WordcountTests.js new file mode 100644 index 0000000000..d3fa7d2b94 --- /dev/null +++ b/services/clsi/test/acceptance/js/WordcountTests.js @@ -0,0 +1,71 @@ +/* eslint-disable + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Client = require('./helpers/Client') +const request = require('request') +const { expect } = require('chai') +const path = require('path') +const fs = require('fs') +const ClsiApp = require('./helpers/ClsiApp') + +describe('Syncing', function () { + before(function (done) { + this.request = { + resources: [ + { + path: 'main.tex', + content: fs.readFileSync( + path.join(__dirname, '../fixtures/naugty_strings.txt'), + 'utf-8' + ), + }, + ], + } + this.project_id = Client.randomId() + return ClsiApp.ensureRunning(() => { + return Client.compile( + this.project_id, + this.request, + (error, res, body) => { + this.error = error + this.res = res + this.body = body + return done() + } + ) + }) + }) + + return describe('wordcount file', function () { + return it('should return wordcount info', function (done) { + return Client.wordcount(this.project_id, 'main.tex', (error, result) => { + if (error != null) { + throw error + } + expect(result).to.deep.equal({ + texcount: { + encode: 'utf8', + textWords: 2281, + headWords: 2, + outside: 0, + headers: 2, + elements: 0, + mathInline: 6, + mathDisplay: 0, + errors: 0, + messages: '', + }, + }) + return done() + }) + }) + }) +}) diff --git a/services/clsi/test/acceptance/js/helpers/Client.js b/services/clsi/test/acceptance/js/helpers/Client.js new file mode 100644 index 0000000000..af8e0e30fa --- /dev/null +++ b/services/clsi/test/acceptance/js/helpers/Client.js @@ -0,0 +1,237 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let Client +const request = require('request') +const fs = require('fs') +const Settings = require('@overleaf/settings') + +const host = 'localhost' + +module.exports = Client = { + host: Settings.apis.clsi.url, + + randomId() { + return Math.random().toString(16).slice(2) + }, + + compile(project_id, data, callback) { + if (callback == null) { + callback = function (error, res, body) {} + } + if (data) { + // Enable pdf caching unless disabled explicitly. + data.options = Object.assign({}, { enablePdfCaching: true }, data.options) + } + return request.post( + { + url: `${this.host}/project/${project_id}/compile`, + json: { + compile: data, + }, + }, + callback + ) + }, + + clearCache(project_id, callback) { + if (callback == null) { + callback = function (error, res, body) {} + } + return request.del(`${this.host}/project/${project_id}`, callback) + }, + + getOutputFile(response, type) { + for (const file of Array.from(response.compile.outputFiles)) { + if (file.type === type && file.url.match(`output.${type}`)) { + return file + } + } + return null + }, + + runServer(port, directory) { + const express = require('express') + const app = express() + app.use(express.static(directory)) + console.log('starting test server on', port, host) + return app.listen(port, host).on('error', error => { + console.error('error starting server:', error.message) + return process.exit(1) + }) + }, + + syncFromCode(project_id, file, line, column, callback) { + Client.syncFromCodeWithImage(project_id, file, line, column, '', callback) + }, + + syncFromCodeWithImage(project_id, file, line, column, imageName, callback) { + if (callback == null) { + callback = function (error, pdfPositions) {} + } + return request.get( + { + url: `${this.host}/project/${project_id}/sync/code`, + qs: { + imageName, + file, + line, + column, + }, + json: true, + }, + (error, response, body) => { + if (error != null) { + return callback(error) + } + if (response.statusCode !== 200) { + return callback(new Error(`statusCode=${response.statusCode}`), body) + } + return callback(null, body) + } + ) + }, + + syncFromPdf(project_id, page, h, v, callback) { + Client.syncFromPdfWithImage(project_id, page, h, v, '', callback) + }, + + syncFromPdfWithImage(project_id, page, h, v, imageName, callback) { + if (callback == null) { + callback = function (error, pdfPositions) {} + } + return request.get( + { + url: `${this.host}/project/${project_id}/sync/pdf`, + qs: { + imageName, + page, + h, + v, + }, + json: true, + }, + (error, response, body) => { + if (error != null) { + return callback(error) + } + if (response.statusCode !== 200) { + return callback(new Error(`statusCode=${response.statusCode}`), body) + } + return callback(null, body) + } + ) + }, + + compileDirectory(project_id, baseDirectory, directory, serverPort, callback) { + if (callback == null) { + callback = function (error, res, body) {} + } + const resources = [] + let entities = fs.readdirSync(`${baseDirectory}/${directory}`) + let rootResourcePath = 'main.tex' + while (entities.length > 0) { + var entity = entities.pop() + const stat = fs.statSync(`${baseDirectory}/${directory}/${entity}`) + if (stat.isDirectory()) { + entities = entities.concat( + fs + .readdirSync(`${baseDirectory}/${directory}/${entity}`) + .map(subEntity => { + if (subEntity === 'main.tex') { + rootResourcePath = `${entity}/${subEntity}` + } + return `${entity}/${subEntity}` + }) + ) + } else if (stat.isFile() && entity !== 'output.pdf') { + const extension = entity.split('.').pop() + if ( + [ + 'tex', + 'bib', + 'cls', + 'sty', + 'pdf_tex', + 'Rtex', + 'ist', + 'md', + 'Rmd', + ].indexOf(extension) > -1 + ) { + resources.push({ + path: entity, + content: fs + .readFileSync(`${baseDirectory}/${directory}/${entity}`) + .toString(), + }) + } else if ( + ['eps', 'ttf', 'png', 'jpg', 'pdf', 'jpeg'].indexOf(extension) > -1 + ) { + resources.push({ + path: entity, + url: `http://${host}:${serverPort}/${directory}/${entity}`, + modified: stat.mtime, + }) + } + } + } + + return fs.readFile( + `${baseDirectory}/${directory}/options.json`, + (error, body) => { + const req = { + resources, + rootResourcePath, + } + + if (error == null) { + body = JSON.parse(body) + req.options = body + } + + return this.compile(project_id, req, callback) + } + ) + }, + + wordcount(project_id, file, callback) { + const image = undefined + Client.wordcountWithImage(project_id, file, image, callback) + }, + + wordcountWithImage(project_id, file, image, callback) { + if (callback == null) { + callback = function (error, pdfPositions) {} + } + return request.get( + { + url: `${this.host}/project/${project_id}/wordcount`, + qs: { + image, + file, + }, + }, + (error, response, body) => { + if (error != null) { + return callback(error) + } + if (response.statusCode !== 200) { + return callback(new Error(`statusCode=${response.statusCode}`), body) + } + return callback(null, JSON.parse(body)) + } + ) + }, +} diff --git a/services/clsi/test/acceptance/js/helpers/ClsiApp.js b/services/clsi/test/acceptance/js/helpers/ClsiApp.js new file mode 100644 index 0000000000..8dc946ff04 --- /dev/null +++ b/services/clsi/test/acceptance/js/helpers/ClsiApp.js @@ -0,0 +1,64 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const app = require('../../../../app') +require('logger-sharelatex').logger.level('info') +const logger = require('logger-sharelatex') +const Settings = require('@overleaf/settings') + +module.exports = { + running: false, + initing: false, + callbacks: [], + ensureRunning(callback) { + if (callback == null) { + callback = function (error) {} + } + if (this.running) { + return callback() + } else if (this.initing) { + return this.callbacks.push(callback) + } else { + this.initing = true + this.callbacks.push(callback) + return app.listen( + __guard__( + Settings.internal != null ? Settings.internal.clsi : undefined, + x => x.port + ), + 'localhost', + error => { + if (error != null) { + throw error + } + this.running = true + logger.log('clsi running in dev mode') + + return (() => { + const result = [] + for (callback of Array.from(this.callbacks)) { + result.push(callback()) + } + return result + })() + } + ) + } + }, +} +function __guard__(value, transform) { + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/services/clsi/test/acceptance/scripts/full-test.sh b/services/clsi/test/acceptance/scripts/full-test.sh new file mode 100755 index 0000000000..953b49452a --- /dev/null +++ b/services/clsi/test/acceptance/scripts/full-test.sh @@ -0,0 +1,23 @@ +#!/bin/bash -x + +export SHARELATEX_CONFIG=`pwd`/test/acceptance/scripts/settings.test.js + +echo ">> Starting server..." + +grunt --no-color >server.log 2>&1 & + +echo ">> Server started" + +sleep 5 + +echo ">> Running acceptance tests..." +grunt --no-color mochaTest:acceptance +_test_exit_code=$? + +echo ">> Killing server" + +kill %1 + +echo ">> Done" + +exit $_test_exit_code diff --git a/services/clsi/test/acceptance/scripts/settings.test.js b/services/clsi/test/acceptance/scripts/settings.test.js new file mode 100644 index 0000000000..877ad34641 --- /dev/null +++ b/services/clsi/test/acceptance/scripts/settings.test.js @@ -0,0 +1,63 @@ +const Path = require('path') + +module.exports = { + // Options are passed to Sequelize. + // See http://sequelizejs.com/documentation#usage-options for details + mysql: { + clsi: { + database: 'clsi', + username: 'clsi', + password: null, + dialect: 'sqlite', + storage: Path.resolve('db.sqlite'), + }, + }, + + path: { + // eslint-disable-next-line no-path-concat + compilesDir: Path.resolve(__dirname + '/../../../compiles'), + // eslint-disable-next-line no-path-concat + clsiCacheDir: Path.resolve(__dirname + '/../../../cache'), + // synctexBaseDir: (project_id) -> Path.join(@compilesDir, project_id) + synctexBaseDir() { + return '/compile' + }, + sandboxedCompilesHostDir: process.env.SANDBOXED_COMPILES_HOST_DIR, + }, + + clsi: { + // strace: true + // archive_logs: true + commandRunner: 'docker-runner-sharelatex', + latexmkCommandPrefix: ['/usr/bin/time', '-v'], // on Linux + docker: { + image: process.env.TEXLIVE_IMAGE || 'texlive-full:2017.1-opt', + env: { + PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/texlive/2017/bin/x86_64-linux/', + HOME: '/tmp', + }, + modem: { + socketPath: false, + }, + user: process.env.SIBLING_CONTAINER_USER || '111', + }, + }, + + internal: { + clsi: { + port: 3013, + load_port: 3044, + host: 'localhost', + }, + }, + + apis: { + clsi: { + url: 'http://localhost:3013', + }, + }, + + smokeTest: false, + project_cache_length_ms: 1000 * 60 * 60 * 24, + parallelFileDownloads: 1, +} diff --git a/services/clsi/test/bench/hashbench.js b/services/clsi/test/bench/hashbench.js new file mode 100644 index 0000000000..787a4e2280 --- /dev/null +++ b/services/clsi/test/bench/hashbench.js @@ -0,0 +1,73 @@ +const ContentCacheManager = require('../../app/js/ContentCacheManager') +const fs = require('fs') +const crypto = require('crypto') +const path = require('path') +const os = require('os') +const async = require('async') +const _createHash = crypto.createHash + +const files = process.argv.slice(2) + +function test(hashType, filePath, callback) { + // override the default hash in ContentCacheManager + crypto.createHash = function (hash) { + if (hashType === 'hmac-sha1') { + return crypto.createHmac('sha1', 'a secret') + } + hash = hashType + return _createHash(hash) + } + fs.mkdtemp(path.join(os.tmpdir(), 'pdfcache'), (err, dir) => { + if (err) { + return callback(err) + } + const t0 = process.hrtime.bigint() + ContentCacheManager.update(dir, filePath, x => { + const t1 = process.hrtime.bigint() + const cold = Number(t1 - t0) / 1e6 + ContentCacheManager.update(dir, filePath, x => { + const t2 = process.hrtime.bigint() + const warm = Number(t2 - t1) / 1e6 + fs.rmdir(dir, { recursive: true }, err => { + if (err) { + return callback(err) + } + console.log( + 'uvthreads', + process.env.UV_THREADPOOL_SIZE, + filePath, + 'hashType', + hashType, + 'cold-start', + cold.toFixed(2), + 'ms', + 'warm-start', + warm.toFixed(2), + 'ms' + ) + callback(null, [hashType, cold, warm]) + }) + }) + }) + }) +} + +const jobs = [] +files.forEach(file => { + jobs.push(cb => { + test('md5', file, cb) + }) + jobs.push(cb => { + test('sha1', file, cb) + }) + jobs.push(cb => { + test('hmac-sha1', file, cb) + }) + jobs.push(cb => { + test('sha256', file, cb) + }) +}) + +async.timesSeries(10, (n, cb) => { + async.series(jobs, cb) +}) diff --git a/services/clsi/test/load/js/bulk.tex b/services/clsi/test/load/js/bulk.tex new file mode 100644 index 0000000000..67c4772c14 --- /dev/null +++ b/services/clsi/test/load/js/bulk.tex @@ -0,0 +1,234 @@ + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tincidunt mattis sapien at tempor. Mauris ac tristique erat. Praesent interdum ipsum sem, ac fermentum urna imperdiet in. Nulla tincidunt purus vitae ipsum sagittis tincidunt. Aenean id nisi ullamcorper, ultrices mi vel, iaculis nunc. Sed vel varius metus, ac eleifend mauris. Donec sed orci fringilla, fermentum nulla vehicula, sodales purus. + +Maecenas nulla quam, congue vitae pellentesque sed, bibendum eu felis. Vestibulum congue gravida diam, in venenatis nisl lacinia id. Nullam eget purus ac enim dignissim consectetur vel at dolor. Integer rhoncus nisl eu odio luctus, at placerat dolor congue. Fusce sodales molestie sem eget scelerisque. Sed eros tellus, tempor eu commodo nec, maximus imperdiet eros. Aliquam vulputate ligula non bibendum tempus. In commodo eros ante, ultrices condimentum purus finibus ut. Suspendisse at eleifend mauris, vitae tincidunt sapien. Curabitur orci ipsum, aliquet a cursus efficitur, lacinia ac ex. Integer lacinia bibendum dui ut ullamcorper. Curabitur in ultricies tellus, quis ullamcorper sem. Praesent sodales dui odio. Ut lacinia aliquet eros, ut maximus nisi. Donec sit amet dui a neque interdum dapibus. + +Ut vulputate sem in lectus porttitor ullamcorper. Nulla ut urna vitae tellus posuere aliquam vitae in odio. Praesent placerat laoreet viverra. Curabitur lacinia est lectus, eget euismod nisi viverra eget. Aliquam facilisis lectus ut tincidunt mollis. Donec ut rhoncus lorem. Vivamus ultricies venenatis congue. Etiam non risus quis leo sodales lacinia. Phasellus commodo feugiat sem quis dignissim. Nunc augue dui, bibendum sed leo vitae, malesuada vulputate sem. + +Quisque nec semper nulla. Etiam dictum blandit interdum. Morbi leo leo, scelerisque vel enim vel, egestas volutpat ligula. Maecenas ac elementum lacus. Duis molestie nunc id metus iaculis, in hendrerit massa egestas. Praesent feugiat tempor dui, sit amet ultrices dui elementum id. Suspendisse cursus accumsan diam, non imperdiet diam dapibus facilisis. Praesent blandit urna felis, eget sodales nisi dictum non. Cras finibus quis augue a venenatis. In pretium condimentum arcu, at vehicula ex gravida ut. Etiam congue urna ipsum, mattis interdum neque cursus bibendum. + +Morbi felis orci, ultricies eget magna gravida, blandit condimentum erat. Curabitur convallis quam eros, eu porta diam ornare vitae. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed eleifend convallis massa, eget tristique dolor iaculis sed. Mauris id nunc erat. Donec semper rhoncus libero sit amet rhoncus. Suspendisse cursus suscipit augue quis fermentum. Sed in maximus erat. + +Ut ultrices massa vitae lectus dictum fermentum. Cras vitae risus metus. Curabitur eleifend hendrerit dolor sit amet rutrum. Pellentesque pellentesque dolor ut felis vehicula pharetra. Nam id ante eget turpis vehicula interdum in vitae odio. Nullam nec orci interdum, commodo massa et, rutrum purus. Aenean vitae porta sem. Nam in lacinia turpis. Duis dui ligula, molestie quis sagittis sit amet, faucibus ac leo. Curabitur sit amet porta ligula. Integer et sollicitudin velit. Donec magna justo, ultricies eu nunc ut, rutrum aliquam orci. Sed in dignissim sem. Proin rutrum velit urna, eu tincidunt ipsum fermentum non. Morbi id cursus nisl. + +Curabitur sed gravida ex, posuere laoreet orci. Morbi ac lacus quis tortor faucibus feugiat. Etiam fringilla lacinia libero. Duis varius sem vel lorem euismod luctus. Fusce tincidunt quis sem in ullamcorper. Ut luctus massa aliquam hendrerit finibus. Ut venenatis, neque eu hendrerit finibus, nisl tortor venenatis eros, in imperdiet leo est quis erat. Fusce luctus posuere massa, ut fermentum sapien blandit ut. Maecenas feugiat consequat lorem, eget sagittis elit vestibulum sit amet. Vivamus molestie ante ut turpis laoreet facilisis vitae eu diam. Integer a tempor tortor. In hac habitasse platea dictumst. Quisque arcu est, blandit eu justo sed, posuere congue nisi. Aliquam magna augue, convallis ac scelerisque vel, cursus eget dui. Nam rutrum auctor odio, vel sagittis ipsum gravida vel. + +Etiam elementum placerat egestas. Morbi nec mi posuere, congue ligula eu, sagittis turpis. Fusce urna nisi, dapibus in pretium et, lobortis eu arcu. Curabitur ornare urna mauris, vitae varius nulla posuere in. Integer faucibus euismod dui, a venenatis massa vehicula sit amet. Donec fringilla tellus vitae ligula pretium mattis. Aliquam aliquet quam augue, a luctus orci euismod sed. Morbi tincidunt tincidunt nulla, eget elementum turpis congue id. Suspendisse pellentesque nulla leo, fermentum ultrices massa sollicitudin vel. Morbi vel nisl consectetur, pulvinar sapien a, accumsan diam. Morbi posuere auctor nibh, nec maximus ante tincidunt ac. Etiam ut erat consectetur, molestie est sit amet, pharetra nulla. Quisque varius vestibulum ex, eget feugiat enim molestie ac. Nulla quis imperdiet risus. + +Nullam nec tempor arcu. Duis fringilla mi at magna dignissim, quis feugiat turpis lacinia. Nunc sed aliquet ipsum. Curabitur at dolor in dui posuere ornare a ut ex. Ut congue neque quis justo iaculis, ut accumsan odio condimentum. Donec sed tempus diam. Phasellus tincidunt malesuada dui, nec gravida justo volutpat vel. Praesent mi purus, sagittis in imperdiet sed, sodales eu turpis. Nullam rutrum non lacus ac imperdiet. Ut ultrices lorem at facilisis feugiat. Morbi eros enim, tristique at nisl ut, venenatis porttitor ligula. Nullam sed diam at nibh tristique consectetur. Phasellus iaculis justo nisi, ut interdum ante rutrum sit amet. Pellentesque finibus felis blandit metus pulvinar lacinia. + +Aliquam erat volutpat. Nulla eu tortor sit amet tellus bibendum tristique eget consequat metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut in aliquet augue. Phasellus laoreet nulla convallis finibus vehicula. Fusce et urna dui. Duis vel porta nunc. Nunc condimentum, justo at efficitur dignissim, lorem diam elementum ex, at dictum lectus sapien ac neque. Aliquam lacinia et ipsum lacinia efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus convallis urna orci, et dictum sapien porta sit amet. Maecenas euismod dolor mattis sapien vestibulum pulvinar. + +Vestibulum eget posuere purus, et viverra est. Nullam egestas massa et finibus semper. Vestibulum egestas porta ante eget maximus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque bibendum magna et fermentum consequat. Duis non arcu quis justo dignissim tempus at id diam. Praesent condimentum vel leo ac efficitur. Phasellus sollicitudin ipsum ut consectetur euismod. Proin diam eros, placerat sed dui ac, porttitor pellentesque nibh. Curabitur fermentum volutpat enim, in ullamcorper ipsum euismod et. Nunc a justo tortor. Phasellus libero nunc, consectetur ut dolor non, volutpat condimentum metus. + +Ut tincidunt est sem, eu venenatis lectus pretium pretium. Vivamus venenatis, erat nec sollicitudin semper, justo nulla euismod dui, quis tempor libero lectus sit amet neque. Sed in iaculis ipsum. Quisque ultricies sed mi a consequat. Sed tincidunt ante ut turpis vehicula, sed fringilla ligula efficitur. Cras eget suscipit sapien. Ut sed malesuada est, ut tempor leo. Mauris dignissim turpis quis turpis placerat cursus. Vivamus dictum dui sed blandit aliquet. + +Ut cursus, nulla eget ultricies tempor, magna enim aliquam libero, eget tempus mauris mauris ut elit. Nulla a mi quam. Integer ullamcorper ex et enim ornare efficitur. Vivamus tellus orci, pharetra in suscipit ac, ultrices sit amet sapien. Pellentesque pretium mauris vel orci accumsan, a hendrerit lectus sagittis. Mauris id nisi commodo, eleifend arcu in, vestibulum metus. Fusce vulputate gravida tincidunt. Nulla cursus non tortor ut tincidunt. Phasellus vel nisi tempus, fringilla lectus sed, ultricies erat. Ut gravida, enim id facilisis consequat, est nisi scelerisque magna, eget pharetra elit mi elementum ligula. Morbi hendrerit tortor eget velit rhoncus, consequat porta nisl aliquet. Nam diam turpis, ullamcorper vitae nisi eu, ultrices hendrerit magna. Vivamus eget pretium elit. Vivamus vitae odio sit amet libero hendrerit imperdiet. + +Aenean pharetra ex eget lectus sodales placerat. Fusce quis orci vel est suscipit venenatis. Curabitur maximus, sem in tincidunt imperdiet, nisl lorem venenatis mauris, eget facilisis lectus mauris a eros. Nam luctus sem ac diam ultrices, eget vulputate tortor efficitur. Nunc fermentum condimentum lacus id faucibus. Nunc ut tellus pretium, mattis eros vitae, scelerisque felis. Aenean ligula nulla, vulputate id eros id, vestibulum vulputate odio. Nunc in elit id augue porttitor auctor sed vitae lacus. Integer enim orci, auctor at magna eget, viverra tempus risus. Nulla suscipit metus tortor, ultricies vestibulum odio euismod at. Etiam consequat diam ac leo dignissim vulputate. Donec lectus lorem, finibus sed purus ac, eleifend condimentum ipsum. + +Fusce ornare metus vel dui scelerisque vehicula. Proin dictum sapien nec auctor congue. Nunc id erat sed velit facilisis tincidunt. In convallis eu diam id aliquam. Suspendisse eu nisl ante. Sed sit amet arcu non erat sagittis vehicula. Quisque pellentesque at lectus quis maximus. Nam mollis nulla interdum lobortis egestas. Fusce eu tellus eget libero pretium venenatis quis tristique justo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin tempor suscipit enim, eget lacinia augue malesuada sit amet. Ut ornare massa in magna pulvinar sagittis. Etiam non risus mi. Aenean aliquam dui et risus egestas aliquet. + +Aenean semper dui risus. Aenean consequat id elit a finibus. Sed vitae est sed arcu interdum maximus interdum in leo. Donec justo lorem, dictum sed placerat sit amet, eleifend in justo. Integer efficitur metus id interdum fringilla. Morbi et dui vitae libero consectetur fermentum quis sed quam. Sed interdum aliquam lorem, at blandit lectus fermentum a. Aliquam ac mollis felis, ut vulputate massa. Praesent convallis cursus eleifend. Donec non sem auctor, efficitur nisi ac, egestas libero. Nullam turpis lacus, dignissim eget pellentesque sed, fermentum ut ipsum. Vestibulum a posuere lacus, vitae rutrum neque. In hac habitasse platea dictumst. Sed vel maximus sem. Etiam dapibus risus et consectetur auctor. Phasellus vestibulum posuere sagittis. + +Aliquam nec libero at velit rhoncus pretium. Curabitur tristique blandit orci id vestibulum. Praesent in tempus arcu. Vivamus in felis tellus. Nunc ac fermentum massa. Cras nisi mi, sollicitudin eu maximus vitae, sodales gravida lorem. Vivamus mollis metus id lectus rhoncus consequat. In dui tellus, vulputate sit amet purus vel, volutpat ornare turpis. Fusce vitae massa non ligula lobortis rhoncus eget id sapien. Sed nec tempus lectus. Proin tempor risus ipsum, fermentum suscipit felis cursus sit amet. + +Maecenas ut dignissim ante, vitae ornare lorem. Fusce nec convallis eros, sed finibus urna. Proin ut finibus dolor. In non nunc sed dui aliquam suscipit. Etiam semper varius ex, sed venenatis sem gravida in. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus tempus aliquet placerat. Nam odio mauris, pharetra ac felis vel, ornare cursus nisl. Phasellus elit risus, finibus id ornare ut, scelerisque sed nisi. Curabitur aliquet, magna in finibus congue, dui libero auctor dui, ut fermentum metus enim vitae ex. Duis at elementum tellus. + +Suspendisse laoreet luctus sem sit amet tempor. Vestibulum non lorem fringilla, maximus nisl vel, pulvinar enim. Suspendisse egestas elit et sem sagittis rhoncus. Morbi nulla augue, semper euismod ultricies quis, maximus et lorem. Nulla nec posuere justo. Ut blandit nisl vitae turpis varius finibus. Donec porttitor eros neque, id mollis neque tempus et. Maecenas a massa placerat, laoreet nisl vel, venenatis diam. + +Phasellus at leo vel nisi aliquet placerat. Vestibulum luctus erat quis velit laoreet auctor. Aenean ultricies nulla tristique metus commodo, id fermentum justo tristique. Nullam ut tincidunt libero. Suspendisse volutpat, lacus ac congue ultricies, metus mi imperdiet magna, in maximus turpis ex eget leo. Sed lorem nibh, vestibulum id sodales ac, sagittis at elit. Curabitur purus nunc, sodales eget vehicula vitae, bibendum gravida diam. Nullam dignissim consequat pharetra. Nullam a diam consectetur, mollis odio sed, blandit lectus. Vestibulum eu velit id massa varius sagittis. Quisque tempor ante ac mauris rhoncus molestie. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Ut sit amet euismod mi. Nulla facilisis est posuere, feugiat est et, dictum nulla. Proin eleifend ultricies nunc. Sed commodo justo nisi, id suscipit massa malesuada ut. Donec aliquam nibh tellus, vitae gravida lectus ultricies quis. Nam pulvinar lobortis erat sit amet convallis. Sed quis magna facilisis, tincidunt dui non, hendrerit nunc. Morbi egestas, risus fringilla fermentum porttitor, nunc velit viverra mi, non sodales augue arcu ac sapien. Duis blandit urna at nisl pellentesque semper. Nulla et malesuada nulla. Aenean tristique tortor odio, sit amet luctus odio aliquam id. Phasellus facilisis lorem vitae velit aliquam imperdiet. Cras faucibus dolor eget neque fringilla, ut mattis ex hendrerit. Integer molestie porttitor sagittis. + +Pellentesque diam quam, auctor eget tristique eget, molestie sit amet est. Pellentesque a eros non dui gravida volutpat. Donec molestie blandit nunc ac interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse lobortis, neque non aliquet convallis, lectus ex venenatis ex, quis malesuada massa erat non dolor. In tristique, enim eu ultrices ultricies, lectus ligula pretium orci, commodo cursus ante est vel odio. Sed quis accumsan purus. Nam fringilla ex ut urna vestibulum, et feugiat diam ultrices. Vivamus tempus felis ac quam blandit convallis. + +Vestibulum eros erat, volutpat in est at, blandit pharetra sapien. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aenean quis enim orci. Aliquam imperdiet vel arcu ac sagittis. Mauris vitae augue sed metus commodo ornare. Nulla malesuada tellus nisl, eu vestibulum ante mollis a. Sed sagittis euismod nunc, sit amet hendrerit tortor condimentum eu. + +Praesent lacinia massa eget mi auctor placerat. Fusce porttitor augue lectus, a cursus ante dictum vel. Vestibulum ultrices vel mauris in fermentum. Nunc tincidunt non magna sed pharetra. Donec porttitor rutrum arcu, vitae tincidunt lacus suscipit ac. Aliquam lorem mauris, pulvinar non dignissim sed, pulvinar vitae dui. Donec id neque eu velit imperdiet lacinia nec eu magna. Ut a purus sit amet nulla venenatis vulputate. Integer vulputate est sem, iaculis porttitor mi mattis et. Phasellus condimentum ipsum eget tellus viverra, a tincidunt nunc feugiat. Praesent posuere aliquam ex et faucibus. Nullam pretium felis id mauris luctus, a luctus eros sodales. + +Mauris et condimentum velit. Praesent id dignissim odio. Phasellus nisl velit, molestie sed nisi et, sollicitudin tempor nisi. Pellentesque lacus eros, ultricies non leo sit amet, porttitor ullamcorper ipsum. Vestibulum maximus lorem ac justo tempus imperdiet. Suspendisse rhoncus, mi in commodo tempus, orci turpis feugiat dui, nec facilisis arcu diam ut mauris. Vestibulum risus ligula, ornare non cursus vel, pellentesque non augue. Morbi eu gravida arcu. Nunc sed fermentum lacus. Nulla id quam aliquet, aliquet lacus in, rutrum metus. Duis tristique sodales risus vel interdum. Integer rhoncus nibh eget semper malesuada. Nunc sit amet ante diam. Fusce tincidunt aliquam ex, at lobortis tellus porttitor non. Vestibulum tincidunt iaculis dui vel scelerisque. + +Aliquam sagittis mauris eget massa accumsan viverra. Pellentesque luctus sit amet augue ac scelerisque. Praesent imperdiet nisi dolor, sed malesuada est commodo at. Aenean vel leo eget felis tincidunt interdum. Fusce orci mauris, egestas eget lectus et, finibus consectetur urna. Donec ut dapibus elit, eu lacinia neque. Ut et accumsan nulla. Sed ullamcorper ligula purus, eu dapibus nunc auctor vel. Ut convallis consectetur dapibus. Curabitur eget porttitor felis. Maecenas pretium ac leo vitae volutpat. Donec in augue sit amet lorem efficitur dignissim. + +Praesent iaculis tristique rutrum. Pellentesque id odio vel purus bibendum sodales suscipit id odio. Nullam ac velit imperdiet, imperdiet nisi sed, malesuada ipsum. Quisque varius dictum efficitur. Phasellus efficitur varius imperdiet. Aenean facilisis libero non augue porttitor, nec interdum felis imperdiet. Etiam et libero id elit commodo tincidunt. Nullam rutrum odio id rutrum tristique. Cras vehicula aliquet risus ac elementum. Duis nisl urna, commodo eget ante et, vehicula tempus lacus. + +Mauris eu sapien sed erat auctor volutpat vel vel tortor. Aenean in commodo felis. Donec a dui a urna varius aliquet quis at nisi. Pellentesque et urna lacinia, commodo arcu at, laoreet lectus. Aliquam sodales, massa in convallis aliquam, dui orci eleifend arcu, a gravida mauris magna sed arcu. Ut ac lectus in risus feugiat lobortis. Nulla quis est eget dui pharetra ultricies eget at risus. Phasellus sagittis molestie ligula, eget egestas orci volutpat vitae. + +Fusce nec finibus ligula, sed volutpat tortor. Sed placerat quam fringilla augue pharetra dictum. Proin ornare mi erat, eget sollicitudin ligula venenatis vitae. Aliquam semper sagittis urna rutrum pharetra. Vivamus lacinia mattis erat, vitae ultrices arcu. Maecenas id lacus eget justo imperdiet vehicula commodo a leo. Quisque vitae eros interdum, posuere ex ornare, tincidunt lectus. + +Vestibulum hendrerit sed libero et bibendum. Sed ornare eu massa ut vestibulum. Curabitur imperdiet odio felis, at ullamcorper eros rhoncus nec. Cras commodo nisl eu augue iaculis posuere. Aliquam massa tortor, consectetur quis dui in, mollis dictum tellus. Fusce porttitor dapibus arcu. Fusce finibus pretium porttitor. + +Proin dapibus viverra nisi. Cras ullamcorper purus et consequat fermentum. Duis imperdiet in dui in imperdiet. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam quis enim at ipsum ultricies auctor scelerisque nec nulla. Vivamus ut efficitur enim. Quisque dictum quam ac dui iaculis efficitur. Morbi at nulla convallis, condimentum tellus sit amet, dapibus nunc. Morbi metus felis, commodo sit amet justo id, finibus sagittis lorem. Nam nisi diam, fermentum dapibus varius in, convallis eu leo. Phasellus ut nunc orci. Sed tincidunt mauris in ante consequat, id bibendum libero volutpat. Aliquam a dictum libero. Etiam massa odio, congue ut lorem tincidunt, elementum egestas ligula. + +Ut semper arcu a lectus interdum euismod. Curabitur nec ultrices neque. In eget sapien nulla. Pellentesque pellentesque faucibus urna id placerat. Aenean condimentum posuere interdum. Etiam vel tristique lorem, in dapibus urna. Vestibulum facilisis lobortis metus ac egestas. Vestibulum ultrices aliquet dui id efficitur. Sed a velit sed erat ultrices sodales suscipit a tortor. Nam mattis rhoncus augue et viverra. Praesent volutpat gravida enim quis sodales. + +Nam placerat nisl a ullamcorper pharetra. Sed eu eros egestas, suscipit ante id, efficitur mi. Curabitur accumsan gravida pellentesque. Vestibulum urna risus, condimentum vel libero in, porta pharetra nisi. Duis eu feugiat neque, quis condimentum dolor. Suspendisse et elementum urna. Vivamus malesuada nisi eget blandit faucibus. Duis eu lorem ac est ultrices placerat nec nec elit. Nunc sed sagittis ligula. Vivamus gravida suscipit tellus nec euismod. + +Ut posuere porta diam, vitae euismod erat egestas vitae. Aenean imperdiet quis quam eget dictum. Cras vulputate elit eu nibh scelerisque, vitae consectetur nisi malesuada. Praesent iaculis, neque nec tempor elementum, est mi egestas urna, nec commodo neque lacus vel mi. In a orci eu metus elementum tincidunt nec id tortor. Aenean augue augue, vulputate a porta quis, bibendum finibus augue. Nam condimentum ante ac congue ultrices. Praesent eu nisi eu enim accumsan scelerisque et id augue. Cras gravida dictum suscipit. Nulla tristique tempor lacus non eleifend. Curabitur sodales est in arcu accumsan, vel dignissim nunc blandit. Aenean sodales sodales lectus volutpat commodo. Maecenas venenatis accumsan nibh, sit amet semper risus ultrices non. + +In blandit iaculis dolor sit amet convallis. Aliquam quis nisl sit amet augue semper vehicula. Sed aliquam vel ex vel condimentum. Nunc diam massa, mattis ac felis vel, cursus tincidunt ligula. Aliquam erat volutpat. Quisque faucibus in metus in tempus. Ut pharetra congue tellus. Vivamus est libero, fringilla vel elit ac, rhoncus fermentum arcu. Praesent tortor diam, mattis in varius commodo, lacinia accumsan neque. Integer nec luctus nibh. Duis tincidunt velit nisi, id porttitor turpis posuere in. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam finibus tortor lectus. Curabitur condimentum orci eget urna sollicitudin vehicula. + +Donec sagittis mi lacus, quis rutrum sapien scelerisque sed. In quis interdum velit. Nulla eget tincidunt enim. Fusce viverra, sem pharetra ultricies laoreet, magna erat ornare lectus, a viverra mauris magna id mi. Quisque vitae pretium velit. Integer venenatis vel sapien non varius. Praesent eros neque, posuere sit amet posuere ut, posuere a sem. Vestibulum porttitor interdum posuere. Nam viverra felis dolor, eget ultrices lacus tincidunt a. Suspendisse elementum rhoncus tristique. Nam vehicula, odio eu porta ullamcorper, neque nunc pretium neque, ac vehicula mauris eros ac turpis. Aliquam augue nisl, pharetra non mauris id, finibus egestas massa. + +Aliquam rhoncus tortor a nunc vulputate gravida. Phasellus aliquam lorem ipsum, a suscipit orci euismod ac. Curabitur fringilla orci in ante aliquam venenatis. Ut nec sollicitudin orci. Morbi consectetur massa nec lacus vestibulum commodo. Donec quis erat at nibh scelerisque interdum. Donec sed velit molestie purus volutpat tempus. Aenean consequat, massa vitae mollis eleifend, felis ante convallis ex, quis egestas libero nisi interdum dui. Maecenas aliquet nisi quis est dapibus posuere. + +Phasellus lectus ex, finibus non orci et, suscipit fermentum orci. Vestibulum sed ligula non arcu facilisis feugiat. Praesent pellentesque eros quis eleifend tempus. In hac habitasse platea dictumst. Nulla accumsan suscipit risus, nec dignissim purus sollicitudin quis. Vestibulum vestibulum ligula non massa congue commodo. Aliquam velit ante, facilisis et aliquet non, imperdiet nec velit. Nunc vel elit felis. + +Sed sed ex ut dui cursus consectetur. Phasellus laoreet velit lacinia dui placerat tincidunt. Nullam ornare sagittis quam ac pretium. Donec imperdiet velit quis ipsum placerat, vitae lacinia felis sagittis. Aenean vitae dui fermentum, laoreet lacus egestas, faucibus libero. Maecenas blandit blandit mi, et mattis lectus placerat sollicitudin. Aliquam at semper nulla. + +Sed scelerisque lacus felis, et commodo libero tincidunt ac. Ut vel elit vel ex luctus lacinia ut et nisi. Sed ac tristique nisl. Suspendisse efficitur varius purus, sit amet gravida orci sagittis lacinia. Proin non placerat urna. Duis vehicula faucibus est vitae vehicula. Praesent vehicula tempor eros, in aliquet nisl vehicula in. Phasellus in nibh commodo, tempor magna in, convallis metus. Vivamus velit risus, scelerisque quis dolor in, finibus rhoncus erat. Vivamus ipsum libero, tempus non magna eget, condimentum tempus elit. + +Sed eu feugiat neque. In velit ex, suscipit in semper blandit, malesuada in orci. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sem odio, elementum at turpis in, aliquet posuere augue. Etiam accumsan libero lorem, tempor cursus purus fringilla in. Vestibulum id diam consectetur, interdum dui vitae, accumsan tellus. Ut eu viverra nisi. Duis odio nisl, consectetur id volutpat eu, interdum a tortor. In et ipsum interdum, fringilla urna nec, congue lectus. Aliquam eu sodales neque. Vivamus et tincidunt dolor. Sed porttitor rhoncus rutrum. Nulla facilisi. + +Vivamus dapibus ipsum vitae libero ullamcorper, quis ullamcorper tortor porttitor. Phasellus elementum sapien ac felis sagittis, non finibus massa faucibus. Curabitur id enim neque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean velit lectus, tempus placerat pretium ultricies, mollis sit amet nibh. Praesent tincidunt turpis purus, vitae malesuada sapien eleifend at. Pellentesque velit mauris, volutpat auctor pharetra at, laoreet vel mi. Duis a ornare leo, nec malesuada ante. Donec a felis nec ex varius rutrum at a libero. + +Etiam blandit nulla et lorem viverra, vitae suscipit mi luctus. Etiam enim nisl, dignissim eget lectus a, molestie hendrerit leo. Cras placerat leo nec blandit aliquet. Suspendisse id cursus metus. Aliquam a lobortis lectus, eget consequat erat. Praesent congue nulla vitae convallis pulvinar. Donec sed dui tellus. Aenean vehicula neque malesuada mi malesuada, sed lobortis nisl porttitor. Sed eu felis lacinia, fringilla nibh ac, laoreet ex. Vestibulum nibh ex, sagittis eu bibendum et, laoreet ut lectus. Proin ac augue tellus. Nulla tristique metus ut sem egestas sodales. In lorem sapien, tempor sit amet semper a, dignissim a dolor. + +Mauris finibus justo ut pretium vestibulum. Morbi euismod faucibus fringilla. Curabitur vitae dictum ipsum. Curabitur nec nulla fringilla, laoreet ligula eu, convallis magna. Proin in accumsan sem. Morbi pretium venenatis sem, vitae fringilla leo vestibulum et. Maecenas justo ligula, iaculis a finibus nec, aliquam tempor ipsum. Donec cursus nisi vel purus pulvinar, non interdum nulla semper. In eu ullamcorper odio. Sed ac augue ut urna pulvinar rhoncus. Integer maximus ultrices nisl, nec volutpat tellus facilisis eu. Fusce dictum, leo iaculis egestas consectetur, enim ligula aliquam nunc, sed condimentum neque dui eget nibh. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; + +Fusce vitae orci eu purus vehicula viverra. Vivamus mollis orci sed euismod sagittis. Duis dui sapien, ullamcorper in gravida nec, imperdiet sed purus. Cras ligula nulla, consectetur a urna a, luctus ultricies augue. Aliquam tincidunt, lectus eget auctor venenatis, elit tortor malesuada mauris, sed iaculis lectus libero et lectus. Aenean dictum imperdiet tortor, ac aliquet magna rhoncus sed. Mauris facilisis velit suscipit ligula tristique ullamcorper. Praesent leo mauris, rhoncus eu sodales a, lobortis nec nibh. + +Cras in libero felis. Donec luctus nunc id imperdiet consectetur. Nam ultrices suscipit mi, eu pretium urna luctus eget. Phasellus eu lacinia augue. Proin eu est condimentum ligula volutpat semper. Sed luctus, dolor quis bibendum venenatis, neque nibh condimentum felis, vitae cursus libero velit vitae lorem. Donec ultricies ullamcorper ipsum. Maecenas maximus accumsan blandit. + +Mauris aliquet, ex non facilisis tristique, nibh elit efficitur quam, et gravida sapien leo sed diam. Suspendisse malesuada odio vel lorem dignissim, eu accumsan ante egestas. Vivamus blandit erat sed fringilla euismod. Etiam nec mauris a sem finibus dapibus. Quisque hendrerit eros nec mattis ultricies. Vestibulum blandit nulla a eleifend sollicitudin. Fusce hendrerit, nunc ut cursus fermentum, arcu odio laoreet turpis, a tincidunt purus massa nec sem. Nam id tellus et eros vehicula fermentum. Nullam imperdiet rhoncus lectus, at vestibulum nunc semper luctus. Sed a massa sed urna posuere congue in sed augue. + +Nullam condimentum eget tortor in lobortis. Maecenas ac cursus tellus. Nunc mollis lorem risus, sed tincidunt sapien ullamcorper quis. In nec diam quis ligula euismod feugiat vitae eget dui. In pulvinar, arcu in molestie sodales, augue elit aliquam elit, vel dignissim quam mi maximus quam. Sed condimentum, nibh ut finibus faucibus, diam leo ultrices dolor, quis cursus nunc dolor non urna. Aliquam suscipit, magna vitae gravida porta, sem orci mattis arcu, nec fringilla dolor nunc in purus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc egestas cursus magna in ultrices. Maecenas quis laoreet ex, eu vehicula metus. Donec sed congue sem, in vulputate diam. Pellentesque molestie nulla ipsum, nec dignissim enim ultricies eget. Morbi vehicula odio ut justo tempus blandit. Sed nec condimentum elit. Morbi ut facilisis mauris. Aliquam luctus odio sed ante aliquam, eget venenatis risus luctus. + +Integer laoreet odio a tellus tincidunt auctor sed id dolor. Praesent quis velit quis nunc dignissim iaculis non non lectus. Praesent blandit ligula urna, semper molestie lectus dignissim sed. Suspendisse bibendum, leo sed placerat tincidunt, sapien dui molestie dui, elementum dignissim nisl nisi et nulla. Ut feugiat felis id malesuada hendrerit. Pellentesque ut nisi et ipsum laoreet tempus vel non eros. Cras ut ante mi. Fusce sed maximus lacus. Etiam hendrerit, odio in maximus tincidunt, felis dolor malesuada justo, quis porttitor odio ipsum vitae eros. Vestibulum risus ante, iaculis sodales accumsan eget, tempor quis neque. + +Vestibulum eget elit vestibulum, imperdiet ex ut, cursus metus. Proin at interdum leo. Vivamus a nisl tristique, varius nisl dignissim, auctor leo. Donec arcu felis, condimentum vel pharetra vitae, fringilla at dolor. Integer elementum viverra tortor, a ullamcorper nunc bibendum in. Vivamus et arcu sit amet nulla maximus condimentum. Vestibulum in nisi ut nulla sollicitudin gravida. Aliquam nulla ipsum, venenatis eu fermentum id, sodales vel diam. Suspendisse metus mi, facilisis ornare est et, interdum pretium odio. Morbi eget nunc orci. + +Mauris neque dolor, imperdiet non dolor ut, suscipit lacinia mi. Donec dolor mauris, viverra in purus aliquet, tincidunt volutpat mi. Proin at dapibus dolor, vel egestas eros. Nulla mattis dictum iaculis. In pulvinar dui sem, eu tincidunt ligula sodales eget. Proin consectetur augue a libero suscipit rutrum blandit id eros. Pellentesque lorem erat, porta at felis id, congue malesuada urna. Quisque fringilla ut odio sed porta. Quisque congue lorem nec augue luctus varius. Nullam nec metus fermentum lacus egestas pharetra a volutpat lectus. Fusce euismod eros sit amet nisi semper imperdiet. Donec a viverra libero, vel ultrices felis. Aliquam vitae ante quis elit posuere ultricies. Mauris velit purus, tincidunt sit amet velit sit amet, sollicitudin pharetra odio. + +Donec semper eleifend aliquet. Vestibulum fringilla augue non arcu tristique pellentesque. Duis viverra, eros vitae dignissim lobortis, mauris lorem ultricies tellus, non cursus diam tellus vitae ipsum. Ut et arcu turpis. Fusce eget neque cursus, posuere augue interdum, fringilla libero. Donec commodo velit finibus urna pellentesque blandit at eu turpis. Proin et viverra tellus, a pharetra sapien. Ut a odio fringilla, viverra elit in, dictum tortor. Morbi est diam, sagittis sed pulvinar sit amet, dictum at lorem. Phasellus a condimentum massa, sit amet vestibulum purus. Suspendisse quis pharetra tortor. Nunc tempus magna vitae ligula luctus laoreet. Integer eleifend varius commodo. In hac habitasse platea dictumst. Cras eget metus sapien. Nulla facilisi. + +Cras euismod mauris tortor, a dapibus ligula gravida fermentum. Duis ultricies fermentum faucibus. Sed interdum, lacus vel mollis tempus, enim tellus ultrices nisi, in sollicitudin enim purus non nulla. Sed eget quam massa. In hac habitasse platea dictumst. Aenean at ante metus. Sed eleifend luctus ipsum nec lacinia. Vestibulum facilisis sodales dui, nec molestie neque tempus in. Curabitur consectetur tortor eget ipsum eleifend varius. Aenean finibus nulla at velit luctus, sed finibus ipsum semper. Vivamus turpis nisi, vulputate in pellentesque ultrices, rhoncus id augue. Quisque efficitur semper ligula, sed dictum turpis porta vitae. Aliquam malesuada est ac leo fermentum, et porttitor erat sagittis. + +Morbi felis odio, tristique quis tempor at, convallis commodo lectus. Integer tincidunt lacus dolor, id molestie ante luctus non. Fusce nec quam in quam euismod malesuada. In consectetur magna ut fermentum volutpat. Phasellus malesuada risus nunc, non pellentesque mauris aliquet quis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent eget mi metus. Nunc in risus eget lacus gravida tristique a in nisi. Cras consequat aliquam quam vitae pulvinar. Curabitur commodo purus ligula, et ornare ipsum aliquet at. Sed tempor sed enim ut convallis. Mauris cursus magna non diam dapibus euismod. Nullam ac nisl est. Maecenas aliquet quam erat, ac imperdiet elit fermentum at. + +Sed urna arcu, convallis et malesuada sit amet, iaculis quis felis. Fusce pellentesque tincidunt lacus, quis aliquam enim dictum vitae. Suspendisse potenti. Donec ut tincidunt est, eget iaculis leo. Curabitur auctor pharetra augue, sed egestas ante varius id. Etiam sollicitudin et mauris vitae ullamcorper. Maecenas mollis vulputate viverra. Etiam efficitur, metus quis cursus elementum, felis arcu congue dui, et volutpat augue tellus a dolor. Duis rhoncus molestie tincidunt. Nunc finibus tortor ut nunc vehicula, ac vestibulum velit tristique. Donec in eros ut erat tempor tincidunt. + +Pellentesque cursus leo non nisl posuere, ac tincidunt lorem tempus. Praesent ut erat dictum, tincidunt elit ut, varius risus. Sed hendrerit id elit ut vestibulum. Suspendisse consequat metus sit amet neque dictum, sed feugiat risus egestas. Aliquam lobortis nisl elit, eget posuere ligula aliquam eget. Nullam lobortis a nunc vel malesuada. Praesent venenatis nisl sit amet libero suscipit, ut placerat sapien egestas. Cras condimentum justo sit amet massa sollicitudin, ac ultricies metus dignissim. Morbi mauris nunc, varius a ornare sit amet, pretium ut ex. Etiam sollicitudin, risus ut viverra euismod, magna mauris mattis tortor, eget cursus massa odio eu ipsum. Mauris tempus nunc mattis lectus varius cursus. Curabitur nisi erat, vulputate rutrum scelerisque vitae, convallis non lorem. Suspendisse purus nulla, aliquet eget hendrerit dignissim, malesuada nec orci. + +In sagittis elit id augue iaculis euismod. Maecenas consequat odio sit amet massa elementum, eget fermentum velit varius. Aliquam ac tellus ac ex ullamcorper tincidunt eget eget diam. Quisque diam tortor, vehicula ac sollicitudin vitae, sollicitudin efficitur ligula. Nullam ut rutrum quam. Phasellus ornare posuere felis, sed vehicula ipsum blandit quis. Etiam a purus eu tortor interdum rutrum. Quisque sed tincidunt magna. Morbi sodales mi vitae sem cursus, sed venenatis augue porttitor. Nam posuere enim dictum hendrerit bibendum. Ut facilisis, dolor sed vestibulum ornare, tellus elit suscipit leo, et euismod arcu neque at tortor. Suspendisse pulvinar neque vel porttitor vestibulum. + +Suspendisse in metus ut nibh euismod sodales. Sed tempor eget dolor at semper. Suspendisse at urna lacus. Donec quis velit sed elit ultricies vestibulum quis nec ipsum. Duis at augue et turpis gravida rhoncus quis in est. Fusce sit amet malesuada quam. Integer nec augue non nisl consequat scelerisque eu et velit. Sed vitae enim felis. + +In a sem accumsan, iaculis nulla vitae, ultricies turpis. Nulla luctus, ligula a gravida dapibus, mauris mauris rutrum erat, et lobortis libero libero sed nibh. In quam diam, dapibus vitae diam in, interdum accumsan ligula. Phasellus ac diam mollis, laoreet sapien ut, vehicula quam. Donec cursus elit tortor, vel mattis odio ornare ut. Quisque et justo a purus aliquam laoreet. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla non euismod metus. Integer venenatis eu nisl tempus consectetur. + +Phasellus dictum elit vel velit rhoncus, porttitor tempor mauris scelerisque. Quisque nec fringilla erat. Sed consectetur in eros ac maximus. In nec lorem sapien. Pellentesque aliquet bibendum mi, at pulvinar justo mattis nec. Proin justo lorem, tempus nec elit lobortis, interdum pretium nisl. Pellentesque euismod, massa a consectetur dignissim, risus purus dictum risus, in molestie dolor elit in turpis. Cras vitae dapibus augue. + +Proin enim diam, semper ac dapibus eget, vulputate id ligula. Proin lectus diam, pharetra sed turpis non, varius pharetra eros. Quisque eget rhoncus enim. Integer velit ante, molestie eget convallis vitae, laoreet eget massa. Etiam at sem nec urna accumsan convallis. Nam a diam luctus, scelerisque nisi id, pulvinar quam. Aliquam convallis maximus aliquet. Aliquam at diam nec tellus pretium euismod. Cras aliquam justo nec quam scelerisque vulputate. Etiam dictum eleifend elit elementum consequat. Donec semper tempus ultrices. Pellentesque bibendum vitae dolor vel scelerisque. Aenean lacinia hendrerit dolor non congue. + +Ut congue orci turpis, sit amet ultricies orci luctus in. Ut felis odio, vestibulum non convallis sit amet, congue vitae mauris. Nullam blandit enim vel lorem laoreet, at gravida est sollicitudin. Aenean posuere dignissim ex, id varius arcu iaculis id. Vestibulum id nulla eget magna pulvinar rutrum. Suspendisse pulvinar blandit mauris, vel pharetra turpis finibus a. Quisque ac ligula arcu. Praesent semper nulla sed ultrices scelerisque. Quisque id erat eget odio dictum euismod. Donec sit amet nunc purus. Quisque nulla dui, sollicitudin non odio sit amet, sagittis interdum urna. Nunc feugiat, lacus non commodo volutpat, tellus lorem fermentum risus, eget dapibus urna massa a elit. + +Sed id tellus augue. Donec quis fringilla lacus. Integer suscipit faucibus eleifend. Donec lobortis odio ut felis cursus rutrum. Morbi augue erat, rutrum eu nisl sed, tincidunt porta enim. Nulla consequat malesuada tellus. Pellentesque facilisis vel nibh et pretium. Morbi volutpat ante sed leo tincidunt, egestas bibendum dui auctor. Morbi mattis feugiat maximus. Donec a sagittis ante, non euismod metus. Morbi commodo neque viverra pretium fermentum. + +In sodales, nisl quis vulputate luctus, sapien est fringilla elit, sed vestibulum urna libero ac ante. Suspendisse potenti. Duis eget sagittis elit. Mauris sapien ligula, egestas at auctor eu, efficitur at nisi. Proin elementum, erat nec tincidunt laoreet, elit risus pellentesque sapien, in malesuada enim ligula id magna. Proin scelerisque augue lorem, et hendrerit ante fringilla vel. Quisque in faucibus nunc, sit amet convallis diam. Sed fermentum tristique fringilla. In condimentum purus ornare tristique dapibus. In malesuada nunc lorem, vel imperdiet erat pellentesque id. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque feugiat faucibus nulla, quis faucibus velit lobortis in. Donec augue sem, scelerisque vitae tortor ac, aliquet fermentum nulla. Fusce convallis non metus in ultrices. Cras justo arcu, tristique vel libero sed, fermentum ullamcorper justo. Mauris libero erat, elementum nec malesuada ac, commodo eget ante. + +Duis laoreet diam non orci volutpat rhoncus. Sed bibendum dolor quam, eget sagittis enim tincidunt at. Mauris at varius sem, id luctus augue. Sed venenatis pulvinar viverra. Curabitur enim nisi, mollis at fermentum ac, rhoncus iaculis mi. Ut dictum urna velit, a rhoncus risus tempus ut. Cras tristique scelerisque dignissim. Donec ex felis, dictum at eleifend at, posuere bibendum quam. Donec luctus aliquet velit, id fringilla sem tincidunt sed. Quisque cursus imperdiet diam, ut facilisis augue convallis et. Aliquam hendrerit consectetur neque, vitae ultricies nulla aliquam ut. Donec at justo ut ipsum aliquam bibendum in id ante. Aenean fermentum eros vel turpis tristique egestas. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent vitae dui felis. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam faucibus leo id mi blandit, posuere maximus felis malesuada. Phasellus et porttitor magna. Curabitur leo ipsum, malesuada at lobortis finibus, facilisis id purus. Suspendisse potenti. Suspendisse at iaculis metus. Nam pharetra leo quis ex aliquam fermentum. Sed quis metus faucibus, varius nunc id, condimentum est. Nam lacinia quis velit a iaculis. Nullam accumsan mattis neque vitae posuere. Vivamus sem neque, ultrices sed molestie at, gravida ut est. Nunc a tellus viverra felis pulvinar fermentum vitae nec mi. Nulla et hendrerit magna, sed bibendum mauris. Cras eget diam eu augue convallis porttitor eget sit amet tortor. Cras arcu tortor, vulputate vitae erat non, rutrum rhoncus urna. Donec blandit non erat sit amet gravida. + +Sed feugiat in nibh et sagittis. Quisque in maximus mi, eu elementum neque. In hac habitasse platea dictumst. Pellentesque ultricies consectetur urna vitae imperdiet. Nullam velit lectus, laoreet ut sem eu, commodo fringilla ipsum. Vivamus placerat vulputate ipsum nec viverra. Aenean vel venenatis augue, vitae pharetra felis. + +Pellentesque rutrum urna orci, a condimentum mi ultrices quis. Nunc facilisis velit nec velit eleifend vestibulum et vel erat. Fusce consequat ex ut lacus elementum lacinia. Nulla a sapien ut ex dignissim pulvinar sed vel ex. Aenean porta diam sit amet pellentesque dignissim. Vestibulum mollis convallis auctor. Etiam lacinia eros non nulla blandit tristique. In hac habitasse platea dictumst. Vestibulum dapibus iaculis consectetur. Morbi ex odio, posuere at sollicitudin mattis, efficitur pharetra sapien. Etiam placerat nec quam vitae fringilla. Donec sodales bibendum odio, eget pharetra erat efficitur id. Nullam ultricies dui odio, sit amet tincidunt eros vestibulum eu. Donec semper libero in lacus elementum maximus. + +Curabitur commodo ex nec sapien fermentum suscipit. Donec vel erat placerat, convallis dui non, mattis mauris. Donec placerat dui augue, et dignissim justo feugiat id. Phasellus nec justo ex. Phasellus eget lobortis orci. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras in scelerisque neque, non pretium purus. Aenean rutrum libero at fermentum pharetra. Nam elementum felis nec dapibus dignissim. Quisque ultricies ipsum a odio pellentesque mollis a maximus leo. + +Sed id urna hendrerit, convallis nisl quis, tristique felis. Sed eget consequat nisl. In vel tincidunt erat. Vivamus molestie rhoncus libero, non tincidunt lacus elementum non. Aenean faucibus lacinia nisi ac lobortis. Suspendisse iaculis augue nibh, id varius tortor ornare ac. Duis vitae congue nibh. Praesent at tortor et justo aliquam fringilla vitae id orci. Mauris ultricies velit condimentum lectus volutpat, vitae tincidunt odio fermentum. Curabitur luctus convallis libero eget pellentesque. Vivamus a tempus ipsum. + +In et justo vel nisi convallis tempor ac vitae purus. Cras efficitur ac orci sit amet aliquet. Vestibulum vel libero egestas, pharetra nisl a, ultrices erat. Nunc id turpis a erat aliquet hendrerit. Suspendisse lobortis est sed quam consectetur condimentum. Proin fermentum purus nec risus dignissim lacinia. Quisque eu libero eu nibh dictum congue id at odio. Nulla posuere justo a mollis scelerisque. Nunc luctus, augue congue volutpat tincidunt, orci nulla euismod elit, eget mollis arcu augue eget elit. Aliquam rhoncus nisl at quam dictum viverra. + +Pellentesque sit amet elit condimentum, suscipit sapien sed, dapibus turpis. Proin felis nunc, aliquet in vulputate a, lobortis et ex. Donec ac magna vulputate, tincidunt mauris eget, ultrices urna. Duis venenatis commodo massa, eget rutrum enim consectetur vel. Aenean vel erat hendrerit, tincidunt dui ut, elementum est. Vestibulum in ipsum fringilla, gravida nunc ac, sagittis dolor. Aenean pulvinar ornare diam eget ultricies. + +Cras ut luctus mauris, sed sodales orci. Quisque vitae ullamcorper metus. Ut vel justo ligula. Aenean sit amet tellus tortor. Mauris eu diam et mauris vestibulum vehicula. Donec finibus, turpis vel blandit pretium, sem quam sagittis purus, in sagittis nibh leo id augue. Duis venenatis mollis pretium. Praesent pretium bibendum eros. In non ipsum cursus, tristique orci vel, elementum dolor. Nulla egestas leo in feugiat dignissim. Integer fringilla odio ut aliquam accumsan. + +Sed risus est, tristique ut ex quis, aliquam malesuada lorem. Maecenas hendrerit eros ultricies venenatis aliquam. Vestibulum ut laoreet lectus. Integer purus neque, porttitor sed tristique congue, vestibulum et ligula. Aliquam fringilla, eros et mattis vulputate, tellus urna auctor velit, ac pharetra ligula mauris ut tortor. Donec tristique nunc metus, vitae vulputate nulla iaculis vitae. Donec iaculis dapibus dolor, eget rhoncus dui. Ut feugiat sed enim tristique efficitur. Curabitur leo risus, vehicula ac ligula id, vestibulum eleifend diam. Aliquam erat volutpat. In ipsum diam, volutpat at diam non, finibus lobortis eros. Curabitur id diam mi. Proin purus urna, auctor et diam nec, aliquam interdum lectus. Ut eget mollis tortor. Quisque elementum porta ultrices. + +Nullam aliquet augue velit, sed suscipit erat eleifend non. Vivamus nisl felis, blandit sit amet neque id, malesuada tincidunt mi. Phasellus a mauris metus. Cras tempor, arcu tincidunt fermentum viverra, tortor lacus tincidunt erat, vel tristique dolor justo vitae eros. Aliquam erat volutpat. Cras aliquet nunc et dignissim sagittis. Fusce vel nisi mi. + +Nunc tempus purus non magna tincidunt, eget dignissim justo posuere. Aenean mattis lacinia risus vel luctus. Suspendisse ac rhoncus massa, id finibus dui. Pellentesque nulla turpis, iaculis vitae mauris non, hendrerit tempus erat. Integer venenatis, dui in rutrum porttitor, purus risus commodo nisl, a fermentum nisi nunc eu neque. Quisque euismod est nec mi facilisis, ut varius leo congue. Integer sed arcu ultrices, volutpat diam at, elementum turpis. Quisque et accumsan orci. Duis consequat sollicitudin tortor in ultrices. Vestibulum porta fringilla auctor. Maecenas maximus eros at erat vestibulum mattis. Praesent fringilla pellentesque quam, vel ullamcorper nisi sollicitudin non. Curabitur fermentum fermentum ligula sed viverra. + +In hac habitasse platea dictumst. Quisque et convallis est, quis posuere felis. In congue, elit nec venenatis hendrerit, eros sapien dictum erat, non vehicula nibh felis ac sem. Nam sed semper massa. Proin id accumsan lorem. Mauris ultrices leo et velit euismod facilisis. Nulla facilisi. Morbi ultrices, mauris id ullamcorper sodales, ex neque eleifend tellus, sed luctus neque orci a dui. Curabitur ut eros metus. Morbi rhoncus odio eget lacinia blandit. Aenean lobortis consequat imperdiet. Proin tempus vehicula massa, nec posuere ex. Phasellus convallis, felis ac lacinia luctus, purus nunc imperdiet ante, eu hendrerit nibh diam eu lacus. + +Vivamus ullamcorper molestie turpis, et euismod lorem semper ac. Proin ornare, purus at ullamcorper euismod, lacus odio gravida nisl, vitae pretium mi erat a sapien. Duis ultrices libero turpis, sit amet varius sapien tempus in. Integer eget dignissim est. Aenean eget nulla nec libero faucibus tempus. Etiam in pellentesque risus. Nunc sed luctus lacus. Duis tristique nulla non enim consequat, congue vestibulum nisl interdum. Proin faucibus, eros non accumsan rutrum, ipsum justo fermentum augue, tempor ornare est metus sit amet tellus. Duis malesuada vel justo at finibus. Nullam sit amet enim scelerisque, dapibus velit ut, iaculis lectus. Nunc elementum erat nibh, eget finibus dolor porta in. Fusce varius tellus mattis tellus commodo pellentesque. Cras viverra gravida ligula, quis hendrerit ex posuere vitae. Sed quis tempor felis, ultrices faucibus velit. + +Quisque porttitor mi vitae metus dapibus, eu tincidunt turpis pharetra. Fusce dolor nulla, vulputate a ligula sed, suscipit hendrerit sem. Integer id nunc vitae erat dictum tempor. Morbi leo dui, tincidunt sed dapibus non, vestibulum sit amet magna. Proin et mauris tellus. Nam volutpat orci eu eros imperdiet congue. Suspendisse nec dictum magna. Nunc consectetur varius augue a ultricies. Proin nec lacus eget massa mattis ornare eget id ligula. Sed laoreet ante nec efficitur lacinia. + +Integer maximus laoreet tellus, eget aliquam mauris luctus ut. Sed condimentum lectus et mi eleifend egestas. Integer convallis tempus sem laoreet consequat. Suspendisse gravida commodo purus eu consequat. Pellentesque sed mi efficitur, tristique felis et, tempus lacus. Nullam in dictum est. Aenean libero libero, ullamcorper a cursus eget, mattis vitae diam. Aliquam at dolor turpis. + +Ut mauris justo, accumsan molestie eros vitae, interdum ornare lorem. Mauris ut consectetur nulla, cursus vulputate lectus. Donec commodo urna velit, nec lacinia elit accumsan sit amet. In et augue vel leo rutrum vulputate at vel magna. Phasellus in tempus erat, quis elementum mauris. Vestibulum tincidunt facilisis ante, ut volutpat eros rhoncus vitae. Aliquam erat volutpat. Proin sit amet lacus turpis. Etiam maximus sodales libero, ut vestibulum dui lacinia sit amet. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec scelerisque, turpis et auctor dictum, dolor odio sodales ligula, a convallis dolor erat a augue. Sed felis nisi, tristique sit amet dolor sit amet, imperdiet auctor mauris. Aenean vitae ex molestie, suscipit lorem fermentum, laoreet nisi. + +Suspendisse pellentesque facilisis erat, sed faucibus sapien facilisis et. Proin pharetra augue ut lorem viverra pulvinar. Fusce quam neque, egestas sed est non, rutrum porta justo. Duis fermentum tincidunt ipsum non pharetra. Suspendisse potenti. Ut volutpat magna quis erat vehicula posuere. Aliquam consequat consequat gravida. Etiam dictum gravida semper. In vitae maximus felis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Aenean pretium, nisl ac dapibus lobortis, sem justo fermentum magna, in ultricies ipsum felis vitae ex. Pellentesque dictum eget purus sit amet commodo. Nunc dapibus, leo eget bibendum luctus, leo nibh eleifend lectus, eget imperdiet felis sem id nunc. Nullam egestas justo commodo, blandit ante at, placerat dolor. Nunc malesuada nulla et nisi maximus, quis molestie diam blandit. Quisque elementum lacus purus, quis varius lectus sodales finibus. Donec sodales nunc non nunc tincidunt, eu rutrum arcu pretium. Etiam risus odio, consequat eget quam eu, dignissim iaculis erat. Aliquam eu porttitor urna. Donec molestie, diam quis tempus maximus, leo mauris pretium mauris, ut lobortis est ipsum a mauris. Nam dignissim congue leo, id mattis sem efficitur eget. Proin risus lorem, fringilla vel varius ac, hendrerit vel neque. Vestibulum auctor est fermentum, congue purus at, semper dolor. Maecenas nec nunc at dolor blandit suscipit. Donec eu nisi aliquam, eleifend sem a, ultrices ex. + +In at augue risus. Nam sed justo in quam porta maximus. Praesent elementum tellus sed dui gravida hendrerit. Cras ultricies pulvinar lobortis. Maecenas tortor augue, auctor ut eleifend eget, egestas at lectus. Aliquam erat volutpat. In in egestas tellus. + +Quisque volutpat placerat vulputate. Nulla aliquam consectetur ex a vulputate. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla volutpat leo elit, id blandit erat vehicula eget. Pellentesque molestie, lacus ac facilisis fermentum, turpis enim faucibus felis, sit amet rhoncus libero dui at mauris. Proin vel placerat risus. Nullam eleifend orci eget eros tempor, aliquet semper diam malesuada. + +In convallis gravida laoreet. Praesent scelerisque mollis massa, non lobortis sapien elementum id. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Etiam dignissim varius eros vitae molestie. Aenean molestie nisl ac bibendum laoreet. Sed placerat pellentesque augue, sit amet commodo turpis condimentum vitae. Duis a purus placerat, tempor nulla ac, rutrum ipsum. Donec lectus nisi, laoreet quis diam eget, finibus scelerisque ante. Aliquam erat volutpat. Integer convallis justo vel nibh vehicula molestie. Donec ut mi eget est pulvinar euismod. In id ligula ut enim venenatis fermentum. In metus leo, rutrum ac mollis tincidunt, semper quis lacus. Sed lobortis, augue in condimentum suscipit, augue velit tempor leo, eu mollis turpis lectus auctor orci. + +Nullam efficitur erat ut dolor tristique condimentum. Suspendisse pretium ex ut bibendum ullamcorper. Phasellus dapibus enim sed tellus condimentum, sit amet mattis tellus cursus. Phasellus venenatis augue in lacus suscipit, vel hendrerit felis vehicula. Cras consectetur cursus lorem vulputate cursus. Vestibulum nec auctor sem. Nam ultricies, mi a ullamcorper eleifend, enim magna elementum erat, a accumsan turpis nunc et tortor. Donec venenatis maximus lacus vitae sodales. Donec dapibus rutrum porta. Nulla facilisi. Vivamus aliquam ex vitae sem consequat blandit. Nullam ultricies, ante ac rutrum efficitur, magna ligula maximus turpis, ut porta est velit et magna. Vestibulum mauris massa, posuere nec convallis in, maximus a nulla. Mauris vitae lorem sed nulla aliquam tempor. Fusce vel fringilla metus, ac aliquet metus. + +Ut ac mattis augue. Fusce cursus at quam ut vehicula. In laoreet cursus urna eu fermentum. Suspendisse est mauris, gravida interdum urna a, ullamcorper hendrerit arcu. Donec dapibus blandit massa nec vulputate. Nam a lacus pretium, imperdiet risus ac, aliquam nunc. Nulla facilisi. Vivamus quam libero, vestibulum sed tellus eget, ornare gravida dolor. Sed placerat nulla in velit imperdiet mattis. Sed at arcu eleifend, scelerisque urna non, porta massa. In volutpat commodo quam et sollicitudin. + +Donec in magna ullamcorper, auctor ex malesuada, tincidunt dui. Etiam enim risus, cursus sit amet ante ut, blandit tincidunt purus. Etiam rutrum dolor nulla, vel feugiat quam convallis sit amet. In finibus mi tortor, non hendrerit purus dictum at. Vivamus condimentum elementum neque et maximus. Cras auctor iaculis metus, at vulputate justo rhoncus eu. Aliquam laoreet mi euismod, lacinia est nec, euismod augue. In viverra tincidunt dolor vitae porttitor. Proin congue, mi eu laoreet congue, libero nunc porttitor tellus, non dictum magna erat sed purus. In nec luctus ligula, vel gravida urna. Sed lacinia mollis justo at hendrerit. Pellentesque gravida laoreet risus non auctor. Praesent ac sollicitudin eros. Vestibulum non viverra magna, sodales tristique ipsum. + +Etiam et lacinia eros, ut scelerisque turpis. Sed elit tortor, varius in nibh at, tempor euismod massa. Sed dapibus purus nec felis venenatis, nec rhoncus eros sagittis. Nullam elit orci, facilisis nec nunc sed, lobortis sagittis metus. Proin aliquam pharetra sagittis. Etiam ultrices nulla quis posuere elementum. Sed ultrices justo justo, eu tempor turpis rutrum vel. Pellentesque at cursus tellus. Vestibulum pulvinar tellus eget felis posuere bibendum. Etiam nec orci eleifend, gravida ipsum mollis, facilisis erat. In efficitur ac metus vel aliquam. Quisque arcu est, malesuada ut ligula ac, consectetur rutrum ex. Quisque varius viverra gravida. Suspendisse id leo quis felis imperdiet fringilla. Aenean ac accumsan urna. Fusce sapien mauris, varius pellentesque porta lobortis, tempus scelerisque metus. + +Vivamus tempus interdum felis, quis gravida ipsum auctor at. Cras sed ligula eu mauris semper pellentesque. Donec ut nibh at odio tempus euismod. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam ultrices odio ut faucibus mollis. Curabitur tincidunt accumsan luctus. Suspendisse tincidunt magna mi, et euismod nulla feugiat quis. Fusce finibus ac velit id fermentum. Aliquam venenatis egestas aliquam. Nulla diam libero, consectetur eu enim ac, dictum tempor lectus. Ut id tempus augue. Nulla facilisis, massa sit amet ultricies ultrices, eros lacus eleifend ex, vitae facilisis velit urna non leo. Ut libero ligula, venenatis ac odio id, posuere hendrerit eros. Cras eget sapien at mauris iaculis tincidunt. Nullam ut neque nisi. Aliquam hendrerit, magna non pellentesque iaculis, nulla libero molestie augue, non vehicula tortor sapien porttitor eros. + +Sed eget lectus nec enim porta gravida. Vestibulum id tincidunt nunc. Quisque scelerisque condimentum ipsum, eu accumsan orci facilisis non. Duis venenatis, est et mattis finibus, turpis urna rhoncus urna, gravida ultrices ipsum neque vitae erat. Vivamus massa enim, tristique vulputate faucibus a, luctus non dui. Vestibulum non risus lorem. Suspendisse eu orci accumsan, molestie enim nec, convallis augue. + +Nullam sed arcu turpis. Quisque ut elementum velit. Maecenas vitae sem vel eros vehicula hendrerit. Aenean suscipit convallis justo. Morbi in consectetur diam. Donec et efficitur justo, vestibulum convallis turpis. Proin sit amet enim id enim tempor tempus. Nam est augue, consectetur vitae ligula ut, tristique consequat nibh. + +Ut pharetra tortor auctor risus posuere, nec hendrerit nibh rhoncus. Aliquam tincidunt aliquam felis, at dignissim justo fermentum quis. Aenean malesuada, eros a ornare varius, sapien ex mollis nunc, vitae bibendum augue lectus vel mi. Nulla ultricies dui sed tellus sodales, at iaculis urna elementum. Sed et lacinia urna. Integer commodo nulla non quam rhoncus, bibendum tristique massa finibus. In posuere, lectus id tristique tristique, risus mauris consectetur erat, fringilla mattis metus eros in velit. Praesent est eros, tempor vel vulputate ut, luctus ut libero. Phasellus a urna porttitor, aliquam enim non, varius ipsum. Cras non ante ut ante egestas condimentum. Praesent finibus eleifend eros, tempor feugiat neque semper eget. Duis ullamcorper condimentum aliquet. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Curabitur eget orci aliquet, pulvinar enim quis, hendrerit lectus. Suspendisse sed dictum lorem, nec porttitor ligula. Aliquam sit amet ligula sed lorem consectetur rhoncus ut a mauris. Quisque a ipsum sit amet augue mollis lobortis id nec risus. Phasellus vulputate justo in eros sodales vulputate. Fusce leo magna, condimentum quis vehicula id, malesuada at justo. Donec fringilla tortor in ullamcorper viverra. Vestibulum efficitur, quam ac consectetur dignissim, metus libero tincidunt libero, quis ultrices nisi mi eu erat. Nulla pharetra iaculis ullamcorper. Donec sit amet tortor congue risus elementum venenatis. Maecenas nisl nunc, imperdiet nec mattis sit amet, dignissim eget sapien. Nullam tristique turpis eu ante tempor, nec suscipit tortor sodales. Quisque cursus a orci quis molestie. Sed sit amet venenatis leo. Maecenas felis lacus, accumsan at accumsan non, ornare quis dolor. + +Aliquam vel enim eros. Curabitur sit amet risus ligula. Aliquam eget iaculis lacus, vitae efficitur nisl. Vestibulum convallis, risus at lacinia tempus, libero tortor rhoncus augue, id fringilla ipsum massa eget lorem. Maecenas justo leo, dignissim vitae finibus a, vehicula quis eros. Vivamus vel nisl porttitor, aliquam sapien et, semper risus. Praesent at sem tellus. Fusce sit amet fringilla elit. Nullam lorem sapien, vulputate eget lacus interdum, fermentum rutrum neque. Nulla scelerisque massa a felis cursus euismod. + +Mauris a mi posuere, eleifend velit in, luctus nisi. Donec mattis lacus velit, non laoreet odio posuere ac. Nulla efficitur fringilla orci a porta. Vestibulum est magna, fermentum id tempor eget, volutpat vel magna. Nunc non aliquam urna, ut congue urna. Aliquam purus nunc, pretium eget vehicula sed, vehicula sed sem. Quisque pellentesque velit sit amet orci dapibus tristique. Cras fringilla velit id ultrices scelerisque. Praesent porta egestas mauris, vitae accumsan quam. Aliquam molestie, magna sit amet maximus feugiat, arcu mauris ultricies lorem, id aliquam turpis arcu ac mauris. Etiam eu scelerisque neque. Donec sed quam vel est dictum convallis quis posuere tortor. Sed sit amet tortor eros. Sed odio purus, egestas at lacinia sed, consectetur id diam. Quisque tincidunt, ante eget mattis cursus, felis ante venenatis leo, in eleifend erat diam eu eros. + +Sed ac nunc mauris. Sed hendrerit ligula efficitur facilisis tincidunt. Morbi ut ornare lorem, sed facilisis ex. Aenean aliquam tristique mi, ac tristique metus rutrum eget. Curabitur id leo id massa commodo sagittis in at elit. Morbi viverra bibendum ligula vitae tristique. Etiam at est interdum, euismod nibh nec, condimentum arcu. Etiam orci ex, sagittis et tincidunt quis, finibus eu sapien. Sed urna lorem, suscipit a gravida vitae, sollicitudin vitae dolor. Cras ut imperdiet massa. Fusce ornare iaculis ipsum a cursus. Pellentesque vulputate, justo vel tincidunt egestas, lacus odio convallis odio, eu porttitor felis ipsum vitae libero. + +Phasellus eget nulla eget est convallis ornare ac non enim. Integer tincidunt massa eu tincidunt euismod. Proin at nulla in dui malesuada venenatis vitae at ligula. Curabitur dapibus mauris vitae turpis euismod, et pharetra quam molestie. Nulla id faucibus tortor. Pellentesque ultrices, turpis vel lobortis fermentum, sapien diam rhoncus sapien, quis tristique turpis lorem a mi. Nam bibendum, sem eget congue interdum, lacus orci convallis elit, ac porttitor lorem erat et orci. Integer elementum tortor et nisl posuere consectetur. Sed malesuada leo ac urna lacinia, malesuada luctus mauris faucibus. Duis consequat posuere lobortis. Cras interdum lacinia lacus. Cras ipsum sapien, porttitor eget pellentesque ut, aliquam ut magna. Integer luctus velit et elementum malesuada. Maecenas tempus mauris quis mollis posuere. + +Maecenas consequat urna elit, eget fringilla felis laoreet ac. In congue ullamcorper odio, sed malesuada turpis luctus sed. Nullam sodales interdum elit ac dignissim. Pellentesque placerat mollis velit, vulputate tristique neque aliquet vitae. Phasellus tempor viverra est, vitae vehicula justo imperdiet nec. Fusce dictum lacinia urna eget rhoncus. Nam imperdiet nisl orci, sed ultricies orci laoreet vel. In in pulvinar eros. Ut eu rhoncus eros. Suspendisse eu dui viverra, lacinia erat vitae, mattis metus. Aliquam vitae posuere sapien. Quisque eu lorem quis mi egestas varius. Aliquam erat volutpat. + +Duis eu arcu lobortis, vehicula orci ac, imperdiet dui. Etiam venenatis nisi quam, quis dapibus erat sagittis non. Suspendisse lacinia blandit interdum. Vivamus vitae sollicitudin leo. Vivamus sit amet commodo tellus, ac sagittis augue. Vivamus mi orci, ultricies ac nulla at, pharetra maximus diam. Nullam rhoncus volutpat magna eu auctor. + +Etiam commodo enim a leo pellentesque, in elementum odio lobortis. Vestibulum lobortis lobortis malesuada. Sed imperdiet ullamcorper viverra. Cras facilisis malesuada purus a consequat. Ut auctor neque mi, in scelerisque nibh ornare eu. In non dui in enim pretium ullamcorper non rutrum urna. Donec dictum porta orci sed malesuada. Morbi ac placerat felis. Aliquam lacus sem, ullamcorper sed laoreet ut, imperdiet non libero. Nunc non metus id justo accumsan ultricies. Donec in lacinia eros. Morbi non nunc diam. Aenean nisl massa, vestibulum vel fringilla vel, placerat eu leo. Sed feugiat malesuada ultricies. + +Nulla tristique massa eu tortor feugiat auctor. In viverra eu ex quis auctor. Praesent ac sapien orci. Proin sit amet orci posuere, ultricies orci in, fermentum justo. Vivamus tempus, ligula et aliquam egestas, enim orci pulvinar dui, in ultricies mauris turpis eu nisi. Pellentesque nec dui in ipsum laoreet convallis. Vestibulum a mi ornare, elementum ex sed, rhoncus neque. + +Nullam sed orci id sem tincidunt suscipit. Cras ac leo at magna facilisis blandit. Aliquam vitae tristique nisi. Nulla vestibulum felis pretium lectus commodo, id eleifend risus eleifend. Mauris bibendum facilisis est vitae scelerisque. Cras interdum dapibus ligula, nec porta lacus imperdiet at. Cras rhoncus bibendum lorem, congue mollis libero dignissim eget. Sed gravida, mauris sit amet sodales posuere, tellus felis imperdiet arcu, quis iaculis orci orci vitae ipsum. Curabitur id orci odio. Aliquam vitae rutrum lacus. + +Maecenas tristique est felis, eget posuere lorem dapibus non. Maecenas eu interdum lectus. Nullam placerat sit amet quam non hendrerit. Nulla facilisis ornare mollis. Cras sed metus facilisis, mattis dui in, auctor est. Morbi vehicula venenatis est, vitae egestas felis tincidunt vitae. Donec iaculis massa id justo ornare rhoncus. Nulla tempor felis ex, eu consectetur justo dictum sed. Integer eget laoreet nibh. Duis vitae pellentesque tellus, id blandit justo. Cras quis mollis eros. Quisque rhoncus dignissim enim at sollicitudin. Vestibulum sit amet diam sed quam sodales finibus. Nulla maximus orci sit amet porttitor vestibulum. Nam consequat urna at varius vulputate. + +Suspendisse laoreet luctus mauris. Aenean mollis felis urna, ac pretium nunc rutrum non. Aenean semper, risus vitae iaculis eleifend, odio ligula tempus nunc, sit amet suscipit nibh lorem quis ipsum. Nam et placerat sapien. In luctus accumsan risus, id pulvinar elit venenatis eu. Phasellus elementum leo urna, a dapibus neque facilisis eget. Sed sit amet tempor tortor. Fusce iaculis, ipsum nec faucibus scelerisque, felis tortor condimentum purus, in fringilla ex est ac nisl. Nunc et interdum diam. Donec venenatis, dolor quis dignissim euismod, libero ante elementum libero, vitae laoreet purus velit sit amet ipsum. Quisque urna tellus, imperdiet eget gravida a, fringilla commodo diam. + +In posuere nisi dictum tortor elementum iaculis. In dignissim diam sit amet volutpat elementum. Curabitur ultricies mi libero, sit amet feugiat massa viverra sed. Vivamus justo nibh, commodo nec elementum eu, suscipit vel elit. Fusce ac cursus libero, et volutpat augue. In tempus ultricies libero, ac rutrum mauris. Morbi vestibulum tellus eu dui dignissim euismod. In hac habitasse platea dictumst. + +Nam rhoncus hendrerit ex et mattis. Sed varius, arcu quis placerat viverra, ex lorem ultrices arcu, nec fringilla ipsum metus ut ligula. Sed in luctus mi. Pellentesque sed eros nisl. Praesent rutrum magna metus, vel efficitur eros lobortis efficitur. Mauris vestibulum urna at ligula lobortis sollicitudin. Aenean rhoncus auctor leo vel interdum. Cras sollicitudin massa leo, et eleifend metus scelerisque sit amet. Praesent dapibus euismod libero a fermentum. + +Curabitur cursus lacus sit amet est feugiat euismod. Nulla accumsan risus congue lorem facilisis scelerisque. In sed lectus elementum, porttitor nisl vulputate, pulvinar mi. Maecenas eu mollis odio. In faucibus sagittis magna, vitae mollis nisi iaculis non. Aenean dictum lectus ac arcu vehicula fringilla. Curabitur accumsan efficitur libero, eget consequat magna ultrices vel. Donec fermentum vel orci eget finibus. Etiam in massa ante. Mauris gravida enim lacus, sit amet accumsan massa suscipit a. Mauris id bibendum ex, et convallis nisl. Morbi luctus, orci in malesuada finibus, neque turpis convallis justo, id gravida sem purus eget turpis. Fusce eu laoreet justo. + +Nulla facilisi. Nulla varius risus quam, sit amet aliquam felis lacinia a. Ut sapien felis, tincidunt in pellentesque sit amet, vehicula id elit. Morbi a congue nunc. Sed justo neque, rutrum tincidunt hendrerit in, luctus at mauris. Suspendisse porttitor ex vitae felis luctus, et bibendum eros consequat. Maecenas vitae lectus eget est volutpat fermentum ac ac elit. In hac habitasse platea dictumst. Aenean luctus ex nec orci euismod aliquam. Integer a dolor elementum, mollis magna vitae, porta dui. In ac erat posuere, facilisis lacus eget, venenatis velit. Nulla ut sapien tincidunt, ultricies lectus aliquet, varius odio. Curabitur viverra congue ipsum, non finibus enim lobortis vitae. + +Duis vestibulum maximus est, sollicitudin dapibus sapien accumsan sit amet. Cras luctus, massa malesuada ornare imperdiet, dolor lorem blandit est, a blandit erat quam vel tortor. Vestibulum id semper ipsum. Ut nec ante eget velit fringilla sagittis. Duis sit amet lobortis nisi. Aenean interdum dui ut metus suscipit, a pretium tortor ultrices. Nullam tincidunt bibendum nisl, vitae tincidunt urna tincidunt tempus. Donec vitae porta sem. Phasellus venenatis egestas ligula, quis volutpat ipsum lacinia et. Pellentesque placerat ipsum elit, a feugiat libero scelerisque fringilla. Suspendisse ullamcorper congue nisi ut fringilla. Aliquam quis suscipit orci. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam at nulla volutpat, viverra sem ac, blandit erat. Quisque gravida quam eu magna hendrerit, quis varius diam scelerisque. Suspendisse nisi enim, dignissim sit amet tristique ut, tempus sodales nibh. + +Nulla ac eleifend leo. Quisque laoreet finibus mattis. Nam est nulla, maximus sed luctus non, volutpat quis ipsum. Praesent et quam dolor. Nulla facilisi. Aliquam enim orci, volutpat vitae mattis pellentesque, fringilla ut eros. Fusce semper arcu et sapien rutrum laoreet. Maecenas vel imperdiet lectus. Mauris et pulvinar justo. Mauris sed luctus diam. Fusce a odio eget ante consequat volutpat. Nullam at lorem ut dui ornare aliquam nec non justo. Mauris turpis eros, blandit eget elementum ullamcorper, molestie vel quam. Aenean pretium interdum ligula ac fringilla. Aenean et felis lorem. Ut id lectus quis risus finibus condimentum. + +Curabitur aliquet quis justo sed posuere. Donec eu libero eget mi ullamcorper placerat. Etiam massa mi, lobortis eget fermentum in, facilisis vel lectus. In hac habitasse platea dictumst. Nam laoreet sodales metus, nec finibus nulla volutpat bibendum. Aenean ut vulputate dolor, vitae venenatis est. Fusce ipsum libero, laoreet sit amet justo at, auctor tristique arcu. Praesent pellentesque efficitur velit non accumsan. Proin fermentum tempus ante, at eleifend lectus fringilla sit amet. Nunc et diam ac velit tristique malesuada a id mauris. Sed euismod turpis lacus, a maximus dolor semper in. Nulla mauris tortor, dignissim vel mauris sed, efficitur ultrices nulla. Donec sed molestie libero. Quisque nec congue eros, ut consequat lectus. Suspendisse vitae tortor sapien. Sed vitae pellentesque dolor. + +Suspendisse dictum velit metus, vel mollis erat imperdiet ut. Mauris et sapien eleifend, malesuada ante vehicula, ornare tellus. Integer condimentum mattis risus, nec luctus lacus convallis a. Phasellus bibendum consequat nisi, ut consequat dui bibendum a. Sed venenatis lobortis turpis, a venenatis ex sodales eu. Duis sit amet sem dui. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla vitae tellus a ex tristique mattis. Quisque viverra vitae turpis accumsan imperdiet. Cras nunc erat, commodo et malesuada at, vulputate in lorem. Fusce tempor venenatis dui consequat auctor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse potenti. + +Fusce et ligula sem. In condimentum dolor metus, eu luctus justo ultrices nec. Aenean vitae ligula non erat tincidunt blandit. Pellentesque luctus tellus ante, mollis cursus arcu posuere a. Ut sed lacus suscipit, dapibus quam non, elementum purus. Nullam sed venenatis tortor, id luctus lacus. Suspendisse orci felis, porta et sagittis at, posuere vitae lectus. + +Cras in ligula ut mi viverra efficitur at sed erat. Donec congue suscipit orci, eu volutpat augue ultricies non. Maecenas imperdiet tincidunt commodo. Cras quis vulputate urna, et malesuada lorem. Donec id convallis nibh, non congue lacus. Curabitur et scelerisque nisl. Maecenas vestibulum elit ipsum, in posuere tortor placerat sit amet. Nullam nec ex eget libero mattis commodo a at leo. Vestibulum aliquet, eros quis facilisis aliquam, mi arcu aliquet nisl, in tempus massa sapien at tortor. + +Maecenas et dolor sed sapien lacinia fringilla eget et nibh. Aenean viverra urna sit amet lobortis vestibulum. Aliquam vehicula rutrum magna ut aliquam. Maecenas pharetra volutpat porttitor. In id ultricies sapien, a accumsan lectus. Fusce in elit a ex auctor rutrum sit amet ac lorem. Mauris eu mi a nisl vehicula mattis. Donec dictum velit nec libero bibendum, in volutpat metus viverra. Vivamus eget sollicitudin nunc, ac vestibulum erat. Sed dolor risus, semper nec lorem vitae, vehicula molestie purus. Quisque ac lectus iaculis, ultrices leo sit amet, mattis erat. Curabitur lorem mauris, vestibulum vel risus eu, molestie facilisis elit. Pellentesque habitant morbi tristique senectus et netus et volutpat. diff --git a/services/clsi/test/load/js/loadTest.js b/services/clsi/test/load/js/loadTest.js new file mode 100644 index 0000000000..f5e1fce063 --- /dev/null +++ b/services/clsi/test/load/js/loadTest.js @@ -0,0 +1,101 @@ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const request = require('request') +const Settings = require('@overleaf/settings') +const async = require('async') +const fs = require('fs') +const _ = require('lodash') +const concurentCompiles = 5 +const totalCompiles = 50 + +const buildUrl = path => + `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` + +const mainTexContent = fs.readFileSync('./bulk.tex', 'utf-8') + +const compileTimes = [] +let failedCount = 0 + +const getAverageCompileTime = function () { + const totalTime = _.reduce(compileTimes, (sum, time) => sum + time, 0) + return totalTime / compileTimes.length +} + +const makeRequest = function (compileNumber, callback) { + let bulkBodyCount = 7 + let bodyContent = '' + while (--bulkBodyCount) { + bodyContent = bodyContent += mainTexContent + } + + const startTime = new Date() + return request.post( + { + url: buildUrl(`project/loadcompile-${compileNumber}/compile`), + json: { + compile: { + resources: [ + { + path: 'main.tex', + content: `\ +\\documentclass{article} +\\begin{document} +${bodyContent} +\\end{document}\ +`, + }, + ], + }, + }, + }, + (err, response, body) => { + if (response.statusCode !== 200) { + failedCount++ + return callback(`compile ${compileNumber} failed`) + } + if (err != null) { + failedCount++ + return callback('failed') + } + const totalTime = new Date() - startTime + console.log(totalTime + 'ms') + compileTimes.push(totalTime) + return callback(err) + } + ) +} + +const jobs = _.map( + __range__(1, totalCompiles, true), + i => cb => makeRequest(i, cb) +) + +const startTime = new Date() +async.parallelLimit(jobs, concurentCompiles, err => { + if (err != null) { + console.error(err) + } + console.log(`total time taken = ${(new Date() - startTime) / 1000}s`) + console.log(`total compiles = ${totalCompiles}`) + console.log(`concurent compiles = ${concurentCompiles}`) + console.log(`average time = ${getAverageCompileTime() / 1000}s`) + console.log(`max time = ${_.max(compileTimes) / 1000}s`) + console.log(`min time = ${_.min(compileTimes) / 1000}s`) + return console.log(`total failures = ${failedCount}`) +}) + +function __range__(left, right, inclusive) { + const range = [] + const ascending = left < right + const end = !inclusive ? right : ascending ? right + 1 : right - 1 + for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i) + } + return range +} diff --git a/services/clsi/test/setup.js b/services/clsi/test/setup.js new file mode 100644 index 0000000000..de87f662b5 --- /dev/null +++ b/services/clsi/test/setup.js @@ -0,0 +1,19 @@ +const chai = require('chai') +const SandboxedModule = require('sandboxed-module') + +// Setup should interface +chai.should() + +// Global SandboxedModule settings +SandboxedModule.configure({ + requires: { + 'logger-sharelatex': { + log() {}, + info() {}, + warn() {}, + error() {}, + err() {}, + }, + }, + globals: { Buffer, console, process }, +}) diff --git a/services/clsi/test/smoke/js/SmokeTests.js b/services/clsi/test/smoke/js/SmokeTests.js new file mode 100644 index 0000000000..0c207e660e --- /dev/null +++ b/services/clsi/test/smoke/js/SmokeTests.js @@ -0,0 +1,102 @@ +const request = require('request') +const Settings = require('@overleaf/settings') + +const buildUrl = path => + `http://${Settings.internal.clsi.host}:${Settings.internal.clsi.port}/${path}` + +const url = buildUrl(`project/smoketest-${process.pid}/compile`) + +module.exports = { + sendNewResult(res) { + this._run(error => this._sendResponse(res, error)) + }, + sendLastResult(res) { + this._sendResponse(res, this._lastError) + }, + triggerRun(cb) { + this._run(error => { + this._lastError = error + cb(error) + }) + }, + + _lastError: new Error('SmokeTestsPending'), + _sendResponse(res, error) { + let code, body + if (error) { + code = 500 + body = error.message + } else { + code = 200 + body = 'OK' + } + res.contentType('text/plain') + res.status(code).send(body) + }, + _run(done) { + request.post( + { + url, + json: { + compile: { + resources: [ + { + path: 'main.tex', + content: `\ +% Membrane-like surface +% Author: Yotam Avital +\\documentclass{article} +\\usepackage{tikz} +\\usetikzlibrary{calc,fadings,decorations.pathreplacing} +\\begin{document} +\\begin{tikzpicture} + \\def\\nuPi{3.1459265} + \\foreach \\i in {5,4,...,2}{% This one doesn't matter + \\foreach \\j in {3,2,...,0}{% This will crate a membrane + % with the front lipids visible + % top layer + \\pgfmathsetmacro{\\dx}{rand*0.1}% A random variance in the x coordinate + \\pgfmathsetmacro{\\dy}{rand*0.1}% A random variance in the y coordinate, + % gives a hight fill to the lipid + \\pgfmathsetmacro{\\rot}{rand*0.1}% A random variance in the + % molecule orientation + \\shade[ball color=red] ({\\i+\\dx+\\rot},{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)}) circle(0.45); + \\shade[ball color=gray] (\\i+\\dx,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-0.9}) circle(0.45); + \\shade[ball color=gray] (\\i+\\dx-\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-1.8}) circle(0.45); + % bottom layer + \\pgfmathsetmacro{\\dx}{rand*0.1} + \\pgfmathsetmacro{\\dy}{rand*0.1} + \\pgfmathsetmacro{\\rot}{rand*0.1} + \\shade[ball color=gray] (\\i+\\dx+\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-2.8}) circle(0.45); + \\shade[ball color=gray] (\\i+\\dx,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-3.7}) circle(0.45); + \\shade[ball color=red] (\\i+\\dx-\\rot,{0.5*\\j+\\dy+0.4*sin(\\i*\\nuPi*10)-4.6}) circle(0.45); + } + } +\\end{tikzpicture} +\\end{document}\ +`, + }, + ], + }, + }, + }, + (error, response, body) => { + if (error) return done(error) + if (!body || !body.compile || !body.compile.outputFiles) { + return done(new Error('response payload incomplete')) + } + + let pdfFound = false + let logFound = false + for (const file of body.compile.outputFiles) { + if (file.type === 'pdf') pdfFound = true + if (file.type === 'log') logFound = true + } + + if (!pdfFound) return done(new Error('no pdf returned')) + if (!logFound) return done(new Error('no log returned')) + done() + } + ) + }, +} diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js new file mode 100644 index 0000000000..792c7adc7e --- /dev/null +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -0,0 +1,445 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const { expect } = require('chai') +const modulePath = require('path').join( + __dirname, + '../../../app/js/CompileController' +) +const tk = require('timekeeper') + +function tryImageNameValidation(method, imageNameField) { + describe('when allowedImages is set', function () { + beforeEach(function () { + this.Settings.clsi = { docker: {} } + this.Settings.clsi.docker.allowedImages = [ + 'repo/image:tag1', + 'repo/image:tag2', + ] + this.res.send = sinon.stub() + this.res.status = sinon.stub().returns({ send: this.res.send }) + + this.CompileManager[method].reset() + }) + + describe('with an invalid image', function () { + beforeEach(function () { + this.req.query[imageNameField] = 'something/evil:1337' + this.CompileController[method](this.req, this.res, this.next) + }) + it('should return a 400', function () { + expect(this.res.status.calledWith(400)).to.equal(true) + }) + it('should not run the query', function () { + expect(this.CompileManager[method].called).to.equal(false) + }) + }) + + describe('with a valid image', function () { + beforeEach(function () { + this.req.query[imageNameField] = 'repo/image:tag1' + this.CompileController[method](this.req, this.res, this.next) + }) + it('should not return a 400', function () { + expect(this.res.status.calledWith(400)).to.equal(false) + }) + it('should run the query', function () { + expect(this.CompileManager[method].called).to.equal(true) + }) + }) + }) +} + +describe('CompileController', function () { + beforeEach(function () { + this.CompileController = SandboxedModule.require(modulePath, { + requires: { + './CompileManager': (this.CompileManager = {}), + './RequestParser': (this.RequestParser = {}), + '@overleaf/settings': (this.Settings = { + apis: { + clsi: { + url: 'http://clsi.example.com', + }, + }, + }), + './ProjectPersistenceManager': (this.ProjectPersistenceManager = {}), + }, + }) + this.Settings.externalUrl = 'http://www.example.com' + this.req = {} + this.res = {} + return (this.next = sinon.stub()) + }) + + describe('compile', function () { + beforeEach(function () { + this.req.body = { + compile: 'mock-body', + } + this.req.params = { project_id: (this.project_id = 'project-id-123') } + this.request = { + compile: 'mock-parsed-request', + } + this.request_with_project_id = { + compile: this.request.compile, + project_id: this.project_id, + } + this.output_files = [ + { + path: 'output.pdf', + type: 'pdf', + size: 1337, + build: 1234, + }, + { + path: 'output.log', + type: 'log', + build: 1234, + }, + ] + this.RequestParser.parse = sinon + .stub() + .callsArgWith(1, null, this.request) + this.ProjectPersistenceManager.markProjectAsJustAccessed = sinon + .stub() + .callsArg(1) + this.stats = { foo: 1 } + this.timings = { bar: 2 } + this.res.status = sinon.stub().returnsThis() + return (this.res.send = sinon.stub()) + }) + + describe('successfully', function () { + beforeEach(function () { + this.CompileManager.doCompileWithLock = sinon + .stub() + .yields(null, this.output_files, this.stats, this.timings) + return this.CompileController.compile(this.req, this.res) + }) + + it('should parse the request', function () { + return this.RequestParser.parse + .calledWith(this.req.body) + .should.equal(true) + }) + + it('should run the compile for the specified project', function () { + return this.CompileManager.doCompileWithLock + .calledWith(this.request_with_project_id) + .should.equal(true) + }) + + it('should mark the project as accessed', function () { + return this.ProjectPersistenceManager.markProjectAsJustAccessed + .calledWith(this.project_id) + .should.equal(true) + }) + + return it('should return the JSON response', function () { + this.res.status.calledWith(200).should.equal(true) + return this.res.send + .calledWith({ + compile: { + status: 'success', + error: null, + stats: this.stats, + timings: this.timings, + outputFiles: this.output_files.map(file => { + return { + url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, + ...file, + } + }), + }, + }) + .should.equal(true) + }) + }) + + describe('with user provided fake_output.pdf', function () { + beforeEach(function () { + this.output_files = [ + { + path: 'fake_output.pdf', + type: 'pdf', + build: 1234, + }, + { + path: 'output.log', + type: 'log', + build: 1234, + }, + ] + this.CompileManager.doCompileWithLock = sinon + .stub() + .yields(null, this.output_files, this.stats, this.timings) + this.CompileController.compile(this.req, this.res) + }) + + it('should return the JSON response with status failure', function () { + this.res.status.calledWith(200).should.equal(true) + this.res.send + .calledWith({ + compile: { + status: 'failure', + error: null, + stats: this.stats, + timings: this.timings, + outputFiles: this.output_files.map(file => { + return { + url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, + ...file, + } + }), + }, + }) + .should.equal(true) + }) + }) + + describe('with an empty output.pdf', function () { + beforeEach(function () { + this.output_files = [ + { + path: 'output.pdf', + type: 'pdf', + size: 0, + build: 1234, + }, + { + path: 'output.log', + type: 'log', + build: 1234, + }, + ] + this.CompileManager.doCompileWithLock = sinon + .stub() + .yields(null, this.output_files, this.stats, this.timings) + this.CompileController.compile(this.req, this.res) + }) + + it('should return the JSON response with status failure', function () { + this.res.status.calledWith(200).should.equal(true) + this.res.send + .calledWith({ + compile: { + status: 'failure', + error: null, + stats: this.stats, + timings: this.timings, + outputFiles: this.output_files.map(file => { + return { + url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, + ...file, + } + }), + }, + }) + .should.equal(true) + }) + }) + + describe('with an error', function () { + beforeEach(function () { + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, new Error((this.message = 'error message')), null) + return this.CompileController.compile(this.req, this.res) + }) + + return it('should return the JSON response with the error', function () { + this.res.status.calledWith(500).should.equal(true) + return this.res.send + .calledWith({ + compile: { + status: 'error', + error: this.message, + outputFiles: [], + // JSON.stringify will omit these + stats: undefined, + timings: undefined, + }, + }) + .should.equal(true) + }) + }) + + describe('when the request times out', function () { + beforeEach(function () { + this.error = new Error((this.message = 'container timed out')) + this.error.timedout = true + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, this.error, null) + return this.CompileController.compile(this.req, this.res) + }) + + return it('should return the JSON response with the timeout status', function () { + this.res.status.calledWith(200).should.equal(true) + return this.res.send + .calledWith({ + compile: { + status: 'timedout', + error: this.message, + outputFiles: [], + // JSON.stringify will omit these + stats: undefined, + timings: undefined, + }, + }) + .should.equal(true) + }) + }) + + return describe('when the request returns no output files', function () { + beforeEach(function () { + this.CompileManager.doCompileWithLock = sinon + .stub() + .callsArgWith(1, null, []) + return this.CompileController.compile(this.req, this.res) + }) + + return it('should return the JSON response with the failure status', function () { + this.res.status.calledWith(200).should.equal(true) + return this.res.send + .calledWith({ + compile: { + error: null, + status: 'failure', + outputFiles: [], + // JSON.stringify will omit these + stats: undefined, + timings: undefined, + }, + }) + .should.equal(true) + }) + }) + }) + + describe('syncFromCode', function () { + beforeEach(function () { + this.file = 'main.tex' + this.line = 42 + this.column = 5 + this.project_id = 'mock-project-id' + this.req.params = { project_id: this.project_id } + this.req.query = { + file: this.file, + line: this.line.toString(), + column: this.column.toString(), + } + this.res.json = sinon.stub() + + this.CompileManager.syncFromCode = sinon + .stub() + .yields(null, (this.pdfPositions = ['mock-positions'])) + return this.CompileController.syncFromCode(this.req, this.res, this.next) + }) + + it('should find the corresponding location in the PDF', function () { + return this.CompileManager.syncFromCode + .calledWith( + this.project_id, + undefined, + this.file, + this.line, + this.column + ) + .should.equal(true) + }) + + it('should return the positions', function () { + return this.res.json + .calledWith({ + pdf: this.pdfPositions, + }) + .should.equal(true) + }) + + tryImageNameValidation('syncFromCode', 'imageName') + }) + + describe('syncFromPdf', function () { + beforeEach(function () { + this.page = 5 + this.h = 100.23 + this.v = 45.67 + this.project_id = 'mock-project-id' + this.req.params = { project_id: this.project_id } + this.req.query = { + page: this.page.toString(), + h: this.h.toString(), + v: this.v.toString(), + } + this.res.json = sinon.stub() + + this.CompileManager.syncFromPdf = sinon + .stub() + .yields(null, (this.codePositions = ['mock-positions'])) + return this.CompileController.syncFromPdf(this.req, this.res, this.next) + }) + + it('should find the corresponding location in the code', function () { + return this.CompileManager.syncFromPdf + .calledWith(this.project_id, undefined, this.page, this.h, this.v) + .should.equal(true) + }) + + it('should return the positions', function () { + return this.res.json + .calledWith({ + code: this.codePositions, + }) + .should.equal(true) + }) + + tryImageNameValidation('syncFromPdf', 'imageName') + }) + + return describe('wordcount', function () { + beforeEach(function () { + this.file = 'main.tex' + this.project_id = 'mock-project-id' + this.req.params = { project_id: this.project_id } + this.req.query = { + file: this.file, + image: (this.image = 'example.com/image'), + } + this.res.json = sinon.stub() + + this.CompileManager.wordcount = sinon + .stub() + .callsArgWith(4, null, (this.texcount = ['mock-texcount'])) + }) + + it('should return the word count of a file', function () { + this.CompileController.wordcount(this.req, this.res, this.next) + return this.CompileManager.wordcount + .calledWith(this.project_id, undefined, this.file, this.image) + .should.equal(true) + }) + + it('should return the texcount info', function () { + this.CompileController.wordcount(this.req, this.res, this.next) + return this.res.json + .calledWith({ + texcount: this.texcount, + }) + .should.equal(true) + }) + + tryImageNameValidation('wordcount', 'image') + }) +}) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js new file mode 100644 index 0000000000..dfedcf31ef --- /dev/null +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -0,0 +1,633 @@ +/* eslint-disable + camelcase, + chai-friendly/no-unused-expressions, + no-path-concat, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join( + __dirname, + '../../../app/js/CompileManager' +) +const tk = require('timekeeper') +const { EventEmitter } = require('events') +const Path = require('path') + +describe('CompileManager', function () { + beforeEach(function () { + this.CompileManager = SandboxedModule.require(modulePath, { + requires: { + './LatexRunner': (this.LatexRunner = {}), + './ResourceWriter': (this.ResourceWriter = {}), + './OutputFileFinder': (this.OutputFileFinder = {}), + './OutputCacheManager': (this.OutputCacheManager = {}), + '@overleaf/settings': (this.Settings = { + path: { + compilesDir: '/compiles/dir', + outputDir: '/output/dir', + }, + synctexBaseDir() { + return '/compile' + }, + clsi: { + docker: { + image: 'SOMEIMAGE', + }, + }, + }), + + child_process: (this.child_process = {}), + './CommandRunner': (this.CommandRunner = {}), + './DraftModeManager': (this.DraftModeManager = {}), + './TikzManager': (this.TikzManager = {}), + './LockManager': (this.LockManager = {}), + fs: (this.fs = {}), + 'fs-extra': (this.fse = { ensureDir: sinon.stub().callsArg(1) }), + }, + }) + this.callback = sinon.stub() + this.project_id = 'project-id-123' + return (this.user_id = '1234') + }) + describe('doCompileWithLock', function () { + beforeEach(function () { + this.request = { + resources: (this.resources = 'mock-resources'), + project_id: this.project_id, + user_id: this.user_id, + } + this.output_files = ['foo', 'bar'] + this.Settings.compileDir = 'compiles' + this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + this.CompileManager.doCompile = sinon + .stub() + .yields(null, this.output_files) + return (this.LockManager.runWithLock = (lockFile, runner, callback) => + runner((err, ...result) => callback(err, ...Array.from(result)))) + }) + + describe('when the project is not locked', function () { + beforeEach(function () { + return this.CompileManager.doCompileWithLock( + this.request, + this.callback + ) + }) + + it('should ensure that the compile directory exists', function () { + return this.fse.ensureDir.calledWith(this.compileDir).should.equal(true) + }) + + it('should call doCompile with the request', function () { + return this.CompileManager.doCompile + .calledWith(this.request) + .should.equal(true) + }) + + return it('should call the callback with the output files', function () { + return this.callback + .calledWithExactly(null, this.output_files) + .should.equal(true) + }) + }) + + return describe('when the project is locked', function () { + beforeEach(function () { + this.error = new Error('locked') + this.LockManager.runWithLock = (lockFile, runner, callback) => { + return callback(this.error) + } + return this.CompileManager.doCompileWithLock( + this.request, + this.callback + ) + }) + + it('should ensure that the compile directory exists', function () { + return this.fse.ensureDir.calledWith(this.compileDir).should.equal(true) + }) + + it('should not call doCompile with the request', function () { + return this.CompileManager.doCompile.called.should.equal(false) + }) + + return it('should call the callback with the error', function () { + return this.callback.calledWithExactly(this.error).should.equal(true) + }) + }) + }) + + describe('doCompile', function () { + beforeEach(function () { + this.output_files = [ + { + path: 'output.log', + type: 'log', + }, + { + path: 'output.pdf', + type: 'pdf', + }, + ] + this.build_files = [ + { + path: 'output.log', + type: 'log', + build: 1234, + }, + { + path: 'output.pdf', + type: 'pdf', + build: 1234, + }, + ] + this.request = { + resources: (this.resources = 'mock-resources'), + rootResourcePath: (this.rootResourcePath = 'main.tex'), + project_id: this.project_id, + user_id: this.user_id, + compiler: (this.compiler = 'pdflatex'), + timeout: (this.timeout = 42000), + imageName: (this.image = 'example.com/image'), + flags: (this.flags = ['-file-line-error']), + compileGroup: (this.compileGroup = 'compile-group'), + } + this.env = {} + this.Settings.compileDir = 'compiles' + this.compileDir = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + this.outputDir = `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}` + this.ResourceWriter.syncResourcesToDisk = sinon + .stub() + .callsArgWith(2, null, this.resources) + this.LatexRunner.runLatex = sinon.stub().callsArg(2) + this.OutputFileFinder.findOutputFiles = sinon + .stub() + .yields(null, this.output_files) + this.OutputCacheManager.saveOutputFiles = sinon + .stub() + .yields(null, this.build_files) + this.DraftModeManager.injectDraftMode = sinon.stub().callsArg(1) + return (this.TikzManager.checkMainFile = sinon.stub().callsArg(3, false)) + }) + + describe('normally', function () { + beforeEach(function () { + return this.CompileManager.doCompile(this.request, this.callback) + }) + + it('should write the resources to disk', function () { + return this.ResourceWriter.syncResourcesToDisk + .calledWith(this.request, this.compileDir) + .should.equal(true) + }) + + it('should run LaTeX', function () { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: this.rootResourcePath, + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, + environment: this.env, + compileGroup: this.compileGroup, + }) + .should.equal(true) + }) + + it('should find the output files', function () { + return this.OutputFileFinder.findOutputFiles + .calledWith(this.resources, this.compileDir) + .should.equal(true) + }) + + it('should return the output files', function () { + return this.callback + .calledWith(null, this.build_files) + .should.equal(true) + }) + + return it('should not inject draft mode by default', function () { + return this.DraftModeManager.injectDraftMode.called.should.equal(false) + }) + }) + + describe('with draft mode', function () { + beforeEach(function () { + this.request.draft = true + return this.CompileManager.doCompile(this.request, this.callback) + }) + + return it('should inject the draft mode header', function () { + return this.DraftModeManager.injectDraftMode + .calledWith(this.compileDir + '/' + this.rootResourcePath) + .should.equal(true) + }) + }) + + describe('with a check option', function () { + beforeEach(function () { + this.request.check = 'error' + return this.CompileManager.doCompile(this.request, this.callback) + }) + + return it('should run chktex', function () { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: this.rootResourcePath, + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, + environment: { + CHKTEX_OPTIONS: '-nall -e9 -e10 -w15 -w16', + CHKTEX_EXIT_ON_ERROR: 1, + CHKTEX_ULIMIT_OPTIONS: '-t 5 -v 64000', + }, + compileGroup: this.compileGroup, + }) + .should.equal(true) + }) + }) + + return describe('with a knitr file and check options', function () { + beforeEach(function () { + this.request.rootResourcePath = 'main.Rtex' + this.request.check = 'error' + return this.CompileManager.doCompile(this.request, this.callback) + }) + + return it('should not run chktex', function () { + return this.LatexRunner.runLatex + .calledWith(`${this.project_id}-${this.user_id}`, { + directory: this.compileDir, + mainFile: 'main.Rtex', + compiler: this.compiler, + timeout: this.timeout, + image: this.image, + flags: this.flags, + environment: this.env, + compileGroup: this.compileGroup, + }) + .should.equal(true) + }) + }) + }) + + describe('clearProject', function () { + describe('succesfully', function () { + beforeEach(function () { + this.Settings.compileDir = 'compiles' + this.fs.lstat = sinon.stub().callsArgWith(1, null, { + isDirectory() { + return true + }, + }) + this.proc = new EventEmitter() + this.proc.stdout = new EventEmitter() + this.proc.stderr = new EventEmitter() + this.proc.stderr.setEncoding = sinon.stub().returns(this.proc.stderr) + this.child_process.spawn = sinon.stub().returns(this.proc) + this.CompileManager.clearProject( + this.project_id, + this.user_id, + this.callback + ) + return this.proc.emit('close', 0) + }) + + it('should remove the project directory', function () { + return this.child_process.spawn + .calledWith('rm', [ + '-r', + '-f', + '--', + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}`, + ]) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + return describe('with a non-success status code', function () { + beforeEach(function () { + this.Settings.compileDir = 'compiles' + this.fs.lstat = sinon.stub().callsArgWith(1, null, { + isDirectory() { + return true + }, + }) + this.proc = new EventEmitter() + this.proc.stdout = new EventEmitter() + this.proc.stderr = new EventEmitter() + this.proc.stderr.setEncoding = sinon.stub().returns(this.proc.stderr) + this.child_process.spawn = sinon.stub().returns(this.proc) + this.CompileManager.clearProject( + this.project_id, + this.user_id, + this.callback + ) + this.proc.stderr.emit('data', (this.error = 'oops')) + return this.proc.emit('close', 1) + }) + + it('should remove the project directory', function () { + return this.child_process.spawn + .calledWith('rm', [ + '-r', + '-f', + '--', + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + `${this.Settings.path.outputDir}/${this.project_id}-${this.user_id}`, + ]) + .should.equal(true) + }) + + it('should call the callback with an error from the stderr', function () { + this.callback.calledWithExactly(sinon.match(Error)).should.equal(true) + + this.callback.args[0][0].message.should.equal( + `rm -r ${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id} ${this.Settings.path.outputDir}/${this.project_id}-${this.user_id} failed: ${this.error}` + ) + }) + }) + }) + + describe('syncing', function () { + beforeEach(function () { + this.page = 1 + this.h = 42.23 + this.v = 87.56 + this.width = 100.01 + this.height = 234.56 + this.line = 5 + this.column = 3 + this.file_name = 'main.tex' + this.child_process.execFile = sinon.stub() + return (this.Settings.path.synctexBaseDir = project_id => + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`) + }) + + describe('syncFromCode', function () { + beforeEach(function () { + this.fs.stat = sinon.stub().callsArgWith(1, null, { + isFile() { + return true + }, + }) + this.stdout = `NODE\t${this.page}\t${this.h}\t${this.v}\t${this.width}\t${this.height}\n` + this.CommandRunner.run = sinon + .stub() + .yields(null, { stdout: this.stdout }) + return this.CompileManager.syncFromCode( + this.project_id, + this.user_id, + this.file_name, + this.line, + this.column, + '', + this.callback + ) + }) + + it('should execute the synctex binary', function () { + const bin_path = Path.resolve(__dirname + '/../../../bin/synctex') + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` + const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}` + return this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + [ + '/opt/synctex', + 'code', + synctex_path, + file_path, + this.line, + this.column, + ], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + this.Settings.clsi.docker.image, + 60000, + {} + ) + .should.equal(true) + }) + + it('should call the callback with the parsed output', function () { + return this.callback + .calledWith(null, [ + { + page: this.page, + h: this.h, + v: this.v, + height: this.height, + width: this.width, + }, + ]) + .should.equal(true) + }) + + describe('with a custom imageName', function () { + const customImageName = 'foo/bar:tag-0' + beforeEach(function () { + this.CommandRunner.run.reset() + this.CompileManager.syncFromCode( + this.project_id, + this.user_id, + this.file_name, + this.line, + this.column, + customImageName, + this.callback + ) + }) + + it('should execute the synctex binary in a custom docker image', function () { + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` + const file_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}` + this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + [ + '/opt/synctex', + 'code', + synctex_path, + file_path, + this.line, + this.column, + ], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + customImageName, + 60000, + {} + ) + .should.equal(true) + }) + }) + }) + + return describe('syncFromPdf', function () { + beforeEach(function () { + this.fs.stat = sinon.stub().callsArgWith(1, null, { + isFile() { + return true + }, + }) + this.stdout = `NODE\t${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/${this.file_name}\t${this.line}\t${this.column}\n` + this.CommandRunner.run = sinon + .stub() + .callsArgWith(7, null, { stdout: this.stdout }) + return this.CompileManager.syncFromPdf( + this.project_id, + this.user_id, + this.page, + this.h, + this.v, + '', + this.callback + ) + }) + + it('should execute the synctex binary', function () { + const bin_path = Path.resolve(__dirname + '/../../../bin/synctex') + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` + return this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + ['/opt/synctex', 'pdf', synctex_path, this.page, this.h, this.v], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + this.Settings.clsi.docker.image, + 60000, + {} + ) + .should.equal(true) + }) + + it('should call the callback with the parsed output', function () { + return this.callback + .calledWith(null, [ + { + file: this.file_name, + line: this.line, + column: this.column, + }, + ]) + .should.equal(true) + }) + + describe('with a custom imageName', function () { + const customImageName = 'foo/bar:tag-1' + beforeEach(function () { + this.CommandRunner.run.reset() + this.CompileManager.syncFromPdf( + this.project_id, + this.user_id, + this.page, + this.h, + this.v, + customImageName, + this.callback + ) + }) + + it('should execute the synctex binary in a custom docker image', function () { + const synctex_path = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}/output.pdf` + this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + ['/opt/synctex', 'pdf', synctex_path, this.page, this.h, this.v], + `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}`, + customImageName, + 60000, + {} + ) + .should.equal(true) + }) + }) + }) + }) + + return describe('wordcount', function () { + beforeEach(function () { + this.CommandRunner.run = sinon.stub().callsArg(7) + this.fs.readFile = sinon + .stub() + .callsArgWith( + 2, + null, + (this.stdout = 'Encoding: ascii\nWords in text: 2') + ) + this.callback = sinon.stub() + + this.project_id + this.timeout = 60 * 1000 + this.file_name = 'main.tex' + this.Settings.path.compilesDir = '/local/compile/directory' + this.image = 'example.com/image' + + return this.CompileManager.wordcount( + this.project_id, + this.user_id, + this.file_name, + this.image, + this.callback + ) + }) + + it('should run the texcount command', function () { + this.directory = `${this.Settings.path.compilesDir}/${this.project_id}-${this.user_id}` + this.file_path = `$COMPILE_DIR/${this.file_name}` + this.command = [ + 'texcount', + '-nocol', + '-inc', + this.file_path, + `-out=${this.file_path}.wc`, + ] + + return this.CommandRunner.run + .calledWith( + `${this.project_id}-${this.user_id}`, + this.command, + this.directory, + this.image, + this.timeout, + {} + ) + .should.equal(true) + }) + + return it('should call the callback with the parsed output', function () { + return this.callback + .calledWith(null, { + encode: 'ascii', + textWords: 2, + headWords: 0, + outside: 0, + headers: 0, + elements: 0, + mathInline: 0, + mathDisplay: 0, + errors: 0, + messages: '', + }) + .should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ContentCacheManagerTests.js b/services/clsi/test/unit/js/ContentCacheManagerTests.js new file mode 100644 index 0000000000..e59bd68d93 --- /dev/null +++ b/services/clsi/test/unit/js/ContentCacheManagerTests.js @@ -0,0 +1,220 @@ +const fs = require('fs') +const Path = require('path') +const { expect } = require('chai') + +const MODULE_PATH = '../../../app/js/ContentCacheManager' + +describe('ContentCacheManager', function () { + let contentDir, pdfPath + let ContentCacheManager, files, Settings + before(function () { + Settings = require('@overleaf/settings') + ContentCacheManager = require(MODULE_PATH) + }) + let contentRanges, newContentRanges, reclaimed + async function run(filePath, size) { + const result = await ContentCacheManager.promises.update( + contentDir, + filePath, + size + ) + let newlyReclaimed + ;[contentRanges, newContentRanges, newlyReclaimed] = result + reclaimed += newlyReclaimed + + const fileNames = await fs.promises.readdir(contentDir) + files = {} + for (const fileName of fileNames) { + const path = Path.join(contentDir, fileName) + files[path] = await fs.promises.readFile(path) + } + } + before(function () { + contentDir = + '/app/output/602cee6f6460fca0ba7921e6/content/1797a7f48f9-5abc1998509dea1f' + pdfPath = + '/app/output/602cee6f6460fca0ba7921e6/generated-files/1797a7f48ea-8ac6805139f43351/output.pdf' + + reclaimed = 0 + Settings.pdfCachingMinChunkSize = 1024 + }) + + before(async function () { + await fs.promises.rmdir(contentDir, { recursive: true }) + await fs.promises.mkdir(contentDir, { recursive: true }) + await fs.promises.mkdir(Path.dirname(pdfPath), { recursive: true }) + }) + + describe('minimal', function () { + const PATH_MINIMAL = 'test/acceptance/fixtures/minimal.pdf' + const OBJECT_ID_1 = '9 0 ' + const HASH_LARGE = + 'd7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd' + const OBJECT_ID_2 = '10 0 ' + const HASH_SMALL = + '896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71' + function getChunkPath(hash) { + return Path.join('test/unit/js/snapshots/minimalCompile/chunks', hash) + } + let MINIMAL_SIZE, RANGE_1, RANGE_2, h1, h2, START_1, START_2, END_1, END_2 + before(async function () { + await fs.promises.copyFile(PATH_MINIMAL, pdfPath) + const MINIMAL = await fs.promises.readFile(PATH_MINIMAL) + MINIMAL_SIZE = (await fs.promises.stat(PATH_MINIMAL)).size + RANGE_1 = await fs.promises.readFile(getChunkPath(HASH_LARGE)) + RANGE_2 = await fs.promises.readFile(getChunkPath(HASH_SMALL)) + h1 = HASH_LARGE + h2 = HASH_SMALL + START_1 = MINIMAL.indexOf(RANGE_1) + END_1 = START_1 + RANGE_1.byteLength + START_2 = MINIMAL.indexOf(RANGE_2) + END_2 = START_2 + RANGE_2.byteLength + }) + async function runWithMinimal() { + await run(pdfPath, MINIMAL_SIZE) + } + + describe('with two ranges qualifying', function () { + before(function () { + Settings.pdfCachingMinChunkSize = 500 + }) + before(async function () { + await runWithMinimal() + }) + it('should produce two ranges', function () { + expect(contentRanges).to.have.length(2) + }) + + it('should find the correct offsets', function () { + expect(contentRanges).to.deep.equal([ + { + objectId: OBJECT_ID_1, + start: START_1, + end: END_1, + hash: h1, + }, + { + objectId: OBJECT_ID_2, + start: START_2, + end: END_2, + hash: h2, + }, + ]) + }) + + it('should store the contents', function () { + expect(files).to.deep.equal({ + [Path.join(contentDir, h1)]: RANGE_1, + [Path.join(contentDir, h2)]: RANGE_2, + [Path.join(contentDir, '.state.v0.json')]: Buffer.from( + JSON.stringify({ + hashAge: [ + [h1, 0], + [h2, 0], + ], + hashSize: [ + [h1, RANGE_1.byteLength], + [h2, RANGE_2.byteLength], + ], + }) + ), + }) + }) + + it('should mark all ranges as new', function () { + expect(contentRanges).to.deep.equal(newContentRanges) + }) + + describe('when re-running with one range too small', function () { + before(function () { + Settings.pdfCachingMinChunkSize = 1024 + }) + + before(async function () { + await runWithMinimal() + }) + + it('should produce one range', function () { + expect(contentRanges).to.have.length(1) + }) + + it('should find the correct offsets', function () { + expect(contentRanges).to.deep.equal([ + { + objectId: OBJECT_ID_1, + start: START_1, + end: END_1, + hash: h1, + }, + ]) + }) + + it('should update the age of the 2nd range', function () { + expect(files).to.deep.equal({ + [Path.join(contentDir, h1)]: RANGE_1, + [Path.join(contentDir, h2)]: RANGE_2, + [Path.join(contentDir, '.state.v0.json')]: Buffer.from( + JSON.stringify({ + hashAge: [ + [h1, 0], + [h2, 1], + ], + hashSize: [ + [h1, RANGE_1.byteLength], + [h2, RANGE_2.byteLength], + ], + }) + ), + }) + }) + + it('should find no new ranges', function () { + expect(newContentRanges).to.deep.equal([]) + }) + + describe('when re-running 5 more times', function () { + for (let i = 0; i < 5; i++) { + before(async function () { + await runWithMinimal() + }) + } + + it('should still produce one range', function () { + expect(contentRanges).to.have.length(1) + }) + + it('should still find the correct offsets', function () { + expect(contentRanges).to.deep.equal([ + { + objectId: OBJECT_ID_1, + start: START_1, + end: END_1, + hash: h1, + }, + ]) + }) + + it('should delete the 2nd range', function () { + expect(files).to.deep.equal({ + [Path.join(contentDir, h1)]: RANGE_1, + [Path.join(contentDir, '.state.v0.json')]: Buffer.from( + JSON.stringify({ + hashAge: [[h1, 0]], + hashSize: [[h1, RANGE_1.byteLength]], + }) + ), + }) + }) + + it('should find no new ranges', function () { + expect(newContentRanges).to.deep.equal([]) + }) + + it('should yield the reclaimed space', function () { + expect(reclaimed).to.equal(RANGE_2.byteLength) + }) + }) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ContentTypeMapperTests.js b/services/clsi/test/unit/js/ContentTypeMapperTests.js new file mode 100644 index 0000000000..b6363623d2 --- /dev/null +++ b/services/clsi/test/unit/js/ContentTypeMapperTests.js @@ -0,0 +1,80 @@ +/* eslint-disable + camelcase, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join( + __dirname, + '../../../app/js/ContentTypeMapper' +) + +describe('ContentTypeMapper', function () { + beforeEach(function () { + return (this.ContentTypeMapper = SandboxedModule.require(modulePath)) + }) + + return describe('map', function () { + it('should map .txt to text/plain', function () { + const content_type = this.ContentTypeMapper.map('example.txt') + return content_type.should.equal('text/plain') + }) + + it('should map .csv to text/csv', function () { + const content_type = this.ContentTypeMapper.map('example.csv') + return content_type.should.equal('text/csv') + }) + + it('should map .pdf to application/pdf', function () { + const content_type = this.ContentTypeMapper.map('example.pdf') + return content_type.should.equal('application/pdf') + }) + + it('should fall back to octet-stream', function () { + const content_type = this.ContentTypeMapper.map('example.unknown') + return content_type.should.equal('application/octet-stream') + }) + + describe('coercing web files to plain text', function () { + it('should map .js to plain text', function () { + const content_type = this.ContentTypeMapper.map('example.js') + return content_type.should.equal('text/plain') + }) + + it('should map .html to plain text', function () { + const content_type = this.ContentTypeMapper.map('example.html') + return content_type.should.equal('text/plain') + }) + + return it('should map .css to plain text', function () { + const content_type = this.ContentTypeMapper.map('example.css') + return content_type.should.equal('text/plain') + }) + }) + + return describe('image files', function () { + it('should map .png to image/png', function () { + const content_type = this.ContentTypeMapper.map('example.png') + return content_type.should.equal('image/png') + }) + + it('should map .jpeg to image/jpeg', function () { + const content_type = this.ContentTypeMapper.map('example.jpeg') + return content_type.should.equal('image/jpeg') + }) + + return it('should map .svg to text/plain to protect against XSS (SVG can execute JS)', function () { + const content_type = this.ContentTypeMapper.map('example.svg') + return content_type.should.equal('text/plain') + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DockerLockManagerTests.js b/services/clsi/test/unit/js/DockerLockManagerTests.js new file mode 100644 index 0000000000..5708faf292 --- /dev/null +++ b/services/clsi/test/unit/js/DockerLockManagerTests.js @@ -0,0 +1,246 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join( + __dirname, + '../../../app/js/DockerLockManager' +) + +describe('LockManager', function () { + beforeEach(function () { + return (this.LockManager = SandboxedModule.require(modulePath, { + requires: { + '@overleaf/settings': (this.Settings = { clsi: { docker: {} } }), + }, + })) + }) + + return describe('runWithLock', function () { + describe('with a single lock', function () { + beforeEach(function (done) { + this.callback = sinon.stub() + return this.LockManager.runWithLock( + 'lock-one', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world'), 100), + + (err, ...args) => { + this.callback(err, ...Array.from(args)) + return done() + } + ) + }) + + return it('should call the callback', function () { + return this.callback + .calledWith(null, 'hello', 'world') + .should.equal(true) + }) + }) + + describe('with two locks', function () { + beforeEach(function (done) { + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + this.LockManager.runWithLock( + 'lock-one', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100), + + (err, ...args) => { + return this.callback1(err, ...Array.from(args)) + } + ) + return this.LockManager.runWithLock( + 'lock-two', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), + + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return done() + } + ) + }) + + it('should call the first callback', function () { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) + + return it('should call the second callback', function () { + return this.callback2 + .calledWith(null, 'hello', 'world', 'two') + .should.equal(true) + }) + }) + + return describe('with lock contention', function () { + describe('where the first lock is released quickly', function () { + beforeEach(function (done) { + this.LockManager.MAX_LOCK_WAIT_TIME = 1000 + this.LockManager.LOCK_TEST_INTERVAL = 100 + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100), + + (err, ...args) => { + return this.callback1(err, ...Array.from(args)) + } + ) + return this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200), + + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return done() + } + ) + }) + + it('should call the first callback', function () { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) + + return it('should call the second callback', function () { + return this.callback2 + .calledWith(null, 'hello', 'world', 'two') + .should.equal(true) + }) + }) + + describe('where the first lock is held longer than the waiting time', function () { + beforeEach(function (done) { + let doneTwo + this.LockManager.MAX_LOCK_HOLD_TIME = 10000 + this.LockManager.MAX_LOCK_WAIT_TIME = 1000 + this.LockManager.LOCK_TEST_INTERVAL = 100 + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + let doneOne = (doneTwo = false) + const finish = function (key) { + if (key === 1) { + doneOne = true + } + if (key === 2) { + doneTwo = true + } + if (doneOne && doneTwo) { + return done() + } + } + this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout( + () => releaseLock(null, 'hello', 'world', 'one'), + 1100 + ), + + (err, ...args) => { + this.callback1(err, ...Array.from(args)) + return finish(1) + } + ) + return this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), + + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return finish(2) + } + ) + }) + + it('should call the first callback', function () { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) + + return it('should call the second callback with an error', function () { + const error = sinon.match.instanceOf(Error) + return this.callback2.calledWith(error).should.equal(true) + }) + }) + + return describe('where the first lock is held longer than the max holding time', function () { + beforeEach(function (done) { + let doneTwo + this.LockManager.MAX_LOCK_HOLD_TIME = 1000 + this.LockManager.MAX_LOCK_WAIT_TIME = 2000 + this.LockManager.LOCK_TEST_INTERVAL = 100 + this.callback1 = sinon.stub() + this.callback2 = sinon.stub() + let doneOne = (doneTwo = false) + const finish = function (key) { + if (key === 1) { + doneOne = true + } + if (key === 2) { + doneTwo = true + } + if (doneOne && doneTwo) { + return done() + } + } + this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout( + () => releaseLock(null, 'hello', 'world', 'one'), + 1500 + ), + + (err, ...args) => { + this.callback1(err, ...Array.from(args)) + return finish(1) + } + ) + return this.LockManager.runWithLock( + 'lock', + releaseLock => + setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100), + + (err, ...args) => { + this.callback2(err, ...Array.from(args)) + return finish(2) + } + ) + }) + + it('should call the first callback', function () { + return this.callback1 + .calledWith(null, 'hello', 'world', 'one') + .should.equal(true) + }) + + return it('should call the second callback', function () { + return this.callback2 + .calledWith(null, 'hello', 'world', 'two') + .should.equal(true) + }) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DockerRunnerTests.js b/services/clsi/test/unit/js/DockerRunnerTests.js new file mode 100644 index 0000000000..dee351ec2f --- /dev/null +++ b/services/clsi/test/unit/js/DockerRunnerTests.js @@ -0,0 +1,957 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS206: Consider reworking classes to avoid initClass + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const { expect } = require('chai') +const modulePath = require('path').join( + __dirname, + '../../../app/js/DockerRunner' +) +const Path = require('path') + +describe('DockerRunner', function () { + beforeEach(function () { + let container, Docker, Timer + this.container = container = {} + this.DockerRunner = SandboxedModule.require(modulePath, { + requires: { + '@overleaf/settings': (this.Settings = { + clsi: { docker: {} }, + path: {}, + }), + dockerode: (Docker = (function () { + Docker = class Docker { + static initClass() { + this.prototype.getContainer = sinon.stub().returns(container) + this.prototype.createContainer = sinon + .stub() + .yields(null, container) + this.prototype.listContainers = sinon.stub() + } + } + Docker.initClass() + return Docker + })()), + fs: (this.fs = { + stat: sinon.stub().yields(null, { + isDirectory() { + return true + }, + }), + }), + './Metrics': { + Timer: (Timer = class Timer { + done() {} + }), + }, + './LockManager': { + runWithLock(key, runner, callback) { + return runner(callback) + }, + }, + }, + globals: { Math }, // used by lodash + }) + this.Docker = Docker + this.getContainer = Docker.prototype.getContainer + this.createContainer = Docker.prototype.createContainer + this.listContainers = Docker.prototype.listContainers + + this.directory = '/local/compile/directory' + this.mainFile = 'main-file.tex' + this.compiler = 'pdflatex' + this.image = 'example.com/sharelatex/image:2016.2' + this.env = {} + this.callback = sinon.stub() + this.project_id = 'project-id-123' + this.volumes = { '/local/compile/directory': '/compile' } + this.Settings.clsi.docker.image = this.defaultImage = 'default-image' + this.compileGroup = 'compile-group' + return (this.Settings.clsi.docker.env = { PATH: 'mock-path' }) + }) + + afterEach(function () { + this.DockerRunner.stopContainerMonitor() + }) + + describe('run', function () { + beforeEach(function (done) { + this.DockerRunner._getContainerOptions = sinon + .stub() + .returns((this.options = { mockoptions: 'foo' })) + this.DockerRunner._fingerprintContainer = sinon + .stub() + .returns((this.fingerprint = 'fingerprint')) + + this.name = `project-${this.project_id}-${this.fingerprint}` + + this.command = ['mock', 'command', '--outdir=$COMPILE_DIR'] + this.command_with_dir = ['mock', 'command', '--outdir=/compile'] + this.timeout = 42000 + return done() + }) + + describe('successfully', function () { + beforeEach(function (done) { + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.compileGroup, + (err, output) => { + this.callback(err, output) + return done() + } + ) + }) + + it('should generate the options for the container', function () { + return this.DockerRunner._getContainerOptions + .calledWith( + this.command_with_dir, + this.image, + this.volumes, + this.timeout + ) + .should.equal(true) + }) + + it('should generate the fingerprint from the returned options', function () { + return this.DockerRunner._fingerprintContainer + .calledWith(this.options) + .should.equal(true) + }) + + it('should do the run', function () { + return this.DockerRunner._runAndWaitForContainer + .calledWith(this.options, this.volumes, this.timeout) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + + describe('when path.sandboxedCompilesHostDir is set', function () { + beforeEach(function () { + this.Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles' + this.directory = '/var/lib/sharelatex/data/compiles/xyz' + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + it('should re-write the bind directory', function () { + const volumes = + this.DockerRunner._runAndWaitForContainer.lastCall.args[1] + return expect(volumes).to.deep.equal({ + '/some/host/dir/compiles/xyz': '/compile', + }) + }) + + return it('should call the callback', function () { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + + describe('when the run throws an error', function () { + beforeEach(function () { + let firstTime = true + this.output = 'mock-output' + this.DockerRunner._runAndWaitForContainer = ( + options, + volumes, + timeout, + callback + ) => { + if (callback == null) { + callback = function (error, output) {} + } + if (firstTime) { + firstTime = false + const error = new Error('(HTTP code 500) server error - ...') + error.statusCode = 500 + return callback(error) + } else { + return callback(null, this.output) + } + } + sinon.spy(this.DockerRunner, '_runAndWaitForContainer') + this.DockerRunner.destroyContainer = sinon.stub().callsArg(3) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + it('should do the run twice', function () { + return this.DockerRunner._runAndWaitForContainer.calledTwice.should.equal( + true + ) + }) + + it('should destroy the container in between', function () { + return this.DockerRunner.destroyContainer + .calledWith(this.name, null) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + + describe('with no image', function () { + beforeEach(function () { + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + null, + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + return it('should use the default image', function () { + return this.DockerRunner._getContainerOptions + .calledWith( + this.command_with_dir, + this.defaultImage, + this.volumes, + this.timeout + ) + .should.equal(true) + }) + }) + + describe('with image override', function () { + beforeEach(function () { + this.Settings.texliveImageNameOveride = 'overrideimage.com/something' + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + return it('should use the override and keep the tag', function () { + const image = this.DockerRunner._getContainerOptions.args[0][1] + return image.should.equal('overrideimage.com/something/image:2016.2') + }) + }) + + describe('with image restriction', function () { + beforeEach(function () { + this.Settings.clsi.docker.allowedImages = [ + 'repo/image:tag1', + 'repo/image:tag2', + ] + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + }) + + describe('with a valid image', function () { + beforeEach(function () { + this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + 'repo/image:tag1', + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + it('should setup the container', function () { + this.DockerRunner._getContainerOptions.called.should.equal(true) + }) + }) + + describe('with a invalid image', function () { + beforeEach(function () { + this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + 'something/different:evil', + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + it('should call the callback with an error', function () { + const err = new Error('image not allowed') + this.callback.called.should.equal(true) + this.callback.args[0][0].message.should.equal(err.message) + }) + + it('should not setup the container', function () { + this.DockerRunner._getContainerOptions.called.should.equal(false) + }) + }) + }) + }) + + describe('run with _getOptions', function () { + beforeEach(function (done) { + // this.DockerRunner._getContainerOptions = sinon + // .stub() + // .returns((this.options = { mockoptions: 'foo' })) + this.DockerRunner._fingerprintContainer = sinon + .stub() + .returns((this.fingerprint = 'fingerprint')) + + this.name = `project-${this.project_id}-${this.fingerprint}` + + this.command = ['mock', 'command', '--outdir=$COMPILE_DIR'] + this.command_with_dir = ['mock', 'command', '--outdir=/compile'] + this.timeout = 42000 + return done() + }) + + describe('when a compile group config is set', function () { + beforeEach(function () { + this.Settings.clsi.docker.compileGroupConfig = { + 'compile-group': { + 'HostConfig.newProperty': 'new-property', + }, + 'other-group': { otherProperty: 'other-property' }, + } + this.DockerRunner._runAndWaitForContainer = sinon + .stub() + .callsArgWith(3, null, (this.output = 'mock-output')) + return this.DockerRunner.run( + this.project_id, + this.command, + this.directory, + this.image, + this.timeout, + this.env, + this.compileGroup, + this.callback + ) + }) + + it('should set the docker options for the compile group', function () { + const options = + this.DockerRunner._runAndWaitForContainer.lastCall.args[0] + return expect(options.HostConfig).to.deep.include({ + Binds: ['/local/compile/directory:/compile:rw'], + LogConfig: { Type: 'none', Config: {} }, + CapDrop: 'ALL', + SecurityOpt: ['no-new-privileges'], + newProperty: 'new-property', + }) + }) + + return it('should call the callback', function () { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + }) + + describe('_runAndWaitForContainer', function () { + beforeEach(function () { + this.options = { mockoptions: 'foo', name: (this.name = 'mock-name') } + this.DockerRunner.startContainer = ( + options, + volumes, + attachStreamHandler, + callback + ) => { + attachStreamHandler(null, (this.output = 'mock-output')) + return callback(null, (this.containerId = 'container-id')) + } + sinon.spy(this.DockerRunner, 'startContainer') + this.DockerRunner.waitForContainer = sinon + .stub() + .callsArgWith(2, null, (this.exitCode = 42)) + return this.DockerRunner._runAndWaitForContainer( + this.options, + this.volumes, + this.timeout, + this.callback + ) + }) + + it('should create/start the container', function () { + return this.DockerRunner.startContainer + .calledWith(this.options, this.volumes) + .should.equal(true) + }) + + it('should wait for the container to finish', function () { + return this.DockerRunner.waitForContainer + .calledWith(this.name, this.timeout) + .should.equal(true) + }) + + return it('should call the callback with the output', function () { + return this.callback.calledWith(null, this.output).should.equal(true) + }) + }) + + describe('startContainer', function () { + beforeEach(function () { + this.attachStreamHandler = sinon.stub() + this.attachStreamHandler.cock = true + this.options = { mockoptions: 'foo', name: 'mock-name' } + this.container.inspect = sinon.stub().callsArgWith(0) + this.DockerRunner.attachToContainer = ( + containerId, + attachStreamHandler, + cb + ) => { + attachStreamHandler() + return cb() + } + return sinon.spy(this.DockerRunner, 'attachToContainer') + }) + + describe('when the container exists', function () { + beforeEach(function () { + this.container.inspect = sinon.stub().callsArgWith(0) + this.container.start = sinon.stub().yields() + + return this.DockerRunner.startContainer( + this.options, + this.volumes, + () => {}, + this.callback + ) + }) + + it('should start the container with the given name', function () { + this.getContainer.calledWith(this.options.name).should.equal(true) + return this.container.start.called.should.equal(true) + }) + + it('should not try to create the container', function () { + return this.createContainer.called.should.equal(false) + }) + + it('should attach to the container', function () { + return this.DockerRunner.attachToContainer.called.should.equal(true) + }) + + it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + + return it('should attach before the container starts', function () { + return sinon.assert.callOrder( + this.DockerRunner.attachToContainer, + this.container.start + ) + }) + }) + + describe('when the container does not exist', function () { + beforeEach(function () { + const exists = false + this.container.start = sinon.stub().yields() + this.container.inspect = sinon + .stub() + .callsArgWith(0, { statusCode: 404 }) + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.attachStreamHandler, + this.callback + ) + }) + + it('should create the container', function () { + return this.createContainer.calledWith(this.options).should.equal(true) + }) + + it('should call the callback and stream handler', function () { + this.attachStreamHandler.called.should.equal(true) + return this.callback.called.should.equal(true) + }) + + it('should attach to the container', function () { + return this.DockerRunner.attachToContainer.called.should.equal(true) + }) + + return it('should attach before the container starts', function () { + return sinon.assert.callOrder( + this.DockerRunner.attachToContainer, + this.container.start + ) + }) + }) + + describe('when the container is already running', function () { + beforeEach(function () { + const error = new Error( + `HTTP code is 304 which indicates error: server error - start: Cannot start container ${this.name}: The container MOCKID is already running.` + ) + error.statusCode = 304 + this.container.start = sinon.stub().yields(error) + this.container.inspect = sinon.stub().callsArgWith(0) + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.attachStreamHandler, + this.callback + ) + }) + + it('should not try to create the container', function () { + return this.createContainer.called.should.equal(false) + }) + + return it('should call the callback and stream handler without an error', function () { + this.attachStreamHandler.called.should.equal(true) + return this.callback.called.should.equal(true) + }) + }) + + describe('when a volume does not exist', function () { + beforeEach(function () { + this.fs.stat = sinon.stub().yields(new Error('no such path')) + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.attachStreamHandler, + this.callback + ) + }) + + it('should not try to create the container', function () { + return this.createContainer.called.should.equal(false) + }) + + it('should call the callback with an error', function () { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + }) + }) + + describe('when a volume exists but is not a directory', function () { + beforeEach(function () { + this.fs.stat = sinon.stub().yields(null, { + isDirectory() { + return false + }, + }) + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.attachStreamHandler, + this.callback + ) + }) + + it('should not try to create the container', function () { + return this.createContainer.called.should.equal(false) + }) + + it('should call the callback with an error', function () { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + }) + }) + + describe('when a volume does not exist, but sibling-containers are used', function () { + beforeEach(function () { + this.fs.stat = sinon.stub().yields(new Error('no such path')) + this.Settings.path.sandboxedCompilesHostDir = '/some/path' + this.container.start = sinon.stub().yields() + return this.DockerRunner.startContainer( + this.options, + this.volumes, + this.callback + ) + }) + + afterEach(function () { + return delete this.Settings.path.sandboxedCompilesHostDir + }) + + it('should start the container with the given name', function () { + this.getContainer.calledWith(this.options.name).should.equal(true) + return this.container.start.called.should.equal(true) + }) + + it('should not try to create the container', function () { + return this.createContainer.called.should.equal(false) + }) + + return it('should call the callback', function () { + this.callback.called.should.equal(true) + return this.callback.calledWith(new Error()).should.equal(false) + }) + }) + + return describe('when the container tries to be created, but already has been (race condition)', function () {}) + }) + + describe('waitForContainer', function () { + beforeEach(function () { + this.containerId = 'container-id' + this.timeout = 5000 + this.container.wait = sinon + .stub() + .yields(null, { StatusCode: (this.statusCode = 42) }) + return (this.container.kill = sinon.stub().yields()) + }) + + describe('when the container returns in time', function () { + beforeEach(function () { + return this.DockerRunner.waitForContainer( + this.containerId, + this.timeout, + this.callback + ) + }) + + it('should wait for the container', function () { + this.getContainer.calledWith(this.containerId).should.equal(true) + return this.container.wait.called.should.equal(true) + }) + + return it('should call the callback with the exit', function () { + return this.callback + .calledWith(null, this.statusCode) + .should.equal(true) + }) + }) + + return describe('when the container does not return before the timeout', function () { + beforeEach(function (done) { + this.container.wait = function (callback) { + if (callback == null) { + callback = function (error, exitCode) {} + } + return setTimeout(() => callback(null, { StatusCode: 42 }), 100) + } + this.timeout = 5 + return this.DockerRunner.waitForContainer( + this.containerId, + this.timeout, + (...args) => { + this.callback(...Array.from(args || [])) + return done() + } + ) + }) + + it('should call kill on the container', function () { + this.getContainer.calledWith(this.containerId).should.equal(true) + return this.container.kill.called.should.equal(true) + }) + + it('should call the callback with an error', function () { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const errorObj = this.callback.args[0][0] + expect(errorObj.message).to.include('container timed out') + expect(errorObj.timedout).equal(true) + }) + }) + }) + + describe('destroyOldContainers', function () { + beforeEach(function (done) { + const oneHourInSeconds = 60 * 60 + const oneHourInMilliseconds = oneHourInSeconds * 1000 + const nowInSeconds = Date.now() / 1000 + this.containers = [ + { + Name: '/project-old-container-name', + Id: 'old-container-id', + Created: nowInSeconds - oneHourInSeconds - 100, + }, + { + Name: '/project-new-container-name', + Id: 'new-container-id', + Created: nowInSeconds - oneHourInSeconds + 100, + }, + { + Name: '/totally-not-a-project-container', + Id: 'some-random-id', + Created: nowInSeconds - 2 * oneHourInSeconds, + }, + ] + this.DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds + this.listContainers.callsArgWith(1, null, this.containers) + this.DockerRunner.destroyContainer = sinon.stub().callsArg(3) + return this.DockerRunner.destroyOldContainers(error => { + this.callback(error) + return done() + }) + }) + + it('should list all containers', function () { + return this.listContainers.calledWith({ all: true }).should.equal(true) + }) + + it('should destroy old containers', function () { + this.DockerRunner.destroyContainer.callCount.should.equal(1) + return this.DockerRunner.destroyContainer + .calledWith('project-old-container-name', 'old-container-id') + .should.equal(true) + }) + + it('should not destroy new containers', function () { + return this.DockerRunner.destroyContainer + .calledWith('project-new-container-name', 'new-container-id') + .should.equal(false) + }) + + it('should not destroy non-project containers', function () { + return this.DockerRunner.destroyContainer + .calledWith('totally-not-a-project-container', 'some-random-id') + .should.equal(false) + }) + + return it('should callback the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + describe('_destroyContainer', function () { + beforeEach(function () { + this.containerId = 'some_id' + this.fakeContainer = { remove: sinon.stub().callsArgWith(1, null) } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + it('should get the container', function (done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + this.Docker.prototype.getContainer.callCount.should.equal(1) + this.Docker.prototype.getContainer + .calledWith(this.containerId) + .should.equal(true) + return done() + } + ) + }) + + it('should try to force-destroy the container when shouldForce=true', function (done) { + return this.DockerRunner._destroyContainer( + this.containerId, + true, + err => { + this.fakeContainer.remove.callCount.should.equal(1) + this.fakeContainer.remove + .calledWith({ force: true, v: true }) + .should.equal(true) + return done() + } + ) + }) + + it('should not try to force-destroy the container when shouldForce=false', function (done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + this.fakeContainer.remove.callCount.should.equal(1) + this.fakeContainer.remove + .calledWith({ force: false, v: true }) + .should.equal(true) + return done() + } + ) + }) + + it('should not produce an error', function (done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + expect(err).to.equal(null) + return done() + } + ) + }) + + describe('when the container is already gone', function () { + beforeEach(function () { + this.fakeError = new Error('woops') + this.fakeError.statusCode = 404 + this.fakeContainer = { + remove: sinon.stub().callsArgWith(1, this.fakeError), + } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + return it('should not produce an error', function (done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + expect(err).to.equal(null) + return done() + } + ) + }) + }) + + return describe('when container.destroy produces an error', function (done) { + beforeEach(function () { + this.fakeError = new Error('woops') + this.fakeError.statusCode = 500 + this.fakeContainer = { + remove: sinon.stub().callsArgWith(1, this.fakeError), + } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + return it('should produce an error', function (done) { + return this.DockerRunner._destroyContainer( + this.containerId, + false, + err => { + expect(err).to.not.equal(null) + expect(err).to.equal(this.fakeError) + return done() + } + ) + }) + }) + }) + + return describe('kill', function () { + beforeEach(function () { + this.containerId = 'some_id' + this.fakeContainer = { kill: sinon.stub().callsArgWith(0, null) } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + it('should get the container', function (done) { + return this.DockerRunner.kill(this.containerId, err => { + this.Docker.prototype.getContainer.callCount.should.equal(1) + this.Docker.prototype.getContainer + .calledWith(this.containerId) + .should.equal(true) + return done() + }) + }) + + it('should try to force-destroy the container', function (done) { + return this.DockerRunner.kill(this.containerId, err => { + this.fakeContainer.kill.callCount.should.equal(1) + return done() + }) + }) + + it('should not produce an error', function (done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.equal(undefined) + return done() + }) + }) + + describe('when the container is not actually running', function () { + beforeEach(function () { + this.fakeError = new Error('woops') + this.fakeError.statusCode = 500 + this.fakeError.message = + 'Cannot kill container <whatever> is not running' + this.fakeContainer = { + kill: sinon.stub().callsArgWith(0, this.fakeError), + } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + return it('should not produce an error', function (done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.equal(undefined) + return done() + }) + }) + }) + + return describe('when container.kill produces a legitimate error', function (done) { + beforeEach(function () { + this.fakeError = new Error('woops') + this.fakeError.statusCode = 500 + this.fakeError.message = 'Totally legitimate reason to throw an error' + this.fakeContainer = { + kill: sinon.stub().callsArgWith(0, this.fakeError), + } + return (this.Docker.prototype.getContainer = sinon + .stub() + .returns(this.fakeContainer)) + }) + + return it('should produce an error', function (done) { + return this.DockerRunner.kill(this.containerId, err => { + expect(err).to.not.equal(undefined) + expect(err).to.equal(this.fakeError) + return done() + }) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/DraftModeManagerTests.js b/services/clsi/test/unit/js/DraftModeManagerTests.js new file mode 100644 index 0000000000..b2391554ca --- /dev/null +++ b/services/clsi/test/unit/js/DraftModeManagerTests.js @@ -0,0 +1,84 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join( + __dirname, + '../../../app/js/DraftModeManager' +) + +describe('DraftModeManager', function () { + beforeEach(function () { + return (this.DraftModeManager = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = {}), + }, + })) + }) + + describe('_injectDraftOption', function () { + it('should add draft option into documentclass with existing options', function () { + return this.DraftModeManager._injectDraftOption(`\ +\\documentclass[a4paper,foo=bar]{article}\ +`).should.equal(`\ +\\documentclass[draft,a4paper,foo=bar]{article}\ +`) + }) + + return it('should add draft option into documentclass with no options', function () { + return this.DraftModeManager._injectDraftOption(`\ +\\documentclass{article}\ +`).should.equal(`\ +\\documentclass[draft]{article}\ +`) + }) + }) + + return describe('injectDraftMode', function () { + beforeEach(function () { + this.filename = '/mock/filename.tex' + this.callback = sinon.stub() + const content = `\ +\\documentclass{article} +\\begin{document} +Hello world +\\end{document}\ +` + this.fs.readFile = sinon.stub().callsArgWith(2, null, content) + this.fs.writeFile = sinon.stub().callsArg(2) + return this.DraftModeManager.injectDraftMode(this.filename, this.callback) + }) + + it('should read the file', function () { + return this.fs.readFile + .calledWith(this.filename, 'utf8') + .should.equal(true) + }) + + it('should write the modified file', function () { + return this.fs.writeFile + .calledWith( + this.filename, + `\ +\\documentclass[draft]{article} +\\begin{document} +Hello world +\\end{document}\ +` + ) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/LatexRunnerTests.js b/services/clsi/test/unit/js/LatexRunnerTests.js new file mode 100644 index 0000000000..16f40bd7d8 --- /dev/null +++ b/services/clsi/test/unit/js/LatexRunnerTests.js @@ -0,0 +1,195 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const { expect } = require('chai') +const modulePath = require('path').join( + __dirname, + '../../../app/js/LatexRunner' +) +const Path = require('path') + +describe('LatexRunner', function () { + beforeEach(function () { + let Timer + this.LatexRunner = SandboxedModule.require(modulePath, { + requires: { + '@overleaf/settings': (this.Settings = { + docker: { + socketPath: '/var/run/docker.sock', + }, + }), + './Metrics': { + Timer: (Timer = class Timer { + done() {} + }), + }, + './CommandRunner': (this.CommandRunner = {}), + fs: (this.fs = { + writeFile: sinon.stub().callsArg(2), + }), + }, + }) + + this.directory = '/local/compile/directory' + this.mainFile = 'main-file.tex' + this.compiler = 'pdflatex' + this.image = 'example.com/image' + this.compileGroup = 'compile-group' + this.callback = sinon.stub() + this.project_id = 'project-id-123' + return (this.env = { foo: '123' }) + }) + + return describe('runLatex', function () { + beforeEach(function () { + return (this.CommandRunner.run = sinon.stub().callsArgWith(7, null, { + stdout: 'this is stdout', + stderr: 'this is stderr', + })) + }) + + describe('normally', function () { + beforeEach(function (done) { + return this.LatexRunner.runLatex( + this.project_id, + { + directory: this.directory, + mainFile: this.mainFile, + compiler: this.compiler, + timeout: (this.timeout = 42000), + image: this.image, + environment: this.env, + compileGroup: this.compileGroup, + }, + (error, output, stats, timings) => { + this.timings = timings + done(error) + } + ) + }) + + it('should run the latex command', function () { + return this.CommandRunner.run + .calledWith( + this.project_id, + sinon.match.any, + this.directory, + this.image, + this.timeout, + this.env, + this.compileGroup + ) + .should.equal(true) + }) + + it('should record the stdout and stderr', function () { + this.fs.writeFile + .calledWith(this.directory + '/' + 'output.stdout', 'this is stdout') + .should.equal(true) + this.fs.writeFile + .calledWith(this.directory + '/' + 'output.stderr', 'this is stderr') + .should.equal(true) + }) + + it('should not record cpu metrics', function () { + expect(this.timings['cpu-percent']).to.not.exist + expect(this.timings['cpu-time']).to.not.exist + expect(this.timings['sys-time']).to.not.exist + }) + }) + + describe('with time -v', function () { + beforeEach(function (done) { + this.CommandRunner.run = sinon.stub().callsArgWith(7, null, { + stdout: 'this is stdout', + stderr: + '\tCommand being timed: "sh -c timeout 1 yes > /dev/null"\n' + + '\tUser time (seconds): 0.28\n' + + '\tSystem time (seconds): 0.70\n' + + '\tPercent of CPU this job got: 98%\n', + }) + this.LatexRunner.runLatex( + this.project_id, + { + directory: this.directory, + mainFile: this.mainFile, + compiler: this.compiler, + timeout: (this.timeout = 42000), + image: this.image, + environment: this.env, + compileGroup: this.compileGroup, + }, + (error, output, stats, timings) => { + this.timings = timings + done(error) + } + ) + }) + + it('should record cpu metrics', function () { + expect(this.timings['cpu-percent']).to.equal(98) + expect(this.timings['cpu-time']).to.equal(0.28) + expect(this.timings['sys-time']).to.equal(0.7) + }) + }) + + describe('with an .Rtex main file', function () { + beforeEach(function () { + return this.LatexRunner.runLatex( + this.project_id, + { + directory: this.directory, + mainFile: 'main-file.Rtex', + compiler: this.compiler, + image: this.image, + timeout: (this.timeout = 42000), + }, + this.callback + ) + }) + + return it('should run the latex command on the equivalent .tex file', function () { + const command = this.CommandRunner.run.args[0][1] + const mainFile = command.slice(-1)[0] + return mainFile.should.equal('$COMPILE_DIR/main-file.tex') + }) + }) + + return describe('with a flags option', function () { + beforeEach(function () { + return this.LatexRunner.runLatex( + this.project_id, + { + directory: this.directory, + mainFile: this.mainFile, + compiler: this.compiler, + image: this.image, + timeout: (this.timeout = 42000), + flags: ['-file-line-error', '-halt-on-error'], + }, + this.callback + ) + }) + + return it('should include the flags in the command', function () { + const command = this.CommandRunner.run.args[0][1] + const flags = command.filter( + arg => arg === '-file-line-error' || arg === '-halt-on-error' + ) + flags.length.should.equal(2) + flags[0].should.equal('-file-line-error') + return flags[1].should.equal('-halt-on-error') + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/LockManagerTests.js b/services/clsi/test/unit/js/LockManagerTests.js new file mode 100644 index 0000000000..e109054801 --- /dev/null +++ b/services/clsi/test/unit/js/LockManagerTests.js @@ -0,0 +1,88 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join( + __dirname, + '../../../app/js/LockManager' +) +const Path = require('path') +const Errors = require('../../../app/js/Errors') + +describe('DockerLockManager', function () { + beforeEach(function () { + this.LockManager = SandboxedModule.require(modulePath, { + requires: { + '@overleaf/settings': {}, + fs: { + lstat: sinon.stub().callsArgWith(1), + readdir: sinon.stub().callsArgWith(1), + }, + lockfile: (this.Lockfile = {}), + }, + }) + return (this.lockFile = '/local/compile/directory/.project-lock') + }) + + return describe('runWithLock', function () { + beforeEach(function () { + this.runner = sinon.stub().callsArgWith(0, null, 'foo', 'bar') + return (this.callback = sinon.stub()) + }) + + describe('normally', function () { + beforeEach(function () { + this.Lockfile.lock = sinon.stub().callsArgWith(2, null) + this.Lockfile.unlock = sinon.stub().callsArgWith(1, null) + return this.LockManager.runWithLock( + this.lockFile, + this.runner, + this.callback + ) + }) + + it('should run the compile', function () { + return this.runner.calledWith().should.equal(true) + }) + + return it('should call the callback with the response from the compile', function () { + return this.callback + .calledWithExactly(null, 'foo', 'bar') + .should.equal(true) + }) + }) + + return describe('when the project is locked', function () { + beforeEach(function () { + this.error = new Error() + this.error.code = 'EEXIST' + this.Lockfile.lock = sinon.stub().callsArgWith(2, this.error) + this.Lockfile.unlock = sinon.stub().callsArgWith(1, null) + return this.LockManager.runWithLock( + this.lockFile, + this.runner, + this.callback + ) + }) + + it('should not run the compile', function () { + return this.runner.called.should.equal(false) + }) + + it('should return an error', function () { + this.callback + .calledWithExactly(sinon.match(Errors.AlreadyCompilingError)) + .should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/OutputFileFinderTests.js b/services/clsi/test/unit/js/OutputFileFinderTests.js new file mode 100644 index 0000000000..9d1eb4388c --- /dev/null +++ b/services/clsi/test/unit/js/OutputFileFinderTests.js @@ -0,0 +1,107 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join( + __dirname, + '../../../app/js/OutputFileFinder' +) +const path = require('path') +const { expect } = require('chai') +const { EventEmitter } = require('events') + +describe('OutputFileFinder', function () { + beforeEach(function () { + this.OutputFileFinder = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = {}), + child_process: { spawn: (this.spawn = sinon.stub()) }, + }, + globals: { + Math, // used by lodash + }, + }) + this.directory = '/test/dir' + return (this.callback = sinon.stub()) + }) + + describe('findOutputFiles', function () { + beforeEach(function () { + this.resource_path = 'resource/path.tex' + this.output_paths = ['output.pdf', 'extra/file.tex'] + this.all_paths = this.output_paths.concat([this.resource_path]) + this.resources = [{ path: (this.resource_path = 'resource/path.tex') }] + this.OutputFileFinder._getAllFiles = sinon + .stub() + .callsArgWith(1, null, this.all_paths) + return this.OutputFileFinder.findOutputFiles( + this.resources, + this.directory, + (error, outputFiles) => { + this.outputFiles = outputFiles + } + ) + }) + + return it('should only return the output files, not directories or resource paths', function () { + return expect(this.outputFiles).to.deep.equal([ + { + path: 'output.pdf', + type: 'pdf', + }, + { + path: 'extra/file.tex', + type: 'tex', + }, + ]) + }) + }) + + return describe('_getAllFiles', function () { + beforeEach(function () { + this.proc = new EventEmitter() + this.proc.stdout = new EventEmitter() + this.proc.stdout.setEncoding = sinon.stub().returns(this.proc.stdout) + this.spawn.returns(this.proc) + this.directory = '/base/dir' + return this.OutputFileFinder._getAllFiles(this.directory, this.callback) + }) + + describe('successfully', function () { + beforeEach(function () { + this.proc.stdout.emit( + 'data', + ['/base/dir/main.tex', '/base/dir/chapters/chapter1.tex'].join('\n') + + '\n' + ) + return this.proc.emit('close', 0) + }) + + return it('should call the callback with the relative file paths', function () { + return this.callback + .calledWith(null, ['main.tex', 'chapters/chapter1.tex']) + .should.equal(true) + }) + }) + + return describe("when the directory doesn't exist", function () { + beforeEach(function () { + return this.proc.emit('close', 1) + }) + + return it('should call the callback with a blank array', function () { + return this.callback.calledWith(null, []).should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/OutputFileOptimiserTests.js b/services/clsi/test/unit/js/OutputFileOptimiserTests.js new file mode 100644 index 0000000000..6a0a015d97 --- /dev/null +++ b/services/clsi/test/unit/js/OutputFileOptimiserTests.js @@ -0,0 +1,192 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, + node/no-deprecated-api, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join( + __dirname, + '../../../app/js/OutputFileOptimiser' +) +const path = require('path') +const { expect } = require('chai') +const { EventEmitter } = require('events') + +describe('OutputFileOptimiser', function () { + beforeEach(function () { + this.OutputFileOptimiser = SandboxedModule.require(modulePath, { + requires: { + fs: (this.fs = {}), + path: (this.Path = {}), + child_process: { spawn: (this.spawn = sinon.stub()) }, + './Metrics': {}, + }, + globals: { Math }, // used by lodash + }) + this.directory = '/test/dir' + return (this.callback = sinon.stub()) + }) + + describe('optimiseFile', function () { + beforeEach(function () { + this.src = './output.pdf' + return (this.dst = './output.pdf') + }) + + describe('when the file is not a pdf file', function () { + beforeEach(function (done) { + this.src = './output.log' + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon + .stub() + .callsArgWith(1, null, false) + this.OutputFileOptimiser.optimisePDF = sinon + .stub() + .callsArgWith(2, null) + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) + }) + + it('should not check if the file is optimised', function () { + return this.OutputFileOptimiser.checkIfPDFIsOptimised + .calledWith(this.src) + .should.equal(false) + }) + + return it('should not optimise the file', function () { + return this.OutputFileOptimiser.optimisePDF + .calledWith(this.src, this.dst) + .should.equal(false) + }) + }) + + describe('when the pdf file is not optimised', function () { + beforeEach(function (done) { + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon + .stub() + .callsArgWith(1, null, false) + this.OutputFileOptimiser.optimisePDF = sinon + .stub() + .callsArgWith(2, null) + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) + }) + + it('should check if the pdf is optimised', function () { + return this.OutputFileOptimiser.checkIfPDFIsOptimised + .calledWith(this.src) + .should.equal(true) + }) + + return it('should optimise the pdf', function () { + return this.OutputFileOptimiser.optimisePDF + .calledWith(this.src, this.dst) + .should.equal(true) + }) + }) + + return describe('when the pdf file is optimised', function () { + beforeEach(function (done) { + this.OutputFileOptimiser.checkIfPDFIsOptimised = sinon + .stub() + .callsArgWith(1, null, true) + this.OutputFileOptimiser.optimisePDF = sinon + .stub() + .callsArgWith(2, null) + return this.OutputFileOptimiser.optimiseFile(this.src, this.dst, done) + }) + + it('should check if the pdf is optimised', function () { + return this.OutputFileOptimiser.checkIfPDFIsOptimised + .calledWith(this.src) + .should.equal(true) + }) + + return it('should not optimise the pdf', function () { + return this.OutputFileOptimiser.optimisePDF + .calledWith(this.src, this.dst) + .should.equal(false) + }) + }) + }) + + return describe('checkIfPDFISOptimised', function () { + beforeEach(function () { + this.callback = sinon.stub() + this.fd = 1234 + this.fs.open = sinon.stub().yields(null, this.fd) + this.fs.read = sinon + .stub() + .withArgs(this.fd) + .yields(null, 100, Buffer.from('hello /Linearized 1')) + this.fs.close = sinon.stub().withArgs(this.fd).yields(null) + return this.OutputFileOptimiser.checkIfPDFIsOptimised( + this.src, + this.callback + ) + }) + + describe('for a linearised file', function () { + beforeEach(function () { + this.fs.read = sinon + .stub() + .withArgs(this.fd) + .yields(null, 100, Buffer.from('hello /Linearized 1')) + return this.OutputFileOptimiser.checkIfPDFIsOptimised( + this.src, + this.callback + ) + }) + + it('should open the file', function () { + return this.fs.open.calledWith(this.src, 'r').should.equal(true) + }) + + it('should read the header', function () { + return this.fs.read.calledWith(this.fd).should.equal(true) + }) + + it('should close the file', function () { + return this.fs.close.calledWith(this.fd).should.equal(true) + }) + + return it('should call the callback with a true result', function () { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) + + return describe('for an unlinearised file', function () { + beforeEach(function () { + this.fs.read = sinon + .stub() + .withArgs(this.fd) + .yields(null, 100, Buffer.from('hello not linearized 1')) + return this.OutputFileOptimiser.checkIfPDFIsOptimised( + this.src, + this.callback + ) + }) + + it('should open the file', function () { + return this.fs.open.calledWith(this.src, 'r').should.equal(true) + }) + + it('should read the header', function () { + return this.fs.read.calledWith(this.fd).should.equal(true) + }) + + it('should close the file', function () { + return this.fs.close.calledWith(this.fd).should.equal(true) + }) + + return it('should call the callback with a false result', function () { + return this.callback.calledWith(null, false).should.equal(true) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js new file mode 100644 index 0000000000..eceaf89821 --- /dev/null +++ b/services/clsi/test/unit/js/ProjectPersistenceManagerTests.js @@ -0,0 +1,156 @@ +/* eslint-disable + camelcase, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const assert = require('chai').assert +const modulePath = require('path').join( + __dirname, + '../../../app/js/ProjectPersistenceManager' +) +const tk = require('timekeeper') + +describe('ProjectPersistenceManager', function () { + beforeEach(function () { + this.ProjectPersistenceManager = SandboxedModule.require(modulePath, { + requires: { + './UrlCache': (this.UrlCache = {}), + './CompileManager': (this.CompileManager = {}), + diskusage: (this.diskusage = { check: sinon.stub() }), + '@overleaf/settings': (this.settings = { + project_cache_length_ms: 1000, + path: { + compilesDir: '/compiles', + outputDir: '/output', + clsiCacheDir: '/cache', + }, + }), + './db': (this.db = {}), + }, + }) + this.callback = sinon.stub() + this.project_id = 'project-id-123' + return (this.user_id = '1234') + }) + + describe('refreshExpiryTimeout', function () { + it('should leave expiry alone if plenty of disk', function (done) { + this.diskusage.check.resolves({ + available: 40, + total: 100, + }) + + this.ProjectPersistenceManager.refreshExpiryTimeout(() => { + this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal( + this.settings.project_cache_length_ms + ) + done() + }) + }) + + it('should drop EXPIRY_TIMEOUT 10% if low disk usage', function (done) { + this.diskusage.check.resolves({ + available: 5, + total: 100, + }) + + this.ProjectPersistenceManager.refreshExpiryTimeout(() => { + this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal(900) + done() + }) + }) + + it('should not drop EXPIRY_TIMEOUT to below 50% of project_cache_length_ms', function (done) { + this.diskusage.check.resolves({ + available: 5, + total: 100, + }) + this.ProjectPersistenceManager.EXPIRY_TIMEOUT = 500 + this.ProjectPersistenceManager.refreshExpiryTimeout(() => { + this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal(500) + done() + }) + }) + + it('should not modify EXPIRY_TIMEOUT if there is an error getting disk values', function (done) { + this.diskusage.check.throws(new Error()) + this.ProjectPersistenceManager.refreshExpiryTimeout(() => { + this.ProjectPersistenceManager.EXPIRY_TIMEOUT.should.equal(1000) + done() + }) + }) + }) + + describe('clearExpiredProjects', function () { + beforeEach(function () { + this.project_ids = ['project-id-1', 'project-id-2'] + this.ProjectPersistenceManager._findExpiredProjectIds = sinon + .stub() + .callsArgWith(0, null, this.project_ids) + this.ProjectPersistenceManager.clearProjectFromCache = sinon + .stub() + .callsArg(1) + this.CompileManager.clearExpiredProjects = sinon.stub().callsArg(1) + return this.ProjectPersistenceManager.clearExpiredProjects(this.callback) + }) + + it('should clear each expired project', function () { + return Array.from(this.project_ids).map(project_id => + this.ProjectPersistenceManager.clearProjectFromCache + .calledWith(project_id) + .should.equal(true) + ) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + return describe('clearProject', function () { + beforeEach(function () { + this.ProjectPersistenceManager._clearProjectFromDatabase = sinon + .stub() + .callsArg(1) + this.UrlCache.clearProject = sinon.stub().callsArg(1) + this.CompileManager.clearProject = sinon.stub().callsArg(2) + return this.ProjectPersistenceManager.clearProject( + this.project_id, + this.user_id, + this.callback + ) + }) + + it('should clear the project from the database', function () { + return this.ProjectPersistenceManager._clearProjectFromDatabase + .calledWith(this.project_id) + .should.equal(true) + }) + + it('should clear all the cached Urls for the project', function () { + return this.UrlCache.clearProject + .calledWith(this.project_id) + .should.equal(true) + }) + + it('should clear the project compile folder', function () { + return this.CompileManager.clearProject + .calledWith(this.project_id, this.user_id) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/RequestParserTests.js b/services/clsi/test/unit/js/RequestParserTests.js new file mode 100644 index 0000000000..48364990b4 --- /dev/null +++ b/services/clsi/test/unit/js/RequestParserTests.js @@ -0,0 +1,461 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const { expect } = require('chai') +const modulePath = require('path').join( + __dirname, + '../../../app/js/RequestParser' +) +const tk = require('timekeeper') + +describe('RequestParser', function () { + beforeEach(function () { + tk.freeze() + this.callback = sinon.stub() + this.validResource = { + path: 'main.tex', + date: '12:00 01/02/03', + content: 'Hello world', + } + this.validRequest = { + compile: { + token: 'token-123', + options: { + imageName: 'basicImageName/here:2017-1', + compiler: 'pdflatex', + timeout: 42, + }, + resources: [], + }, + } + return (this.RequestParser = SandboxedModule.require(modulePath, { + requires: { + '@overleaf/settings': (this.settings = {}), + }, + })) + }) + + afterEach(function () { + return tk.reset() + }) + + describe('without a top level object', function () { + beforeEach(function () { + return this.RequestParser.parse([], this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('top level object should have a compile attribute') + .should.equal(true) + }) + }) + + describe('without a compile attribute', function () { + beforeEach(function () { + return this.RequestParser.parse({}, this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('top level object should have a compile attribute') + .should.equal(true) + }) + }) + + describe('without a valid compiler', function () { + beforeEach(function () { + this.validRequest.compile.options.compiler = 'not-a-compiler' + return this.RequestParser.parse(this.validRequest, this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith( + 'compiler attribute should be one of: pdflatex, latex, xelatex, lualatex' + ) + .should.equal(true) + }) + }) + + describe('without a compiler specified', function () { + beforeEach(function () { + delete this.validRequest.compile.options.compiler + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) + + return it('should set the compiler to pdflatex by default', function () { + return this.data.compiler.should.equal('pdflatex') + }) + }) + + describe('with imageName set', function () { + beforeEach(function () { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) + + return it('should set the imageName', function () { + return this.data.imageName.should.equal('basicImageName/here:2017-1') + }) + }) + + describe('when image restrictions are present', function () { + beforeEach(function () { + this.settings.clsi = { docker: {} } + this.settings.clsi.docker.allowedImages = [ + 'repo/name:tag1', + 'repo/name:tag2', + ] + }) + + describe('with imageName set to something invalid', function () { + beforeEach(function () { + const request = this.validRequest + request.compile.options.imageName = 'something/different:latest' + this.RequestParser.parse(request, (error, data) => { + this.error = error + this.data = data + }) + }) + + it('should throw an error for imageName', function () { + expect(String(this.error)).to.include( + 'imageName attribute should be one of' + ) + }) + }) + + describe('with imageName set to something valid', function () { + beforeEach(function () { + const request = this.validRequest + request.compile.options.imageName = 'repo/name:tag1' + this.RequestParser.parse(request, (error, data) => { + this.error = error + this.data = data + }) + }) + + it('should set the imageName', function () { + this.data.imageName.should.equal('repo/name:tag1') + }) + }) + }) + + describe('with flags set', function () { + beforeEach(function () { + this.validRequest.compile.options.flags = ['-file-line-error'] + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) + + return it('should set the flags attribute', function () { + return expect(this.data.flags).to.deep.equal(['-file-line-error']) + }) + }) + + describe('with flags not specified', function () { + beforeEach(function () { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) + + return it('it should have an empty flags list', function () { + return expect(this.data.flags).to.deep.equal([]) + }) + }) + + describe('without a timeout specified', function () { + beforeEach(function () { + delete this.validRequest.compile.options.timeout + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) + + return it('should set the timeout to MAX_TIMEOUT', function () { + return this.data.timeout.should.equal( + this.RequestParser.MAX_TIMEOUT * 1000 + ) + }) + }) + + describe('with a timeout larger than the maximum', function () { + beforeEach(function () { + this.validRequest.compile.options.timeout = + this.RequestParser.MAX_TIMEOUT + 1 + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) + + return it('should set the timeout to MAX_TIMEOUT', function () { + return this.data.timeout.should.equal( + this.RequestParser.MAX_TIMEOUT * 1000 + ) + }) + }) + + describe('with a timeout', function () { + beforeEach(function () { + return this.RequestParser.parse(this.validRequest, (error, data) => { + this.data = data + }) + }) + + return it('should set the timeout (in milliseconds)', function () { + return this.data.timeout.should.equal( + this.validRequest.compile.options.timeout * 1000 + ) + }) + }) + + describe('with a resource without a path', function () { + beforeEach(function () { + delete this.validResource.path + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('all resources should have a path attribute') + .should.equal(true) + }) + }) + + describe('with a resource with a path', function () { + beforeEach(function () { + this.validResource.path = this.path = 'test.tex' + this.validRequest.compile.resources.push(this.validResource) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it('should return the path in the parsed response', function () { + return this.data.resources[0].path.should.equal(this.path) + }) + }) + + describe('with a resource with a malformed modified date', function () { + beforeEach(function () { + this.validResource.modified = 'not-a-date' + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith( + 'resource modified date could not be understood: ' + + this.validResource.modified + ) + .should.equal(true) + }) + }) + + describe('with a resource with a valid date', function () { + beforeEach(function () { + this.date = '12:00 01/02/03' + this.validResource.modified = this.date + this.validRequest.compile.resources.push(this.validResource) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it('should return the date as a Javascript Date object', function () { + ;(this.data.resources[0].modified instanceof Date).should.equal(true) + return this.data.resources[0].modified + .getTime() + .should.equal(Date.parse(this.date)) + }) + }) + + describe('with a resource without either a content or URL attribute', function () { + beforeEach(function () { + delete this.validResource.url + delete this.validResource.content + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith( + 'all resources should have either a url or content attribute' + ) + .should.equal(true) + }) + }) + + describe('with a resource where the content is not a string', function () { + beforeEach(function () { + this.validResource.content = [] + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('content attribute should be a string') + .should.equal(true) + }) + }) + + describe('with a resource where the url is not a string', function () { + beforeEach(function () { + this.validResource.url = [] + this.validRequest.compile.resources.push(this.validResource) + return this.RequestParser.parse(this.validRequest, this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('url attribute should be a string') + .should.equal(true) + }) + }) + + describe('with a resource with a url', function () { + beforeEach(function () { + this.validResource.url = this.url = 'www.example.com' + this.validRequest.compile.resources.push(this.validResource) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it('should return the url in the parsed response', function () { + return this.data.resources[0].url.should.equal(this.url) + }) + }) + + describe('with a resource with a content attribute', function () { + beforeEach(function () { + this.validResource.content = this.content = 'Hello world' + this.validRequest.compile.resources.push(this.validResource) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it('should return the content in the parsed response', function () { + return this.data.resources[0].content.should.equal(this.content) + }) + }) + + describe('without a root resource path', function () { + beforeEach(function () { + delete this.validRequest.compile.rootResourcePath + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it("should set the root resource path to 'main.tex' by default", function () { + return this.data.rootResourcePath.should.equal('main.tex') + }) + }) + + describe('with a root resource path', function () { + beforeEach(function () { + this.validRequest.compile.rootResourcePath = this.path = 'test.tex' + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it('should return the root resource path in the parsed response', function () { + return this.data.rootResourcePath.should.equal(this.path) + }) + }) + + describe('with a root resource path that is not a string', function () { + beforeEach(function () { + this.validRequest.compile.rootResourcePath = [] + return this.RequestParser.parse(this.validRequest, this.callback) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('rootResourcePath attribute should be a string') + .should.equal(true) + }) + }) + + describe('with a root resource path that needs escaping', function () { + beforeEach(function () { + this.badPath = '`rm -rf foo`.tex' + this.goodPath = 'rm -rf foo.tex' + this.validRequest.compile.rootResourcePath = this.badPath + this.validRequest.compile.resources.push({ + path: this.badPath, + date: '12:00 01/02/03', + content: 'Hello world', + }) + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + it('should return the escaped resource', function () { + return this.data.rootResourcePath.should.equal(this.goodPath) + }) + + return it('should also escape the resource path', function () { + return this.data.resources[0].path.should.equal(this.goodPath) + }) + }) + + describe('with a root resource path that has a relative path', function () { + beforeEach(function () { + this.validRequest.compile.rootResourcePath = 'foo/../../bar.tex' + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('relative path in root resource') + .should.equal(true) + }) + }) + + describe('with a root resource path that has unescaped + relative path', function () { + beforeEach(function () { + this.validRequest.compile.rootResourcePath = 'foo/#../bar.tex' + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('relative path in root resource') + .should.equal(true) + }) + }) + + return describe('with an unknown syncType', function () { + beforeEach(function () { + this.validRequest.compile.options.syncType = 'unexpected' + this.RequestParser.parse(this.validRequest, this.callback) + return (this.data = this.callback.args[0][1]) + }) + + return it('should return an error', function () { + return this.callback + .calledWith('syncType attribute should be one of: full, incremental') + .should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ResourceStateManagerTests.js b/services/clsi/test/unit/js/ResourceStateManagerTests.js new file mode 100644 index 0000000000..0a97d7b705 --- /dev/null +++ b/services/clsi/test/unit/js/ResourceStateManagerTests.js @@ -0,0 +1,241 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const { expect } = require('chai') +const modulePath = require('path').join( + __dirname, + '../../../app/js/ResourceStateManager' +) +const Path = require('path') +const Errors = require('../../../app/js/Errors') + +describe('ResourceStateManager', function () { + beforeEach(function () { + this.ResourceStateManager = SandboxedModule.require(modulePath, { + singleOnly: true, + requires: { + fs: (this.fs = {}), + './SafeReader': (this.SafeReader = {}), + }, + }) + this.basePath = '/path/to/write/files/to' + this.resources = [ + { path: 'resource-1-mock' }, + { path: 'resource-2-mock' }, + { path: 'resource-3-mock' }, + ] + this.state = '1234567890' + this.resourceFileName = `${this.basePath}/.project-sync-state` + this.resourceFileContents = `${this.resources[0].path}\n${this.resources[1].path}\n${this.resources[2].path}\nstateHash:${this.state}` + return (this.callback = sinon.stub()) + }) + + describe('saveProjectState', function () { + beforeEach(function () { + return (this.fs.writeFile = sinon.stub().callsArg(2)) + }) + + describe('when the state is specified', function () { + beforeEach(function () { + return this.ResourceStateManager.saveProjectState( + this.state, + this.resources, + this.basePath, + this.callback + ) + }) + + it('should write the resource list to disk', function () { + return this.fs.writeFile + .calledWith(this.resourceFileName, this.resourceFileContents) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + return describe('when the state is undefined', function () { + beforeEach(function () { + this.state = undefined + this.fs.unlink = sinon.stub().callsArg(1) + return this.ResourceStateManager.saveProjectState( + this.state, + this.resources, + this.basePath, + this.callback + ) + }) + + it('should unlink the resource file', function () { + return this.fs.unlink + .calledWith(this.resourceFileName) + .should.equal(true) + }) + + it('should not write the resource list to disk', function () { + return this.fs.writeFile.called.should.equal(false) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + }) + + describe('checkProjectStateMatches', function () { + describe('when the state matches', function () { + beforeEach(function () { + this.SafeReader.readFile = sinon + .stub() + .callsArgWith(3, null, this.resourceFileContents) + return this.ResourceStateManager.checkProjectStateMatches( + this.state, + this.basePath, + this.callback + ) + }) + + it('should read the resource file', function () { + return this.SafeReader.readFile + .calledWith(this.resourceFileName) + .should.equal(true) + }) + + return it('should call the callback with the results', function () { + return this.callback + .calledWithMatch(null, this.resources) + .should.equal(true) + }) + }) + + describe('when the state file is not present', function () { + beforeEach(function () { + this.SafeReader.readFile = sinon.stub().callsArg(3) + return this.ResourceStateManager.checkProjectStateMatches( + this.state, + this.basePath, + this.callback + ) + }) + + it('should read the resource file', function () { + return this.SafeReader.readFile + .calledWith(this.resourceFileName) + .should.equal(true) + }) + + it('should call the callback with an error', function () { + this.callback + .calledWith(sinon.match(Errors.FilesOutOfSyncError)) + .should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('invalid state for incremental update') + }) + }) + + return describe('when the state does not match', function () { + beforeEach(function () { + this.SafeReader.readFile = sinon + .stub() + .callsArgWith(3, null, this.resourceFileContents) + return this.ResourceStateManager.checkProjectStateMatches( + 'not-the-original-state', + this.basePath, + this.callback + ) + }) + + it('should call the callback with an error', function () { + this.callback + .calledWith(sinon.match(Errors.FilesOutOfSyncError)) + .should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('invalid state for incremental update') + }) + }) + }) + + return describe('checkResourceFiles', function () { + describe('when all the files are present', function () { + beforeEach(function () { + this.allFiles = [ + this.resources[0].path, + this.resources[1].path, + this.resources[2].path, + ] + return this.ResourceStateManager.checkResourceFiles( + this.resources, + this.allFiles, + this.basePath, + this.callback + ) + }) + + return it('should call the callback', function () { + return this.callback.calledWithExactly().should.equal(true) + }) + }) + + describe('when there is a missing file', function () { + beforeEach(function () { + this.allFiles = [this.resources[0].path, this.resources[1].path] + this.fs.stat = sinon.stub().callsArgWith(1, new Error()) + return this.ResourceStateManager.checkResourceFiles( + this.resources, + this.allFiles, + this.basePath, + this.callback + ) + }) + + it('should call the callback with an error', function () { + this.callback + .calledWith(sinon.match(Errors.FilesOutOfSyncError)) + .should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include( + 'resource files missing in incremental update' + ) + }) + }) + + return describe('when a resource contains a relative path', function () { + beforeEach(function () { + this.resources[0].path = '../foo/bar.tex' + this.allFiles = [ + this.resources[0].path, + this.resources[1].path, + this.resources[2].path, + ] + return this.ResourceStateManager.checkResourceFiles( + this.resources, + this.allFiles, + this.basePath, + this.callback + ) + }) + + it('should call the callback with an error', function () { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('relative path in resource file list') + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/ResourceWriterTests.js b/services/clsi/test/unit/js/ResourceWriterTests.js new file mode 100644 index 0000000000..4d69f557f6 --- /dev/null +++ b/services/clsi/test/unit/js/ResourceWriterTests.js @@ -0,0 +1,518 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS206: Consider reworking classes to avoid initClass + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const { expect } = require('chai') +const modulePath = require('path').join( + __dirname, + '../../../app/js/ResourceWriter' +) +const path = require('path') + +describe('ResourceWriter', function () { + beforeEach(function () { + let Timer + this.ResourceWriter = SandboxedModule.require(modulePath, { + singleOnly: true, + requires: { + fs: (this.fs = { + mkdir: sinon.stub().callsArg(1), + unlink: sinon.stub().callsArg(1), + }), + './ResourceStateManager': (this.ResourceStateManager = {}), + wrench: (this.wrench = {}), + './UrlCache': (this.UrlCache = {}), + './OutputFileFinder': (this.OutputFileFinder = {}), + './Metrics': (this.Metrics = { + inc: sinon.stub(), + Timer: (Timer = (function () { + Timer = class Timer { + static initClass() { + this.prototype.done = sinon.stub() + } + } + Timer.initClass() + return Timer + })()), + }), + }, + }) + this.project_id = 'project-id-123' + this.basePath = '/path/to/write/files/to' + return (this.callback = sinon.stub()) + }) + + describe('syncResourcesToDisk on a full request', function () { + beforeEach(function () { + this.resources = ['resource-1-mock', 'resource-2-mock', 'resource-3-mock'] + this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) + this.ResourceWriter._removeExtraneousFiles = sinon.stub().callsArg(2) + this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3) + return this.ResourceWriter.syncResourcesToDisk( + { + project_id: this.project_id, + syncState: (this.syncState = '0123456789abcdef'), + resources: this.resources, + }, + this.basePath, + this.callback + ) + }) + + it('should remove old files', function () { + return this.ResourceWriter._removeExtraneousFiles + .calledWith(this.resources, this.basePath) + .should.equal(true) + }) + + it('should write each resource to disk', function () { + return Array.from(this.resources).map(resource => + this.ResourceWriter._writeResourceToDisk + .calledWith(this.project_id, resource, this.basePath) + .should.equal(true) + ) + }) + + it('should store the sync state and resource list', function () { + return this.ResourceStateManager.saveProjectState + .calledWith(this.syncState, this.resources, this.basePath) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + describe('syncResourcesToDisk on an incremental update', function () { + beforeEach(function () { + this.resources = ['resource-1-mock'] + this.ResourceWriter._writeResourceToDisk = sinon.stub().callsArg(3) + this.ResourceWriter._removeExtraneousFiles = sinon + .stub() + .callsArgWith(2, null, (this.outputFiles = []), (this.allFiles = [])) + this.ResourceStateManager.checkProjectStateMatches = sinon + .stub() + .callsArgWith(2, null, this.resources) + this.ResourceStateManager.saveProjectState = sinon.stub().callsArg(3) + this.ResourceStateManager.checkResourceFiles = sinon.stub().callsArg(3) + return this.ResourceWriter.syncResourcesToDisk( + { + project_id: this.project_id, + syncType: 'incremental', + syncState: (this.syncState = '1234567890abcdef'), + resources: this.resources, + }, + this.basePath, + this.callback + ) + }) + + it('should check the sync state matches', function () { + return this.ResourceStateManager.checkProjectStateMatches + .calledWith(this.syncState, this.basePath) + .should.equal(true) + }) + + it('should remove old files', function () { + return this.ResourceWriter._removeExtraneousFiles + .calledWith(this.resources, this.basePath) + .should.equal(true) + }) + + it('should check each resource exists', function () { + return this.ResourceStateManager.checkResourceFiles + .calledWith(this.resources, this.allFiles, this.basePath) + .should.equal(true) + }) + + it('should write each resource to disk', function () { + return Array.from(this.resources).map(resource => + this.ResourceWriter._writeResourceToDisk + .calledWith(this.project_id, resource, this.basePath) + .should.equal(true) + ) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + describe('syncResourcesToDisk on an incremental update when the state does not match', function () { + beforeEach(function () { + this.resources = ['resource-1-mock'] + this.ResourceStateManager.checkProjectStateMatches = sinon + .stub() + .callsArgWith(2, (this.error = new Error())) + return this.ResourceWriter.syncResourcesToDisk( + { + project_id: this.project_id, + syncType: 'incremental', + syncState: (this.syncState = '1234567890abcdef'), + resources: this.resources, + }, + this.basePath, + this.callback + ) + }) + + it('should check whether the sync state matches', function () { + return this.ResourceStateManager.checkProjectStateMatches + .calledWith(this.syncState, this.basePath) + .should.equal(true) + }) + + return it('should call the callback with an error', function () { + return this.callback.calledWith(this.error).should.equal(true) + }) + }) + + describe('_removeExtraneousFiles', function () { + beforeEach(function () { + this.output_files = [ + { + path: 'output.pdf', + type: 'pdf', + }, + { + path: 'extra/file.tex', + type: 'tex', + }, + { + path: 'extra.aux', + type: 'aux', + }, + { + path: 'cache/_chunk1', + }, + { + path: 'figures/image-eps-converted-to.pdf', + type: 'pdf', + }, + { + path: 'foo/main-figure0.md5', + type: 'md5', + }, + { + path: 'foo/main-figure0.dpth', + type: 'dpth', + }, + { + path: 'foo/main-figure0.pdf', + type: 'pdf', + }, + { + path: '_minted-main/default-pyg-prefix.pygstyle', + type: 'pygstyle', + }, + { + path: '_minted-main/default.pygstyle', + type: 'pygstyle', + }, + { + path: '_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex', + type: 'pygtex', + }, + { + path: '_markdown_main/30893013dec5d869a415610079774c2f.md.tex', + type: 'tex', + }, + { + path: 'output.stdout', + }, + { + path: 'output.stderr', + }, + ] + this.resources = 'mock-resources' + this.OutputFileFinder.findOutputFiles = sinon + .stub() + .callsArgWith(2, null, this.output_files) + this.ResourceWriter._deleteFileIfNotDirectory = sinon.stub().callsArg(1) + return this.ResourceWriter._removeExtraneousFiles( + this.resources, + this.basePath, + this.callback + ) + }) + + it('should find the existing output files', function () { + return this.OutputFileFinder.findOutputFiles + .calledWith(this.resources, this.basePath) + .should.equal(true) + }) + + it('should delete the output files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'output.pdf')) + .should.equal(true) + }) + + it('should delete the stdout log file', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'output.stdout')) + .should.equal(true) + }) + + it('should delete the stderr log file', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'output.stderr')) + .should.equal(true) + }) + + it('should delete the extra files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'extra/file.tex')) + .should.equal(true) + }) + + it('should not delete the extra aux files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'extra.aux')) + .should.equal(false) + }) + + it('should not delete the knitr cache file', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'cache/_chunk1')) + .should.equal(false) + }) + + it('should not delete the epstopdf converted files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith( + path.join(this.basePath, 'figures/image-eps-converted-to.pdf') + ) + .should.equal(false) + }) + + it('should not delete the tikz md5 files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'foo/main-figure0.md5')) + .should.equal(false) + }) + + it('should not delete the tikz dpth files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'foo/main-figure0.dpth')) + .should.equal(false) + }) + + it('should not delete the tikz pdf files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, 'foo/main-figure0.pdf')) + .should.equal(false) + }) + + it('should not delete the minted pygstyle files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith( + path.join(this.basePath, '_minted-main/default-pyg-prefix.pygstyle') + ) + .should.equal(false) + }) + + it('should not delete the minted default pygstyle files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith(path.join(this.basePath, '_minted-main/default.pygstyle')) + .should.equal(false) + }) + + it('should not delete the minted default pygtex files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith( + path.join( + this.basePath, + '_minted-main/35E248B60965545BD232AE9F0FE9750D504A7AF0CD3BAA7542030FC560DFCC45.pygtex' + ) + ) + .should.equal(false) + }) + + it('should not delete the markdown md.tex files', function () { + return this.ResourceWriter._deleteFileIfNotDirectory + .calledWith( + path.join( + this.basePath, + '_markdown_main/30893013dec5d869a415610079774c2f.md.tex' + ) + ) + .should.equal(false) + }) + + it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + + return it('should time the request', function () { + return this.Metrics.Timer.prototype.done.called.should.equal(true) + }) + }) + + describe('_writeResourceToDisk', function () { + describe('with a url based resource', function () { + beforeEach(function () { + this.fs.mkdir = sinon.stub().callsArg(2) + this.resource = { + path: 'main.tex', + url: 'http://www.example.com/main.tex', + modified: Date.now(), + } + this.UrlCache.downloadUrlToFile = sinon + .stub() + .callsArgWith(4, 'fake error downloading file') + return this.ResourceWriter._writeResourceToDisk( + this.project_id, + this.resource, + this.basePath, + this.callback + ) + }) + + it('should ensure the directory exists', function () { + this.fs.mkdir + .calledWith( + path.dirname(path.join(this.basePath, this.resource.path)) + ) + .should.equal(true) + }) + + it('should write the URL from the cache', function () { + return this.UrlCache.downloadUrlToFile + .calledWith( + this.project_id, + this.resource.url, + path.join(this.basePath, this.resource.path), + this.resource.modified + ) + .should.equal(true) + }) + + it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + + return it('should not return an error if the resource writer errored', function () { + return expect(this.callback.args[0][0]).not.to.exist + }) + }) + + describe('with a content based resource', function () { + beforeEach(function () { + this.resource = { + path: 'main.tex', + content: 'Hello world', + } + this.fs.writeFile = sinon.stub().callsArg(2) + this.fs.mkdir = sinon.stub().callsArg(2) + return this.ResourceWriter._writeResourceToDisk( + this.project_id, + this.resource, + this.basePath, + this.callback + ) + }) + + it('should ensure the directory exists', function () { + return this.fs.mkdir + .calledWith( + path.dirname(path.join(this.basePath, this.resource.path)) + ) + .should.equal(true) + }) + + it('should write the contents to disk', function () { + return this.fs.writeFile + .calledWith( + path.join(this.basePath, this.resource.path), + this.resource.content + ) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + return describe('with a file path that breaks out of the root folder', function () { + beforeEach(function () { + this.resource = { + path: '../../main.tex', + content: 'Hello world', + } + this.fs.writeFile = sinon.stub().callsArg(2) + return this.ResourceWriter._writeResourceToDisk( + this.project_id, + this.resource, + this.basePath, + this.callback + ) + }) + + it('should not write to disk', function () { + return this.fs.writeFile.called.should.equal(false) + }) + + it('should return an error', function () { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('resource path is outside root directory') + }) + }) + }) + + return describe('checkPath', function () { + describe('with a valid path', function () { + beforeEach(function () { + return this.ResourceWriter.checkPath('foo', 'bar', this.callback) + }) + + return it('should return the joined path', function () { + return this.callback.calledWith(null, 'foo/bar').should.equal(true) + }) + }) + + describe('with an invalid path', function () { + beforeEach(function () { + this.ResourceWriter.checkPath('foo', 'baz/../../bar', this.callback) + }) + + it('should return an error', function () { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('resource path is outside root directory') + }) + }) + + describe('with another invalid path matching on a prefix', function () { + beforeEach(function () { + return this.ResourceWriter.checkPath( + 'foo', + '../foobar/baz', + this.callback + ) + }) + + it('should return an error', function () { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include('resource path is outside root directory') + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js new file mode 100644 index 0000000000..dd67506bfc --- /dev/null +++ b/services/clsi/test/unit/js/StaticServerForbidSymlinksTests.js @@ -0,0 +1,231 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const assert = require('assert') +const path = require('path') +const sinon = require('sinon') +const modulePath = path.join( + __dirname, + '../../../app/js/StaticServerForbidSymlinks' +) +const { expect } = require('chai') + +describe('StaticServerForbidSymlinks', function () { + beforeEach(function () { + this.settings = { + path: { + compilesDir: '/compiles/here', + }, + } + + this.fs = {} + this.ForbidSymlinks = SandboxedModule.require(modulePath, { + requires: { + '@overleaf/settings': this.settings, + fs: this.fs, + }, + }) + + this.dummyStatic = (rootDir, options) => (req, res, next) => + // console.log "dummyStatic serving file", rootDir, "called with", req.url + // serve it + next() + + this.StaticServerForbidSymlinks = this.ForbidSymlinks( + this.dummyStatic, + this.settings.path.compilesDir + ) + this.req = { + params: { + project_id: '12345', + }, + } + + this.res = {} + return (this.req.url = '/12345/output.pdf') + }) + + describe('sending a normal file through', function () { + beforeEach(function () { + return (this.fs.realpath = sinon + .stub() + .callsArgWith( + 1, + null, + `${this.settings.path.compilesDir}/${this.req.params.project_id}/output.pdf` + )) + }) + + return it('should call next', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(200) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res, done) + }) + }) + + describe('with a missing file', function () { + beforeEach(function () { + return (this.fs.realpath = sinon + .stub() + .callsArgWith( + 1, + { code: 'ENOENT' }, + `${this.settings.path.compilesDir}/${this.req.params.project_id}/unknown.pdf` + )) + }) + + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + + describe('with a symlink file', function () { + beforeEach(function () { + return (this.fs.realpath = sinon + .stub() + .callsArgWith(1, null, `/etc/${this.req.params.project_id}/output.pdf`)) + }) + + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + + describe('with a relative file', function () { + beforeEach(function () { + return (this.req.url = '/12345/../67890/output.pdf') + }) + + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + + describe('with a unnormalized file containing .', function () { + beforeEach(function () { + return (this.req.url = '/12345/foo/./output.pdf') + }) + + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + + describe('with a file containing an empty path', function () { + beforeEach(function () { + return (this.req.url = '/12345/foo//output.pdf') + }) + + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + + describe('with a non-project file', function () { + beforeEach(function () { + return (this.req.url = '/.foo/output.pdf') + }) + + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + + describe('with a file outside the compiledir', function () { + beforeEach(function () { + return (this.req.url = '/../bar/output.pdf') + }) + + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + + describe('with a file with no leading /', function () { + beforeEach(function () { + return (this.req.url = './../bar/output.pdf') + }) + + return it('should send a 404', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(404) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) + + describe('with a github style path', function () { + beforeEach(function () { + this.req.url = '/henryoswald-latex_example/output/output.log' + return (this.fs.realpath = sinon + .stub() + .callsArgWith( + 1, + null, + `${this.settings.path.compilesDir}/henryoswald-latex_example/output/output.log` + )) + }) + + return it('should call next', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(200) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res, done) + }) + }) + + return describe('with an error from fs.realpath', function () { + beforeEach(function () { + return (this.fs.realpath = sinon.stub().callsArgWith(1, 'error')) + }) + + return it('should send a 500', function (done) { + this.res.sendStatus = function (resCode) { + resCode.should.equal(500) + return done() + } + return this.StaticServerForbidSymlinks(this.req, this.res) + }) + }) +}) diff --git a/services/clsi/test/unit/js/TikzManager.js b/services/clsi/test/unit/js/TikzManager.js new file mode 100644 index 0000000000..8e01194955 --- /dev/null +++ b/services/clsi/test/unit/js/TikzManager.js @@ -0,0 +1,185 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join( + __dirname, + '../../../app/js/TikzManager' +) + +describe('TikzManager', function () { + beforeEach(function () { + return (this.TikzManager = SandboxedModule.require(modulePath, { + requires: { + './ResourceWriter': (this.ResourceWriter = {}), + './SafeReader': (this.SafeReader = {}), + fs: (this.fs = {}), + }, + })) + }) + + describe('checkMainFile', function () { + beforeEach(function () { + this.compileDir = 'compile-dir' + this.mainFile = 'main.tex' + return (this.callback = sinon.stub()) + }) + + describe('if there is already an output.tex file in the resources', function () { + beforeEach(function () { + this.resources = [{ path: 'main.tex' }, { path: 'output.tex' }] + return this.TikzManager.checkMainFile( + this.compileDir, + this.mainFile, + this.resources, + this.callback + ) + }) + + return it('should call the callback with false ', function () { + return this.callback.calledWithExactly(null, false).should.equal(true) + }) + }) + + return describe('if there is no output.tex file in the resources', function () { + beforeEach(function () { + this.resources = [{ path: 'main.tex' }] + return (this.ResourceWriter.checkPath = sinon + .stub() + .withArgs(this.compileDir, this.mainFile) + .callsArgWith(2, null, `${this.compileDir}/${this.mainFile}`)) + }) + + describe('and the main file contains tikzexternalize', function () { + beforeEach(function () { + this.SafeReader.readFile = sinon + .stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, 'hello \\tikzexternalize') + return this.TikzManager.checkMainFile( + this.compileDir, + this.mainFile, + this.resources, + this.callback + ) + }) + + it('should look at the file on disk', function () { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true) + }) + + return it('should call the callback with true ', function () { + return this.callback.calledWithExactly(null, true).should.equal(true) + }) + }) + + describe('and the main file does not contain tikzexternalize', function () { + beforeEach(function () { + this.SafeReader.readFile = sinon + .stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, 'hello') + return this.TikzManager.checkMainFile( + this.compileDir, + this.mainFile, + this.resources, + this.callback + ) + }) + + it('should look at the file on disk', function () { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true) + }) + + return it('should call the callback with false', function () { + return this.callback.calledWithExactly(null, false).should.equal(true) + }) + }) + + return describe('and the main file contains \\usepackage{pstool}', function () { + beforeEach(function () { + this.SafeReader.readFile = sinon + .stub() + .withArgs(`${this.compileDir}/${this.mainFile}`) + .callsArgWith(3, null, 'hello \\usepackage[random-options]{pstool}') + return this.TikzManager.checkMainFile( + this.compileDir, + this.mainFile, + this.resources, + this.callback + ) + }) + + it('should look at the file on disk', function () { + return this.SafeReader.readFile + .calledWith(`${this.compileDir}/${this.mainFile}`) + .should.equal(true) + }) + + return it('should call the callback with true ', function () { + return this.callback.calledWithExactly(null, true).should.equal(true) + }) + }) + }) + }) + + return describe('injectOutputFile', function () { + beforeEach(function () { + this.rootDir = '/mock' + this.filename = 'filename.tex' + this.callback = sinon.stub() + this.content = `\ +\\documentclass{article} +\\usepackage{tikz} +\\tikzexternalize +\\begin{document} +Hello world +\\end{document}\ +` + this.fs.readFile = sinon.stub().callsArgWith(2, null, this.content) + this.fs.writeFile = sinon.stub().callsArg(3) + this.ResourceWriter.checkPath = sinon + .stub() + .callsArgWith(2, null, `${this.rootDir}/${this.filename}`) + return this.TikzManager.injectOutputFile( + this.rootDir, + this.filename, + this.callback + ) + }) + + it('sould check the path', function () { + return this.ResourceWriter.checkPath + .calledWith(this.rootDir, this.filename) + .should.equal(true) + }) + + it('should read the file', function () { + return this.fs.readFile + .calledWith(`${this.rootDir}/${this.filename}`, 'utf8') + .should.equal(true) + }) + + it('should write out the same file as output.tex', function () { + return this.fs.writeFile + .calledWith(`${this.rootDir}/output.tex`, this.content, { flag: 'wx' }) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/UrlCacheTests.js b/services/clsi/test/unit/js/UrlCacheTests.js new file mode 100644 index 0000000000..33decd3418 --- /dev/null +++ b/services/clsi/test/unit/js/UrlCacheTests.js @@ -0,0 +1,353 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const modulePath = require('path').join(__dirname, '../../../app/js/UrlCache') +const { EventEmitter } = require('events') + +describe('UrlCache', function () { + beforeEach(function () { + this.callback = sinon.stub() + this.url = 'www.example.com/file' + this.project_id = 'project-id-123' + return (this.UrlCache = SandboxedModule.require(modulePath, { + requires: { + './db': {}, + './UrlFetcher': (this.UrlFetcher = {}), + '@overleaf/settings': (this.Settings = { + path: { clsiCacheDir: '/cache/dir' }, + }), + fs: (this.fs = { copyFile: sinon.stub().yields() }), + }, + })) + }) + + describe('_doesUrlNeedDownloading', function () { + beforeEach(function () { + this.lastModified = new Date() + return (this.lastModifiedRoundedToSeconds = new Date( + Math.floor(this.lastModified.getTime() / 1000) * 1000 + )) + }) + + describe('when URL does not exist in cache', function () { + beforeEach(function () { + this.UrlCache._findUrlDetails = sinon.stub().callsArgWith(2, null, null) + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) + + return it('should return the callback with true', function () { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) + + return describe('when URL does exist in cache', function () { + beforeEach(function () { + this.urlDetails = {} + return (this.UrlCache._findUrlDetails = sinon + .stub() + .callsArgWith(2, null, this.urlDetails)) + }) + + describe('when the modified date is more recent than the cached modified date', function () { + beforeEach(function () { + this.urlDetails.lastModified = new Date( + this.lastModified.getTime() - 1000 + ) + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) + + it('should get the url details', function () { + return this.UrlCache._findUrlDetails + .calledWith(this.project_id, this.url) + .should.equal(true) + }) + + return it('should return the callback with true', function () { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) + + describe('when the cached modified date is more recent than the modified date', function () { + beforeEach(function () { + this.urlDetails.lastModified = new Date( + this.lastModified.getTime() + 1000 + ) + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) + + return it('should return the callback with false', function () { + return this.callback.calledWith(null, false).should.equal(true) + }) + }) + + describe('when the cached modified date is equal to the modified date', function () { + beforeEach(function () { + this.urlDetails.lastModified = this.lastModified + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) + + return it('should return the callback with false', function () { + return this.callback.calledWith(null, false).should.equal(true) + }) + }) + + describe('when the provided modified date does not exist', function () { + beforeEach(function () { + this.lastModified = null + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) + + return it('should return the callback with true', function () { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) + + return describe('when the URL does not have a modified date', function () { + beforeEach(function () { + this.urlDetails.lastModified = null + return this.UrlCache._doesUrlNeedDownloading( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) + + return it('should return the callback with true', function () { + return this.callback.calledWith(null, true).should.equal(true) + }) + }) + }) + }) + + describe('_ensureUrlIsInCache', function () { + beforeEach(function () { + this.UrlFetcher.pipeUrlToFileWithRetry = sinon.stub().callsArg(2) + return (this.UrlCache._updateOrCreateUrlDetails = sinon + .stub() + .callsArg(3)) + }) + + describe('when the URL needs updating', function () { + beforeEach(function () { + this.UrlCache._doesUrlNeedDownloading = sinon + .stub() + .callsArgWith(3, null, true) + return this.UrlCache._ensureUrlIsInCache( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) + + it('should check that the url needs downloading', function () { + return this.UrlCache._doesUrlNeedDownloading + .calledWith( + this.project_id, + this.url, + this.lastModifiedRoundedToSeconds + ) + .should.equal(true) + }) + + it('should download the URL to the cache file', function () { + return this.UrlFetcher.pipeUrlToFileWithRetry + .calledWith( + this.url, + this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) + ) + .should.equal(true) + }) + + it('should update the database entry', function () { + return this.UrlCache._updateOrCreateUrlDetails + .calledWith( + this.project_id, + this.url, + this.lastModifiedRoundedToSeconds + ) + .should.equal(true) + }) + + return it('should return the callback with the cache file path', function () { + return this.callback + .calledWith( + null, + this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) + ) + .should.equal(true) + }) + }) + + return describe('when the URL does not need updating', function () { + beforeEach(function () { + this.UrlCache._doesUrlNeedDownloading = sinon + .stub() + .callsArgWith(3, null, false) + return this.UrlCache._ensureUrlIsInCache( + this.project_id, + this.url, + this.lastModified, + this.callback + ) + }) + + it('should not download the URL to the cache file', function () { + return this.UrlFetcher.pipeUrlToFileWithRetry.called.should.equal(false) + }) + + return it('should return the callback with the cache file path', function () { + return this.callback + .calledWith( + null, + this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) + ) + .should.equal(true) + }) + }) + }) + + describe('downloadUrlToFile', function () { + beforeEach(function () { + this.cachePath = 'path/to/cached/url' + this.destPath = 'path/to/destination' + this.UrlCache._ensureUrlIsInCache = sinon + .stub() + .callsArgWith(3, null, this.cachePath) + return this.UrlCache.downloadUrlToFile( + this.project_id, + this.url, + this.destPath, + this.lastModified, + this.callback + ) + }) + + it('should ensure the URL is downloaded and updated in the cache', function () { + return this.UrlCache._ensureUrlIsInCache + .calledWith(this.project_id, this.url, this.lastModified) + .should.equal(true) + }) + + it('should copy the file to the new location', function () { + return this.fs.copyFile + .calledWith(this.cachePath, this.destPath) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + describe('_deleteUrlCacheFromDisk', function () { + beforeEach(function () { + this.fs.unlink = sinon.stub().callsArg(1) + return this.UrlCache._deleteUrlCacheFromDisk( + this.project_id, + this.url, + this.callback + ) + }) + + it('should delete the cache file', function () { + return this.fs.unlink + .calledWith( + this.UrlCache._cacheFilePathForUrl(this.project_id, this.url) + ) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + describe('_clearUrlFromCache', function () { + beforeEach(function () { + this.UrlCache._deleteUrlCacheFromDisk = sinon.stub().callsArg(2) + this.UrlCache._clearUrlDetails = sinon.stub().callsArg(2) + return this.UrlCache._clearUrlFromCache( + this.project_id, + this.url, + this.callback + ) + }) + + it('should delete the file on disk', function () { + return this.UrlCache._deleteUrlCacheFromDisk + .calledWith(this.project_id, this.url) + .should.equal(true) + }) + + it('should clear the entry in the database', function () { + return this.UrlCache._clearUrlDetails + .calledWith(this.project_id, this.url) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + return describe('clearProject', function () { + beforeEach(function () { + this.urls = ['www.example.com/file1', 'www.example.com/file2'] + this.UrlCache._findAllUrlsInProject = sinon + .stub() + .callsArgWith(1, null, this.urls) + this.UrlCache._clearUrlFromCache = sinon.stub().callsArg(2) + return this.UrlCache.clearProject(this.project_id, this.callback) + }) + + it('should clear the cache for each url in the project', function () { + return Array.from(this.urls).map(url => + this.UrlCache._clearUrlFromCache + .calledWith(this.project_id, url) + .should.equal(true) + ) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/clsi/test/unit/js/UrlFetcherTests.js b/services/clsi/test/unit/js/UrlFetcherTests.js new file mode 100644 index 0000000000..8e79bced73 --- /dev/null +++ b/services/clsi/test/unit/js/UrlFetcherTests.js @@ -0,0 +1,226 @@ +/* eslint-disable + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const { expect } = require('chai') +const modulePath = require('path').join(__dirname, '../../../app/js/UrlFetcher') +const { EventEmitter } = require('events') + +describe('UrlFetcher', function () { + beforeEach(function () { + this.callback = sinon.stub() + this.url = 'https://www.example.com/file/here?query=string' + return (this.UrlFetcher = SandboxedModule.require(modulePath, { + requires: { + request: { + defaults: (this.defaults = sinon.stub().returns((this.request = {}))), + }, + fs: (this.fs = {}), + '@overleaf/settings': (this.settings = { + apis: { + clsiPerf: { + host: 'localhost:3043', + }, + }, + }), + }, + })) + }) + describe('pipeUrlToFileWithRetry', function () { + this.beforeEach(function () { + this.UrlFetcher.pipeUrlToFile = sinon.stub() + }) + + it('should call pipeUrlToFile', function (done) { + this.UrlFetcher.pipeUrlToFile.callsArgWith(2) + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + expect(err).to.equal(undefined) + this.UrlFetcher.pipeUrlToFile.called.should.equal(true) + done() + }) + }) + + it('should call pipeUrlToFile multiple times on error', function (done) { + const error = new Error("couldn't download file") + this.UrlFetcher.pipeUrlToFile.callsArgWith(2, error) + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + expect(err).to.equal(error) + this.UrlFetcher.pipeUrlToFile.callCount.should.equal(3) + done() + }) + }) + + it('should call pipeUrlToFile twice if only 1 error', function (done) { + this.UrlFetcher.pipeUrlToFile.onCall(0).callsArgWith(2, 'error') + this.UrlFetcher.pipeUrlToFile.onCall(1).callsArgWith(2) + this.UrlFetcher.pipeUrlToFileWithRetry(this.url, this.path, err => { + expect(err).to.equal(undefined) + this.UrlFetcher.pipeUrlToFile.callCount.should.equal(2) + done() + }) + }) + }) + + describe('pipeUrlToFile', function () { + it('should turn off the cookie jar in request', function () { + return this.defaults.calledWith({ jar: false }).should.equal(true) + }) + + describe('rewrite url domain if filestoreDomainOveride is set', function () { + beforeEach(function () { + this.path = '/path/to/file/on/disk' + this.request.get = sinon + .stub() + .returns((this.urlStream = new EventEmitter())) + this.urlStream.pipe = sinon.stub() + this.urlStream.pause = sinon.stub() + this.urlStream.resume = sinon.stub() + this.fs.createWriteStream = sinon + .stub() + .returns((this.fileStream = new EventEmitter())) + return (this.fs.unlink = (file, callback) => callback()) + }) + + it('should use the normal domain when override not set', function (done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.request.get.args[0][0].url.should.equal(this.url) + return done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + return this.fileStream.emit('finish') + }) + + it('should not use override clsiPerf domain when filestoreDomainOveride is set', function (done) { + this.settings.filestoreDomainOveride = '192.11.11.11' + const url = 'http://localhost:3043/file/here?query=string' + this.UrlFetcher.pipeUrlToFile(url, this.path, () => { + this.request.get.args[0][0].url.should.equal(url) + done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + this.fileStream.emit('finish') + }) + + return it('should use override domain when filestoreDomainOveride is set', function (done) { + this.settings.filestoreDomainOveride = '192.11.11.11' + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.request.get.args[0][0].url.should.equal( + '192.11.11.11/file/here?query=string' + ) + return done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + return this.fileStream.emit('finish') + }) + }) + + return describe('pipeUrlToFile', function () { + beforeEach(function (done) { + this.path = '/path/to/file/on/disk' + this.request.get = sinon + .stub() + .returns((this.urlStream = new EventEmitter())) + this.urlStream.pipe = sinon.stub() + this.urlStream.pause = sinon.stub() + this.urlStream.resume = sinon.stub() + this.fs.createWriteStream = sinon + .stub() + .returns((this.fileStream = new EventEmitter())) + this.fs.unlink = (file, callback) => callback() + return done() + }) + + describe('successfully', function () { + beforeEach(function (done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, () => { + this.callback() + return done() + }) + this.res = { statusCode: 200 } + this.urlStream.emit('response', this.res) + this.urlStream.emit('end') + return this.fileStream.emit('finish') + }) + + it('should request the URL', function () { + return this.request.get + .calledWith(sinon.match({ url: this.url })) + .should.equal(true) + }) + + it('should open the file for writing', function () { + return this.fs.createWriteStream + .calledWith(this.path) + .should.equal(true) + }) + + it('should pipe the URL to the file', function () { + return this.urlStream.pipe + .calledWith(this.fileStream) + .should.equal(true) + }) + + return it('should call the callback', function () { + return this.callback.called.should.equal(true) + }) + }) + + describe('with non success status code', function () { + beforeEach(function (done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { + this.callback(err) + return done() + }) + this.res = { statusCode: 404 } + this.urlStream.emit('response', this.res) + return this.urlStream.emit('end') + }) + + it('should call the callback with an error', function () { + this.callback.calledWith(sinon.match(Error)).should.equal(true) + + const message = this.callback.args[0][0].message + expect(message).to.include( + 'URL returned non-success status code: 404' + ) + }) + }) + + return describe('with error', function () { + beforeEach(function (done) { + this.UrlFetcher.pipeUrlToFile(this.url, this.path, err => { + this.callback(err) + return done() + }) + return this.urlStream.emit( + 'error', + (this.error = new Error('something went wrong')) + ) + }) + + it('should call the callback with the error', function () { + return this.callback.calledWith(this.error).should.equal(true) + }) + + return it('should only call the callback once, even if end is called', function () { + this.urlStream.emit('end') + return this.callback.calledOnce.should.equal(true) + }) + }) + }) + }) +}) diff --git a/services/clsi/test/unit/js/pdfjsTests.js b/services/clsi/test/unit/js/pdfjsTests.js new file mode 100644 index 0000000000..92f10279d2 --- /dev/null +++ b/services/clsi/test/unit/js/pdfjsTests.js @@ -0,0 +1,96 @@ +const fs = require('fs') +const Path = require('path') +const { expect } = require('chai') +const { parseXrefTable } = require('../../../app/lib/pdfjs/parseXrefTable') +const PATH_EXAMPLES = 'test/acceptance/fixtures/examples/' +const PATH_SNAPSHOTS = 'test/unit/js/snapshots/pdfjs/' +const EXAMPLES = fs.readdirSync(PATH_EXAMPLES) + +function snapshotPath(example) { + return Path.join(PATH_SNAPSHOTS, example, 'XrefTable.json') +} + +function pdfPath(example) { + return Path.join(PATH_EXAMPLES, example, 'output.pdf') +} + +async function loadContext(example) { + const size = (await fs.promises.stat(pdfPath(example))).size + + let blob + try { + blob = await fs.promises.readFile(snapshotPath(example)) + } catch (e) { + if (e.code !== 'ENOENT') { + throw e + } + } + const snapshot = blob ? JSON.parse(blob) : null + return { + size, + snapshot, + } +} + +async function backFillSnapshot(example, size) { + const table = await parseXrefTable(pdfPath(example), size, () => {}) + await fs.promises.mkdir(Path.dirname(snapshotPath(example)), { + recursive: true, + }) + await fs.promises.writeFile( + snapshotPath(example), + JSON.stringify(table, null, 2) + ) + return table +} + +describe('pdfjs', function () { + describe('when the pdf is an empty file', function () { + it('should yield no entries', async function () { + const path = 'does/not/matter.pdf' + const table = await parseXrefTable(path, 0) + expect(table).to.deep.equal([]) + }) + }) + + describe('when the operation times out', function () { + it('should bail out', async function () { + const path = pdfPath(EXAMPLES[0]) + const { size } = await loadContext(EXAMPLES[0]) + const err = new Error() + let table + try { + table = await parseXrefTable(path, size, () => { + throw err + }) + } catch (e) { + expect(e).to.equal(err) + return + } + expect(table).to.not.exist + }) + }) + + for (const example of EXAMPLES) { + describe(example, function () { + let size, snapshot + before('load snapshot', async function () { + const ctx = await loadContext(example) + size = ctx.size + snapshot = ctx.snapshot + }) + + before('back fill new snapshot', async function () { + if (snapshot === null) { + console.error('back filling snapshot for', example) + snapshot = await backFillSnapshot(example, size) + } + }) + + it('should produce the expected xRef table', async function () { + const table = await parseXrefTable(pdfPath(example), size, () => {}) + expect(table).to.deep.equal(snapshot) + }) + }) + } +}) diff --git a/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71 b/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71 new file mode 100644 index 0000000000..bb9f891be7 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71 @@ -0,0 +1,7 @@ +obj +<< /Type /ObjStm /Length 447 /Filter /FlateDecode /N 5 /First 32 >> +stream +xRQk0~߯ǍK'ɒ $vkGIJ[(y8*$IRd>I"H@9@J!` V/gg f>BZxJ9ۮ]-B'ZNg k%i\!f4m݁49ĶCY]\@! 4c Uf=JgOg>zz>A)C9WwKqPÜ#/48VX/ Tp -%2"*B;X2,9Gz;EΥJj/c +n%ᵦf3]!=y ,s]@e+COW.Ckڒ c_ťX v>N2u7=} #HVr9?kv6G^z׬.v=Uyjǡpz2 +endstream +endobj diff --git a/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/d7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd b/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/d7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd new file mode 100644 index 0000000000..d503090d3a Binary files /dev/null and b/services/clsi/test/unit/js/snapshots/minimalCompile/chunks/d7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd differ diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/asymptote/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/asymptote/XrefTable.json new file mode 100644 index 0000000000..be9f29164d --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/asymptote/XrefTable.json @@ -0,0 +1,356 @@ +[ + { + "offset": 0, + "gen": 0, + "free": true + }, + { + "offset": 123086, + "gen": 0, + "uncompressed": true + }, + { + "offset": 123405, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 216, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1084, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1244, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4001, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4155, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4297, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4932, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5307, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5495, + "gen": 0, + "uncompressed": true + }, + { + "offset": 30246, + "gen": 0, + "uncompressed": true + }, + { + "offset": 31466, + "gen": 0, + "uncompressed": true + }, + { + "offset": 38398, + "gen": 0, + "uncompressed": true + }, + { + "offset": 39039, + "gen": 0, + "uncompressed": true + }, + { + "offset": 40158, + "gen": 0, + "uncompressed": true + }, + { + "offset": 40897, + "gen": 0, + "uncompressed": true + }, + { + "offset": 65550, + "gen": 0, + "uncompressed": true + }, + { + "offset": 74691, + "gen": 0, + "uncompressed": true + }, + { + "offset": 81693, + "gen": 0, + "uncompressed": true + }, + { + "offset": 97169, + "gen": 0, + "uncompressed": true + }, + { + "offset": 104103, + "gen": 0, + "uncompressed": true + }, + { + "offset": 111180, + "gen": 0, + "uncompressed": true + }, + { + "offset": 118555, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 6, + "gen": 10 + }, + { + "offset": 6, + "gen": 11 + }, + { + "offset": 6, + "gen": 12 + }, + { + "offset": 6, + "gen": 13 + }, + { + "offset": 6, + "gen": 14 + }, + { + "offset": 6, + "gen": 15 + }, + { + "offset": 6, + "gen": 16 + }, + { + "offset": 6, + "gen": 17 + }, + { + "offset": 6, + "gen": 18 + }, + { + "offset": 6, + "gen": 19 + }, + { + "offset": 6, + "gen": 20 + }, + { + "offset": 6, + "gen": 21 + }, + { + "offset": 6, + "gen": 22 + }, + { + "offset": 6, + "gen": 23 + }, + { + "offset": 6, + "gen": 24 + }, + { + "offset": 6, + "gen": 25 + }, + { + "offset": 6, + "gen": 26 + }, + { + "offset": 6, + "gen": 27 + }, + { + "offset": 6, + "gen": 28 + }, + { + "offset": 6, + "gen": 29 + }, + { + "offset": 6, + "gen": 30 + }, + { + "offset": 6, + "gen": 31 + }, + { + "offset": 6, + "gen": 32 + }, + { + "offset": 6, + "gen": 33 + }, + { + "offset": 6, + "gen": 34 + }, + { + "offset": 6, + "gen": 35 + }, + { + "offset": 6, + "gen": 36 + }, + { + "offset": 6, + "gen": 37 + }, + { + "offset": 6, + "gen": 38 + }, + { + "offset": 6, + "gen": 39 + }, + { + "offset": 6, + "gen": 40 + }, + { + "offset": 6, + "gen": 41 + }, + { + "offset": 6, + "gen": 42 + }, + { + "offset": 6, + "gen": 43 + }, + { + "offset": 6, + "gen": 44 + }, + { + "offset": 6, + "gen": 45 + }, + { + "offset": 6, + "gen": 46 + }, + { + "offset": 6, + "gen": 47 + }, + { + "offset": 6, + "gen": 48 + }, + { + "offset": 6, + "gen": 49 + }, + { + "offset": 6, + "gen": 50 + }, + { + "offset": 6, + "gen": 51 + }, + { + "offset": 6, + "gen": 52 + }, + { + "offset": 6, + "gen": 53 + }, + { + "offset": 6, + "gen": 54 + }, + { + "offset": 6, + "gen": 55 + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/biber_bibliography/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/biber_bibliography/XrefTable.json new file mode 100644 index 0000000000..94c0fddf42 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/biber_bibliography/XrefTable.json @@ -0,0 +1,128 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 9, + "gen": 1 + }, + { + "offset": 9, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 14 + }, + { + "offset": 9, + "gen": 12 + }, + { + "offset": 9, + "gen": 15 + }, + { + "offset": 9, + "gen": 13 + }, + { + "offset": 9, + "gen": 16 + }, + { + "offset": 57274, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 17 + }, + { + "offset": 9, + "gen": 2 + }, + { + "offset": 9, + "gen": 3 + }, + { + "offset": 9, + "gen": 4 + }, + { + "offset": 9, + "gen": 5 + }, + { + "offset": 9, + "gen": 6 + }, + { + "offset": 522, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 7 + }, + { + "offset": 8788, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 8 + }, + { + "offset": 17289, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 9 + }, + { + "offset": 32619, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 10 + }, + { + "offset": 44596, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 11 + }, + { + "offset": 9, + "gen": 18 + }, + { + "offset": 57027, + "gen": 0, + "uncompressed": true + }, + { + "offset": 58655, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/epstopdf/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/epstopdf/XrefTable.json new file mode 100644 index 0000000000..fca6e5447d --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/epstopdf/XrefTable.json @@ -0,0 +1,125 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 208, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 11 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 14 + }, + { + "offset": 29580, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 15 + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 18310, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 6, + "gen": 10 + }, + { + "offset": 18554, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18945, + "gen": 0, + "uncompressed": true + }, + { + "offset": 19674, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 12 + }, + { + "offset": 22318, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 13 + }, + { + "offset": 6, + "gen": 16 + }, + { + "offset": 29321, + "gen": 0, + "uncompressed": true + }, + { + "offset": 30697, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/feynmf/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/feynmf/XrefTable.json new file mode 100644 index 0000000000..0da87fc092 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/feynmf/XrefTable.json @@ -0,0 +1,115 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 8, + "gen": 1 + }, + { + "offset": 8, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 5 + }, + { + "offset": 8, + "gen": 12 + }, + { + "offset": 8, + "gen": 14 + }, + { + "offset": 8, + "gen": 13 + }, + { + "offset": 25628, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 15 + }, + { + "offset": 8, + "gen": 2 + }, + { + "offset": 8, + "gen": 3 + }, + { + "offset": 8, + "gen": 4 + }, + { + "offset": 250, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 6 + }, + { + "offset": 8, + "gen": 7 + }, + { + "offset": 8, + "gen": 8 + }, + { + "offset": 3854, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 9 + }, + { + "offset": 11227, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 10 + }, + { + "offset": 18230, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 11 + }, + { + "offset": 8, + "gen": 16 + }, + { + "offset": 25381, + "gen": 0, + "uncompressed": true + }, + { + "offset": 26423, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/feynmp/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/feynmp/XrefTable.json new file mode 100644 index 0000000000..34048b037d --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/feynmp/XrefTable.json @@ -0,0 +1,102 @@ +[ + { + "offset": 0, + "gen": 65535, + "free": true + }, + { + "offset": 1094, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5512, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1035, + "gen": 0, + "uncompressed": true + }, + { + "offset": 875, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 856, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1159, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1593, + "gen": 0, + "uncompressed": true + }, + { + "offset": 3149, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1436, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2412, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1282, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1768, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1200, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1230, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2006, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2625, + "gen": 0, + "uncompressed": true + }, + { + "offset": 3381, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4069, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/fontawesome/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/fontawesome/XrefTable.json new file mode 100644 index 0000000000..1951e17f7f --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/fontawesome/XrefTable.json @@ -0,0 +1,93 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 7, + "gen": 1 + }, + { + "offset": 7, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 11 + }, + { + "offset": 7, + "gen": 10 + }, + { + "offset": 7, + "gen": 9 + }, + { + "offset": 29476, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 12 + }, + { + "offset": 7, + "gen": 7 + }, + { + "offset": 7, + "gen": 2 + }, + { + "offset": 7, + "gen": 8 + }, + { + "offset": 7, + "gen": 3 + }, + { + "offset": 7, + "gen": 4 + }, + { + "offset": 255, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 5 + }, + { + "offset": 17910, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 6 + }, + { + "offset": 7, + "gen": 13 + }, + { + "offset": 29228, + "gen": 0, + "uncompressed": true + }, + { + "offset": 30448, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/fontawesome_xelatex/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/fontawesome_xelatex/XrefTable.json new file mode 100644 index 0000000000..e6315a28e7 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/fontawesome_xelatex/XrefTable.json @@ -0,0 +1,126 @@ +[ + { + "offset": 0, + "gen": 0, + "free": true + }, + { + "offset": 6338, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 216, + "gen": 0, + "uncompressed": true + }, + { + "offset": 707, + "gen": 0, + "uncompressed": true + }, + { + "offset": 757, + "gen": 0, + "uncompressed": true + }, + { + "offset": 888, + "gen": 0, + "uncompressed": true + }, + { + "offset": 991, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1257, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1678, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2050, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4246, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4339, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5382, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5475, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5513, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0 + }, + { + "offset": 15, + "gen": 1 + }, + { + "offset": 15, + "gen": 2 + }, + { + "offset": 15, + "gen": 3 + }, + { + "offset": 15, + "gen": 4 + }, + { + "offset": 15, + "gen": 5 + }, + { + "offset": 15, + "gen": 6 + }, + { + "offset": 15, + "gen": 7 + }, + { + "offset": 15, + "gen": 8 + }, + { + "offset": 15, + "gen": 9 + }, + { + "offset": 15, + "gen": 10 + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/glossaries/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/glossaries/XrefTable.json new file mode 100644 index 0000000000..51f7dca8bb --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/glossaries/XrefTable.json @@ -0,0 +1,94 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 7, + "gen": 1 + }, + { + "offset": 7, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 10 + }, + { + "offset": 7, + "gen": 9 + }, + { + "offset": 7, + "gen": 8 + }, + { + "offset": 33085, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 11 + }, + { + "offset": 7, + "gen": 2 + }, + { + "offset": 7, + "gen": 3 + }, + { + "offset": 7, + "gen": 4 + }, + { + "offset": 445, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 5 + }, + { + "offset": 10048, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 6 + }, + { + "offset": 18151, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 7 + }, + { + "offset": 7, + "gen": 12 + }, + { + "offset": 32838, + "gen": 0, + "uncompressed": true + }, + { + "offset": 34157, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/gnuplot/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/gnuplot/XrefTable.json new file mode 100644 index 0000000000..5148059666 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/gnuplot/XrefTable.json @@ -0,0 +1,89 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 4, + "gen": 0 + }, + { + "offset": 4, + "gen": 1 + }, + { + "offset": 4, + "gen": 2 + }, + { + "offset": 22047, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4, + "gen": 4 + }, + { + "offset": 4, + "gen": 3 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4, + "gen": 10 + }, + { + "offset": 4, + "gen": 9 + }, + { + "offset": 4, + "gen": 11 + }, + { + "offset": 4, + "gen": 5 + }, + { + "offset": 4, + "gen": 6 + }, + { + "offset": 6451, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4, + "gen": 7 + }, + { + "offset": 14825, + "gen": 0, + "uncompressed": true + }, + { + "offset": 4, + "gen": 8 + }, + { + "offset": 4, + "gen": 12 + }, + { + "offset": 21800, + "gen": 0, + "uncompressed": true + }, + { + "offset": 22696, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/hebrew/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/hebrew/XrefTable.json new file mode 100644 index 0000000000..d3e8221a2e --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/hebrew/XrefTable.json @@ -0,0 +1,81 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 22744, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 364, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 12163, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 10 + }, + { + "offset": 22496, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23856, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/knitr/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/knitr/XrefTable.json new file mode 100644 index 0000000000..d57e124754 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/knitr/XrefTable.json @@ -0,0 +1,145 @@ +[ + { + "offset": 0, + "gen": 0, + "free": true + }, + { + "offset": 43543, + "gen": 0, + "uncompressed": true + }, + { + "offset": 43792, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 216, + "gen": 0, + "uncompressed": true + }, + { + "offset": 734, + "gen": 0, + "uncompressed": true + }, + { + "offset": 784, + "gen": 0, + "uncompressed": true + }, + { + "offset": 912, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1020, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1544, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5791, + "gen": 0, + "uncompressed": true + }, + { + "offset": 12911, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23655, + "gen": 0, + "uncompressed": true + }, + { + "offset": 30651, + "gen": 0, + "uncompressed": true + }, + { + "offset": 42597, + "gen": 0, + "uncompressed": true + }, + { + "offset": 14, + "gen": 0 + }, + { + "offset": 14, + "gen": 1 + }, + { + "offset": 14, + "gen": 2 + }, + { + "offset": 14, + "gen": 3 + }, + { + "offset": 14, + "gen": 4 + }, + { + "offset": 14, + "gen": 5 + }, + { + "offset": 14, + "gen": 6 + }, + { + "offset": 14, + "gen": 7 + }, + { + "offset": 14, + "gen": 8 + }, + { + "offset": 14, + "gen": 9 + }, + { + "offset": 14, + "gen": 10 + }, + { + "offset": 14, + "gen": 11 + }, + { + "offset": 14, + "gen": 12 + }, + { + "offset": 14, + "gen": 13 + }, + { + "offset": 14, + "gen": 14 + }, + { + "offset": 14, + "gen": 15 + }, + { + "offset": 14, + "gen": 16 + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/knitr_utf8/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/knitr_utf8/XrefTable.json new file mode 100644 index 0000000000..14982555fc --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/knitr_utf8/XrefTable.json @@ -0,0 +1,179 @@ +[ + { + "offset": 0, + "gen": 0, + "free": true + }, + { + "offset": 75291, + "gen": 0, + "uncompressed": true + }, + { + "offset": 75540, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 216, + "gen": 0, + "uncompressed": true + }, + { + "offset": 790, + "gen": 0, + "uncompressed": true + }, + { + "offset": 840, + "gen": 0, + "uncompressed": true + }, + { + "offset": 975, + "gen": 0, + "uncompressed": true + }, + { + "offset": 1083, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2127, + "gen": 0, + "uncompressed": true + }, + { + "offset": 13797, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23679, + "gen": 0, + "uncompressed": true + }, + { + "offset": 31863, + "gen": 0, + "uncompressed": true + }, + { + "offset": 36111, + "gen": 0, + "uncompressed": true + }, + { + "offset": 50346, + "gen": 0, + "uncompressed": true + }, + { + "offset": 61562, + "gen": 0, + "uncompressed": true + }, + { + "offset": 73508, + "gen": 0, + "uncompressed": true + }, + { + "offset": 16, + "gen": 0 + }, + { + "offset": 16, + "gen": 1 + }, + { + "offset": 16, + "gen": 2 + }, + { + "offset": 16, + "gen": 3 + }, + { + "offset": 16, + "gen": 4 + }, + { + "offset": 16, + "gen": 5 + }, + { + "offset": 16, + "gen": 6 + }, + { + "offset": 16, + "gen": 7 + }, + { + "offset": 16, + "gen": 8 + }, + { + "offset": 16, + "gen": 9 + }, + { + "offset": 16, + "gen": 10 + }, + { + "offset": 16, + "gen": 11 + }, + { + "offset": 16, + "gen": 12 + }, + { + "offset": 16, + "gen": 13 + }, + { + "offset": 16, + "gen": 14 + }, + { + "offset": 16, + "gen": 15 + }, + { + "offset": 16, + "gen": 16 + }, + { + "offset": 16, + "gen": 17 + }, + { + "offset": 16, + "gen": 18 + }, + { + "offset": 16, + "gen": 19 + }, + { + "offset": 16, + "gen": 20 + }, + { + "offset": 16, + "gen": 21 + }, + { + "offset": 16, + "gen": 22 + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/latex_compiler/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/latex_compiler/XrefTable.json new file mode 100644 index 0000000000..91e596e54f --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/latex_compiler/XrefTable.json @@ -0,0 +1,132 @@ +[ + { + "offset": 0, + "gen": 65535, + "free": true + }, + { + "offset": 17349, + "gen": 0, + "uncompressed": true + }, + { + "offset": 25448, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17290, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17130, + "gen": 0, + "uncompressed": true + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17109, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17414, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17548, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18912, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18381, + "gen": 0, + "uncompressed": true + }, + { + "offset": 22406, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17897, + "gen": 0, + "uncompressed": true + }, + { + "offset": 21796, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18758, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23361, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17455, + "gen": 0, + "uncompressed": true + }, + { + "offset": 17485, + "gen": 0, + "uncompressed": true + }, + { + "offset": 19218, + "gen": 0, + "uncompressed": true + }, + { + "offset": 22021, + "gen": 0, + "uncompressed": true + }, + { + "offset": 22638, + "gen": 0, + "uncompressed": true + }, + { + "offset": 23599, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18051, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18142, + "gen": 0, + "uncompressed": true + }, + { + "offset": 18657, + "gen": 0, + "uncompressed": true + }, + { + "offset": 24005, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/lualatex_compiler/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/lualatex_compiler/XrefTable.json new file mode 100644 index 0000000000..a403bb4f6a --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/lualatex_compiler/XrefTable.json @@ -0,0 +1,74 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 2320, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 202, + "gen": 0, + "uncompressed": true + }, + { + "offset": 298, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 1642, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 2120, + "gen": 0, + "uncompressed": true + }, + { + "offset": 2892, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/makeindex-custom-style/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/makeindex-custom-style/XrefTable.json new file mode 100644 index 0000000000..b2b9dcef35 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/makeindex-custom-style/XrefTable.json @@ -0,0 +1,107 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 5, + "gen": 1 + }, + { + "offset": 5, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 12 + }, + { + "offset": 30470, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 13 + }, + { + "offset": 5, + "gen": 3 + }, + { + "offset": 5, + "gen": 2 + }, + { + "offset": 366, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 11 + }, + { + "offset": 5, + "gen": 10 + }, + { + "offset": 5, + "gen": 4 + }, + { + "offset": 5, + "gen": 5 + }, + { + "offset": 5, + "gen": 6 + }, + { + "offset": 638, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 7 + }, + { + "offset": 7838, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 8 + }, + { + "offset": 15674, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 9 + }, + { + "offset": 5, + "gen": 14 + }, + { + "offset": 30223, + "gen": 0, + "uncompressed": true + }, + { + "offset": 31457, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/makeindex/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/makeindex/XrefTable.json new file mode 100644 index 0000000000..9a58bed4cb --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/makeindex/XrefTable.json @@ -0,0 +1,90 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 5, + "gen": 1 + }, + { + "offset": 5, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 9 + }, + { + "offset": 23352, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 10 + }, + { + "offset": 5, + "gen": 3 + }, + { + "offset": 5, + "gen": 2 + }, + { + "offset": 366, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 8 + }, + { + "offset": 5, + "gen": 4 + }, + { + "offset": 5, + "gen": 5 + }, + { + "offset": 589, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 6 + }, + { + "offset": 8425, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 7 + }, + { + "offset": 5, + "gen": 11 + }, + { + "offset": 23105, + "gen": 0, + "uncompressed": true + }, + { + "offset": 24245, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/minted/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/minted/XrefTable.json new file mode 100644 index 0000000000..f0d2f2a195 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/minted/XrefTable.json @@ -0,0 +1,77 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 19462, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 340, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 7343, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 19215, + "gen": 0, + "uncompressed": true + }, + { + "offset": 20089, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/multibib_bibliography/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/multibib_bibliography/XrefTable.json new file mode 100644 index 0000000000..ffd44df50b --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/multibib_bibliography/XrefTable.json @@ -0,0 +1,133 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 6, + "gen": 1 + }, + { + "offset": 6, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 14 + }, + { + "offset": 6, + "gen": 15 + }, + { + "offset": 40591, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 17 + }, + { + "offset": 6, + "gen": 3 + }, + { + "offset": 6, + "gen": 2 + }, + { + "offset": 290, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 16 + }, + { + "offset": 6, + "gen": 5 + }, + { + "offset": 6, + "gen": 4 + }, + { + "offset": 595, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 7 + }, + { + "offset": 6, + "gen": 6 + }, + { + "offset": 844, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 8 + }, + { + "offset": 6, + "gen": 9 + }, + { + "offset": 6, + "gen": 10 + }, + { + "offset": 1107, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 11 + }, + { + "offset": 11816, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 12 + }, + { + "offset": 28180, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6, + "gen": 13 + }, + { + "offset": 6, + "gen": 18 + }, + { + "offset": 40344, + "gen": 0, + "uncompressed": true + }, + { + "offset": 41851, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/nomenclature/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/nomenclature/XrefTable.json new file mode 100644 index 0000000000..51286af2bc --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/nomenclature/XrefTable.json @@ -0,0 +1,94 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 7, + "gen": 1 + }, + { + "offset": 7, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 8 + }, + { + "offset": 7, + "gen": 9 + }, + { + "offset": 7, + "gen": 10 + }, + { + "offset": 32363, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 11 + }, + { + "offset": 7, + "gen": 2 + }, + { + "offset": 7, + "gen": 3 + }, + { + "offset": 7, + "gen": 4 + }, + { + "offset": 565, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 5 + }, + { + "offset": 10031, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 6 + }, + { + "offset": 18203, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 7 + }, + { + "offset": 7, + "gen": 12 + }, + { + "offset": 32116, + "gen": 0, + "uncompressed": true + }, + { + "offset": 33492, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/references_in_include/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/references_in_include/XrefTable.json new file mode 100644 index 0000000000..b0c6731240 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/references_in_include/XrefTable.json @@ -0,0 +1,90 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 5, + "gen": 1 + }, + { + "offset": 5, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 9 + }, + { + "offset": 15855, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 10 + }, + { + "offset": 5, + "gen": 3 + }, + { + "offset": 5, + "gen": 2 + }, + { + "offset": 167, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 8 + }, + { + "offset": 5, + "gen": 4 + }, + { + "offset": 5, + "gen": 5 + }, + { + "offset": 341, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 6 + }, + { + "offset": 7911, + "gen": 0, + "uncompressed": true + }, + { + "offset": 5, + "gen": 7 + }, + { + "offset": 5, + "gen": 11 + }, + { + "offset": 15608, + "gen": 0, + "uncompressed": true + }, + { + "offset": 16597, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/simple_bibliography/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/simple_bibliography/XrefTable.json new file mode 100644 index 0000000000..d633b7ba6f --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/simple_bibliography/XrefTable.json @@ -0,0 +1,94 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 7, + "gen": 1 + }, + { + "offset": 7, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 9 + }, + { + "offset": 7, + "gen": 8 + }, + { + "offset": 7, + "gen": 10 + }, + { + "offset": 35573, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 11 + }, + { + "offset": 7, + "gen": 2 + }, + { + "offset": 7, + "gen": 3 + }, + { + "offset": 7, + "gen": 4 + }, + { + "offset": 373, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 5 + }, + { + "offset": 8639, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 6 + }, + { + "offset": 23349, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7, + "gen": 7 + }, + { + "offset": 7, + "gen": 12 + }, + { + "offset": 35326, + "gen": 0, + "uncompressed": true + }, + { + "offset": 36668, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/subdirectories/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/subdirectories/XrefTable.json new file mode 100644 index 0000000000..06ed0fdf7d --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/subdirectories/XrefTable.json @@ -0,0 +1,108 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 548, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 1 + }, + { + "offset": 9, + "gen": 2 + }, + { + "offset": 9, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 10 + }, + { + "offset": 9, + "gen": 9 + }, + { + "offset": 9, + "gen": 11 + }, + { + "offset": 46398, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 12 + }, + { + "offset": 8100, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 3 + }, + { + "offset": 9, + "gen": 4 + }, + { + "offset": 9, + "gen": 5 + }, + { + "offset": 9693, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 6 + }, + { + "offset": 17959, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 7 + }, + { + "offset": 34174, + "gen": 0, + "uncompressed": true + }, + { + "offset": 9, + "gen": 8 + }, + { + "offset": 9, + "gen": 13 + }, + { + "offset": 46151, + "gen": 0, + "uncompressed": true + }, + { + "offset": 47562, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/tikz_feynman/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/tikz_feynman/XrefTable.json new file mode 100644 index 0000000000..afeaa84536 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/tikz_feynman/XrefTable.json @@ -0,0 +1,145 @@ +[ + { + "offset": 0, + "gen": 255, + "free": true + }, + { + "offset": 8, + "gen": 8 + }, + { + "offset": 8, + "gen": 9 + }, + { + "offset": 8, + "gen": 10 + }, + { + "offset": 8, + "gen": 1 + }, + { + "offset": 8, + "gen": 0 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 20 + }, + { + "offset": 33577, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 3 + }, + { + "offset": 8, + "gen": 2 + }, + { + "offset": 1151, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 17 + }, + { + "offset": 8, + "gen": 19 + }, + { + "offset": 8, + "gen": 18 + }, + { + "offset": 8, + "gen": 5 + }, + { + "offset": 8, + "gen": 4 + }, + { + "offset": 2721, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 7 + }, + { + "offset": 8, + "gen": 6 + }, + { + "offset": 5757, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 11 + }, + { + "offset": 8, + "gen": 12 + }, + { + "offset": 8, + "gen": 13 + }, + { + "offset": 9558, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 14 + }, + { + "offset": 18967, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 15 + }, + { + "offset": 26388, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 16 + }, + { + "offset": 8, + "gen": 21 + }, + { + "offset": 33354, + "gen": 0, + "uncompressed": true + }, + { + "offset": 34451, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file diff --git a/services/clsi/test/unit/js/snapshots/pdfjs/xelatex_compiler/XrefTable.json b/services/clsi/test/unit/js/snapshots/pdfjs/xelatex_compiler/XrefTable.json new file mode 100644 index 0000000000..31080439b5 --- /dev/null +++ b/services/clsi/test/unit/js/snapshots/pdfjs/xelatex_compiler/XrefTable.json @@ -0,0 +1,73 @@ +[ + { + "offset": 0, + "gen": 65535, + "free": true + }, + { + "offset": 8, + "gen": 4 + }, + { + "offset": 8, + "gen": 3 + }, + { + "offset": 8, + "gen": 1 + }, + { + "offset": 8, + "gen": 6 + }, + { + "offset": 8, + "gen": 8 + }, + { + "offset": 15, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 0 + }, + { + "offset": 6857, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 2 + }, + { + "offset": 265, + "gen": 0, + "uncompressed": true + }, + { + "offset": 8, + "gen": 7 + }, + { + "offset": 8, + "gen": 5 + }, + { + "offset": 701, + "gen": 0, + "uncompressed": true + }, + { + "offset": 6750, + "gen": 0, + "uncompressed": true + }, + { + "offset": 7655, + "gen": 0, + "uncompressed": true + } +] \ No newline at end of file