-
-Eclipse Public License - Version 1.0
-
-
-
-
-
-
-
Eclipse Public License - v 1.0
-
-
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
-PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
-DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
-AGREEMENT.
-
-
1. DEFINITIONS
-
-
"Contribution" means:
-
-
a) in the case of the initial Contributor, the initial
-code and documentation distributed under this Agreement, and
-
b) in the case of each subsequent Contributor:
-
i) changes to the Program, and
-
ii) additions to the Program;
-
where such changes and/or additions to the Program
-originate from and are distributed by that particular Contributor. A
-Contribution 'originates' from a Contributor if it was added to the
-Program by such Contributor itself or anyone acting on such
-Contributor's behalf. Contributions do not include additions to the
-Program which: (i) are separate modules of software distributed in
-conjunction with the Program under their own license agreement, and (ii)
-are not derivative works of the Program.
-
-
"Contributor" means any person or entity that distributes
-the Program.
-
-
"Licensed Patents" mean patent claims licensable by a
-Contributor which are necessarily infringed by the use or sale of its
-Contribution alone or when combined with the Program.
-
-
"Program" means the Contributions distributed in accordance
-with this Agreement.
-
-
"Recipient" means anyone who receives the Program under
-this Agreement, including all Contributors.
-
-
2. GRANT OF RIGHTS
-
-
a) Subject to the terms of this Agreement, each
-Contributor hereby grants Recipient a non-exclusive, worldwide,
-royalty-free copyright license to reproduce, prepare derivative works
-of, publicly display, publicly perform, distribute and sublicense the
-Contribution of such Contributor, if any, and such derivative works, in
-source code and object code form.
-
-
b) Subject to the terms of this Agreement, each
-Contributor hereby grants Recipient a non-exclusive, worldwide,
-royalty-free patent license under Licensed Patents to make, use, sell,
-offer to sell, import and otherwise transfer the Contribution of such
-Contributor, if any, in source code and object code form. This patent
-license shall apply to the combination of the Contribution and the
-Program if, at the time the Contribution is added by the Contributor,
-such addition of the Contribution causes such combination to be covered
-by the Licensed Patents. The patent license shall not apply to any other
-combinations which include the Contribution. No hardware per se is
-licensed hereunder.
-
-
c) Recipient understands that although each Contributor
-grants the licenses to its Contributions set forth herein, no assurances
-are provided by any Contributor that the Program does not infringe the
-patent or other intellectual property rights of any other entity. Each
-Contributor disclaims any liability to Recipient for claims brought by
-any other entity based on infringement of intellectual property rights
-or otherwise. As a condition to exercising the rights and licenses
-granted hereunder, each Recipient hereby assumes sole responsibility to
-secure any other intellectual property rights needed, if any. For
-example, if a third party patent license is required to allow Recipient
-to distribute the Program, it is Recipient's responsibility to acquire
-that license before distributing the Program.
-
-
d) Each Contributor represents that to its knowledge it
-has sufficient copyright rights in its Contribution, if any, to grant
-the copyright license set forth in this Agreement.
-
-
3. REQUIREMENTS
-
-
A Contributor may choose to distribute the Program in object code
-form under its own license agreement, provided that:
-
-
a) it complies with the terms and conditions of this
-Agreement; and
-
-
b) its license agreement:
-
-
i) effectively disclaims on behalf of all Contributors
-all warranties and conditions, express and implied, including warranties
-or conditions of title and non-infringement, and implied warranties or
-conditions of merchantability and fitness for a particular purpose;
-
-
ii) effectively excludes on behalf of all Contributors
-all liability for damages, including direct, indirect, special,
-incidental and consequential damages, such as lost profits;
-
-
iii) states that any provisions which differ from this
-Agreement are offered by that Contributor alone and not by any other
-party; and
-
-
iv) states that source code for the Program is available
-from such Contributor, and informs licensees how to obtain it in a
-reasonable manner on or through a medium customarily used for software
-exchange.
-
-
When the Program is made available in source code form:
-
-
a) it must be made available under this Agreement; and
-
-
b) a copy of this Agreement must be included with each
-copy of the Program.
-
-
Contributors may not remove or alter any copyright notices contained
-within the Program.
-
-
Each Contributor must identify itself as the originator of its
-Contribution, if any, in a manner that reasonably allows subsequent
-Recipients to identify the originator of the Contribution.
-
-
4. COMMERCIAL DISTRIBUTION
-
-
Commercial distributors of software may accept certain
-responsibilities with respect to end users, business partners and the
-like. While this license is intended to facilitate the commercial use of
-the Program, the Contributor who includes the Program in a commercial
-product offering should do so in a manner which does not create
-potential liability for other Contributors. Therefore, if a Contributor
-includes the Program in a commercial product offering, such Contributor
-("Commercial Contributor") hereby agrees to defend and
-indemnify every other Contributor ("Indemnified Contributor")
-against any losses, damages and costs (collectively "Losses")
-arising from claims, lawsuits and other legal actions brought by a third
-party against the Indemnified Contributor to the extent caused by the
-acts or omissions of such Commercial Contributor in connection with its
-distribution of the Program in a commercial product offering. The
-obligations in this section do not apply to any claims or Losses
-relating to any actual or alleged intellectual property infringement. In
-order to qualify, an Indemnified Contributor must: a) promptly notify
-the Commercial Contributor in writing of such claim, and b) allow the
-Commercial Contributor to control, and cooperate with the Commercial
-Contributor in, the defense and any related settlement negotiations. The
-Indemnified Contributor may participate in any such claim at its own
-expense.
-
-
For example, a Contributor might include the Program in a commercial
-product offering, Product X. That Contributor is then a Commercial
-Contributor. If that Commercial Contributor then makes performance
-claims, or offers warranties related to Product X, those performance
-claims and warranties are such Commercial Contributor's responsibility
-alone. Under this section, the Commercial Contributor would have to
-defend claims against the other Contributors related to those
-performance claims and warranties, and if a court requires any other
-Contributor to pay any damages as a result, the Commercial Contributor
-must pay those damages.
-
-
5. NO WARRANTY
-
-
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
-PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
-OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
-ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
-OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
-responsible for determining the appropriateness of using and
-distributing the Program and assumes all risks associated with its
-exercise of rights under this Agreement , including but not limited to
-the risks and costs of program errors, compliance with applicable laws,
-damage to or loss of data, programs or equipment, and unavailability or
-interruption of operations.
-
-
6. DISCLAIMER OF LIABILITY
-
-
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
-NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
-WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
-DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
-HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
-
-
7. GENERAL
-
-
If any provision of this Agreement is invalid or unenforceable under
-applicable law, it shall not affect the validity or enforceability of
-the remainder of the terms of this Agreement, and without further action
-by the parties hereto, such provision shall be reformed to the minimum
-extent necessary to make such provision valid and enforceable.
-
-
If Recipient institutes patent litigation against any entity
-(including a cross-claim or counterclaim in a lawsuit) alleging that the
-Program itself (excluding combinations of the Program with other
-software or hardware) infringes such Recipient's patent(s), then such
-Recipient's rights granted under Section 2(b) shall terminate as of the
-date such litigation is filed.
-
-
All Recipient's rights under this Agreement shall terminate if it
-fails to comply with any of the material terms or conditions of this
-Agreement and does not cure such failure in a reasonable period of time
-after becoming aware of such noncompliance. If all Recipient's rights
-under this Agreement terminate, Recipient agrees to cease use and
-distribution of the Program as soon as reasonably practicable. However,
-Recipient's obligations under this Agreement and any licenses granted by
-Recipient relating to the Program shall continue and survive.
-
-
Everyone is permitted to copy and distribute copies of this
-Agreement, but in order to avoid inconsistency the Agreement is
-copyrighted and may only be modified in the following manner. The
-Agreement Steward reserves the right to publish new versions (including
-revisions) of this Agreement from time to time. No one other than the
-Agreement Steward has the right to modify this Agreement. The Eclipse
-Foundation is the initial Agreement Steward. The Eclipse Foundation may
-assign the responsibility to serve as the Agreement Steward to a
-suitable separate entity. Each new version of the Agreement will be
-given a distinguishing version number. The Program (including
-Contributions) may always be distributed subject to the version of the
-Agreement under which it was received. In addition, after a new version
-of the Agreement is published, Contributor may elect to distribute the
-Program (including its Contributions) under the new version. Except as
-expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
-rights or licenses to the intellectual property of any Contributor under
-this Agreement, whether expressly, by implication, estoppel or
-otherwise. All rights in the Program not expressly granted under this
-Agreement are reserved.
-
-
This Agreement is governed by the laws of the State of New York and
-the intellectual property laws of the United States of America. No party
-to this Agreement will bring a legal action under this Agreement more
-than one year after the cause of action arose. Each party waives its
-rights to a jury trial in any resulting litigation.
-
-
-
-
+<<<<<<< HEAD
+
+
+
+
+
+
+Eclipse Public License - Version 1.0
+
+
+
+
+
+
+
Eclipse Public License - v 1.0
+
+
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
+DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
+AGREEMENT.
+
+
1. DEFINITIONS
+
+
"Contribution" means:
+
+
a) in the case of the initial Contributor, the initial
+code and documentation distributed under this Agreement, and
+
b) in the case of each subsequent Contributor:
+
i) changes to the Program, and
+
ii) additions to the Program;
+
where such changes and/or additions to the Program
+originate from and are distributed by that particular Contributor. A
+Contribution 'originates' from a Contributor if it was added to the
+Program by such Contributor itself or anyone acting on such
+Contributor's behalf. Contributions do not include additions to the
+Program which: (i) are separate modules of software distributed in
+conjunction with the Program under their own license agreement, and (ii)
+are not derivative works of the Program.
+
+
"Contributor" means any person or entity that distributes
+the Program.
+
+
"Licensed Patents" mean patent claims licensable by a
+Contributor which are necessarily infringed by the use or sale of its
+Contribution alone or when combined with the Program.
+
+
"Program" means the Contributions distributed in accordance
+with this Agreement.
+
+
"Recipient" means anyone who receives the Program under
+this Agreement, including all Contributors.
+
+
2. GRANT OF RIGHTS
+
+
a) Subject to the terms of this Agreement, each
+Contributor hereby grants Recipient a non-exclusive, worldwide,
+royalty-free copyright license to reproduce, prepare derivative works
+of, publicly display, publicly perform, distribute and sublicense the
+Contribution of such Contributor, if any, and such derivative works, in
+source code and object code form.
+
+
b) Subject to the terms of this Agreement, each
+Contributor hereby grants Recipient a non-exclusive, worldwide,
+royalty-free patent license under Licensed Patents to make, use, sell,
+offer to sell, import and otherwise transfer the Contribution of such
+Contributor, if any, in source code and object code form. This patent
+license shall apply to the combination of the Contribution and the
+Program if, at the time the Contribution is added by the Contributor,
+such addition of the Contribution causes such combination to be covered
+by the Licensed Patents. The patent license shall not apply to any other
+combinations which include the Contribution. No hardware per se is
+licensed hereunder.
+
+
c) Recipient understands that although each Contributor
+grants the licenses to its Contributions set forth herein, no assurances
+are provided by any Contributor that the Program does not infringe the
+patent or other intellectual property rights of any other entity. Each
+Contributor disclaims any liability to Recipient for claims brought by
+any other entity based on infringement of intellectual property rights
+or otherwise. As a condition to exercising the rights and licenses
+granted hereunder, each Recipient hereby assumes sole responsibility to
+secure any other intellectual property rights needed, if any. For
+example, if a third party patent license is required to allow Recipient
+to distribute the Program, it is Recipient's responsibility to acquire
+that license before distributing the Program.
+
+
d) Each Contributor represents that to its knowledge it
+has sufficient copyright rights in its Contribution, if any, to grant
+the copyright license set forth in this Agreement.
+
+
3. REQUIREMENTS
+
+
A Contributor may choose to distribute the Program in object code
+form under its own license agreement, provided that:
+
+
a) it complies with the terms and conditions of this
+Agreement; and
+
+
b) its license agreement:
+
+
i) effectively disclaims on behalf of all Contributors
+all warranties and conditions, express and implied, including warranties
+or conditions of title and non-infringement, and implied warranties or
+conditions of merchantability and fitness for a particular purpose;
+
+
ii) effectively excludes on behalf of all Contributors
+all liability for damages, including direct, indirect, special,
+incidental and consequential damages, such as lost profits;
+
+
iii) states that any provisions which differ from this
+Agreement are offered by that Contributor alone and not by any other
+party; and
+
+
iv) states that source code for the Program is available
+from such Contributor, and informs licensees how to obtain it in a
+reasonable manner on or through a medium customarily used for software
+exchange.
+
+
When the Program is made available in source code form:
+
+
a) it must be made available under this Agreement; and
+
+
b) a copy of this Agreement must be included with each
+copy of the Program.
+
+
Contributors may not remove or alter any copyright notices contained
+within the Program.
+
+
Each Contributor must identify itself as the originator of its
+Contribution, if any, in a manner that reasonably allows subsequent
+Recipients to identify the originator of the Contribution.
+
+
4. COMMERCIAL DISTRIBUTION
+
+
Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use of
+the Program, the Contributor who includes the Program in a commercial
+product offering should do so in a manner which does not create
+potential liability for other Contributors. Therefore, if a Contributor
+includes the Program in a commercial product offering, such Contributor
+("Commercial Contributor") hereby agrees to defend and
+indemnify every other Contributor ("Indemnified Contributor")
+against any losses, damages and costs (collectively "Losses")
+arising from claims, lawsuits and other legal actions brought by a third
+party against the Indemnified Contributor to the extent caused by the
+acts or omissions of such Commercial Contributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In
+order to qualify, an Indemnified Contributor must: a) promptly notify
+the Commercial Contributor in writing of such claim, and b) allow the
+Commercial Contributor to control, and cooperate with the Commercial
+Contributor in, the defense and any related settlement negotiations. The
+Indemnified Contributor may participate in any such claim at its own
+expense.
+
+
For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those
+performance claims and warranties, and if a court requires any other
+Contributor to pay any damages as a result, the Commercial Contributor
+must pay those damages.
+
+
5. NO WARRANTY
+
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
+ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement , including but not limited to
+the risks and costs of program errors, compliance with applicable laws,
+damage to or loss of data, programs or equipment, and unavailability or
+interruption of operations.
+
+
6. DISCLAIMER OF LIABILITY
+
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+
7. GENERAL
+
+
If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further action
+by the parties hereto, such provision shall be reformed to the minimum
+extent necessary to make such provision valid and enforceable.
+
+
If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other
+software or hardware) infringes such Recipient's patent(s), then such
+Recipient's rights granted under Section 2(b) shall terminate as of the
+date such litigation is filed.
+
+
All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of time
+after becoming aware of such noncompliance. If all Recipient's rights
+under this Agreement terminate, Recipient agrees to cease use and
+distribution of the Program as soon as reasonably practicable. However,
+Recipient's obligations under this Agreement and any licenses granted by
+Recipient relating to the Program shall continue and survive.
+
+
Everyone is permitted to copy and distribute copies of this
+Agreement, but in order to avoid inconsistency the Agreement is
+copyrighted and may only be modified in the following manner. The
+Agreement Steward reserves the right to publish new versions (including
+revisions) of this Agreement from time to time. No one other than the
+Agreement Steward has the right to modify this Agreement. The Eclipse
+Foundation is the initial Agreement Steward. The Eclipse Foundation may
+assign the responsibility to serve as the Agreement Steward to a
+suitable separate entity. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new version
+of the Agreement is published, Contributor may elect to distribute the
+Program (including its Contributions) under the new version. Except as
+expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
+rights or licenses to the intellectual property of any Contributor under
+this Agreement, whether expressly, by implication, estoppel or
+otherwise. All rights in the Program not expressly granted under this
+Agreement are reserved.
+
+
This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No party
+to this Agreement will bring a legal action under this Agreement more
+than one year after the cause of action arose. Each party waives its
+rights to a jury trial in any resulting litigation.
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
+DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
+AGREEMENT.
+
+
1. DEFINITIONS
+
+
"Contribution" means:
+
+
a) in the case of the initial Contributor, the initial
+code and documentation distributed under this Agreement, and
+
b) in the case of each subsequent Contributor:
+
i) changes to the Program, and
+
ii) additions to the Program;
+
where such changes and/or additions to the Program
+originate from and are distributed by that particular Contributor. A
+Contribution 'originates' from a Contributor if it was added to the
+Program by such Contributor itself or anyone acting on such
+Contributor's behalf. Contributions do not include additions to the
+Program which: (i) are separate modules of software distributed in
+conjunction with the Program under their own license agreement, and (ii)
+are not derivative works of the Program.
+
+
"Contributor" means any person or entity that distributes
+the Program.
+
+
"Licensed Patents" mean patent claims licensable by a
+Contributor which are necessarily infringed by the use or sale of its
+Contribution alone or when combined with the Program.
+
+
"Program" means the Contributions distributed in accordance
+with this Agreement.
+
+
"Recipient" means anyone who receives the Program under
+this Agreement, including all Contributors.
+
+
2. GRANT OF RIGHTS
+
+
a) Subject to the terms of this Agreement, each
+Contributor hereby grants Recipient a non-exclusive, worldwide,
+royalty-free copyright license to reproduce, prepare derivative works
+of, publicly display, publicly perform, distribute and sublicense the
+Contribution of such Contributor, if any, and such derivative works, in
+source code and object code form.
+
+
b) Subject to the terms of this Agreement, each
+Contributor hereby grants Recipient a non-exclusive, worldwide,
+royalty-free patent license under Licensed Patents to make, use, sell,
+offer to sell, import and otherwise transfer the Contribution of such
+Contributor, if any, in source code and object code form. This patent
+license shall apply to the combination of the Contribution and the
+Program if, at the time the Contribution is added by the Contributor,
+such addition of the Contribution causes such combination to be covered
+by the Licensed Patents. The patent license shall not apply to any other
+combinations which include the Contribution. No hardware per se is
+licensed hereunder.
+
+
c) Recipient understands that although each Contributor
+grants the licenses to its Contributions set forth herein, no assurances
+are provided by any Contributor that the Program does not infringe the
+patent or other intellectual property rights of any other entity. Each
+Contributor disclaims any liability to Recipient for claims brought by
+any other entity based on infringement of intellectual property rights
+or otherwise. As a condition to exercising the rights and licenses
+granted hereunder, each Recipient hereby assumes sole responsibility to
+secure any other intellectual property rights needed, if any. For
+example, if a third party patent license is required to allow Recipient
+to distribute the Program, it is Recipient's responsibility to acquire
+that license before distributing the Program.
+
+
d) Each Contributor represents that to its knowledge it
+has sufficient copyright rights in its Contribution, if any, to grant
+the copyright license set forth in this Agreement.
+
+
3. REQUIREMENTS
+
+
A Contributor may choose to distribute the Program in object code
+form under its own license agreement, provided that:
+
+
a) it complies with the terms and conditions of this
+Agreement; and
+
+
b) its license agreement:
+
+
i) effectively disclaims on behalf of all Contributors
+all warranties and conditions, express and implied, including warranties
+or conditions of title and non-infringement, and implied warranties or
+conditions of merchantability and fitness for a particular purpose;
+
+
ii) effectively excludes on behalf of all Contributors
+all liability for damages, including direct, indirect, special,
+incidental and consequential damages, such as lost profits;
+
+
iii) states that any provisions which differ from this
+Agreement are offered by that Contributor alone and not by any other
+party; and
+
+
iv) states that source code for the Program is available
+from such Contributor, and informs licensees how to obtain it in a
+reasonable manner on or through a medium customarily used for software
+exchange.
+
+
When the Program is made available in source code form:
+
+
a) it must be made available under this Agreement; and
+
+
b) a copy of this Agreement must be included with each
+copy of the Program.
+
+
Contributors may not remove or alter any copyright notices contained
+within the Program.
+
+
Each Contributor must identify itself as the originator of its
+Contribution, if any, in a manner that reasonably allows subsequent
+Recipients to identify the originator of the Contribution.
+
+
4. COMMERCIAL DISTRIBUTION
+
+
Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use of
+the Program, the Contributor who includes the Program in a commercial
+product offering should do so in a manner which does not create
+potential liability for other Contributors. Therefore, if a Contributor
+includes the Program in a commercial product offering, such Contributor
+("Commercial Contributor") hereby agrees to defend and
+indemnify every other Contributor ("Indemnified Contributor")
+against any losses, damages and costs (collectively "Losses")
+arising from claims, lawsuits and other legal actions brought by a third
+party against the Indemnified Contributor to the extent caused by the
+acts or omissions of such Commercial Contributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In
+order to qualify, an Indemnified Contributor must: a) promptly notify
+the Commercial Contributor in writing of such claim, and b) allow the
+Commercial Contributor to control, and cooperate with the Commercial
+Contributor in, the defense and any related settlement negotiations. The
+Indemnified Contributor may participate in any such claim at its own
+expense.
+
+
For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those
+performance claims and warranties, and if a court requires any other
+Contributor to pay any damages as a result, the Commercial Contributor
+must pay those damages.
+
+
5. NO WARRANTY
+
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
+ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement , including but not limited to
+the risks and costs of program errors, compliance with applicable laws,
+damage to or loss of data, programs or equipment, and unavailability or
+interruption of operations.
+
+
6. DISCLAIMER OF LIABILITY
+
+
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+
7. GENERAL
+
+
If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further action
+by the parties hereto, such provision shall be reformed to the minimum
+extent necessary to make such provision valid and enforceable.
+
+
If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other
+software or hardware) infringes such Recipient's patent(s), then such
+Recipient's rights granted under Section 2(b) shall terminate as of the
+date such litigation is filed.
+
+
All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of time
+after becoming aware of such noncompliance. If all Recipient's rights
+under this Agreement terminate, Recipient agrees to cease use and
+distribution of the Program as soon as reasonably practicable. However,
+Recipient's obligations under this Agreement and any licenses granted by
+Recipient relating to the Program shall continue and survive.
+
+
Everyone is permitted to copy and distribute copies of this
+Agreement, but in order to avoid inconsistency the Agreement is
+copyrighted and may only be modified in the following manner. The
+Agreement Steward reserves the right to publish new versions (including
+revisions) of this Agreement from time to time. No one other than the
+Agreement Steward has the right to modify this Agreement. The Eclipse
+Foundation is the initial Agreement Steward. The Eclipse Foundation may
+assign the responsibility to serve as the Agreement Steward to a
+suitable separate entity. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new version
+of the Agreement is published, Contributor may elect to distribute the
+Program (including its Contributions) under the new version. Except as
+expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
+rights or licenses to the intellectual property of any Contributor under
+this Agreement, whether expressly, by implication, estoppel or
+otherwise. All rights in the Program not expressly granted under this
+Agreement are reserved.
+
+
This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No party
+to this Agreement will bring a legal action under this Agreement more
+than one year after the cause of action arose. Each party waives its
+rights to a jury trial in any resulting litigation.
+
+
+
+
+>>>>>>> 27b7fc4fbc72f7956c2b0056f7a92949a2ed91b0
diff --git a/app/controllers/drawings.coffee b/app/controllers/drawings.coffee
index 52ec793..8fc33e2 100644
--- a/app/controllers/drawings.coffee
+++ b/app/controllers/drawings.coffee
@@ -7,6 +7,9 @@ CanvasRenderer = require('views/drawings/canvas_renderer')
Toolbox = require('controllers/toolbox')
Selection = require('controllers/selection')
Commander = require ('models/commands/commander')
+Palette = require('models/palette')
+NodeShape = require('models/node_shape')
+
class Index extends Spine.Controller
@@ -17,12 +20,13 @@ class Index extends Spine.Controller
constructor: ->
super
@active @render
-
- render: ->
+ Drawing.bind("fetch",@render)
+
+ render: =>
context =
drawings: Drawing.all()
palette_specs: PaletteSpecification.all()
- @log(context)
+ # @log(context)
@html require('views/drawings/index')(context)
create: (event) =>
diff --git a/app/controllers/link_tool.coffee b/app/controllers/link_tool.coffee
index 7e323eb..74375d9 100644
--- a/app/controllers/link_tool.coffee
+++ b/app/controllers/link_tool.coffee
@@ -5,11 +5,12 @@ CreateLink = require('models/commands/create_link')
class LinkTool extends Tool
parameters: {'shape' : null}
drafting: false
+ timer:0
onMouseMove: (event) ->
- if @parameters.shape
- @clearSelection()
- @select(@hitTester.nodeAt(event.point))
+ # if @parameters.shape
+ # @clearSelection()
+ # @select(@hitTester.nodeAt(event.point))
onMouseDown: (event) ->
if @parameters.shape and @hitTester.nodeAt(event.point)
@@ -17,9 +18,12 @@ class LinkTool extends Tool
@draftLink = new DraftLink(event.point)
onMouseDrag: (event) ->
- if @drafting
- @draftLink.extendTo(event.point)
- @changeSelectionTo(@hitTester.nodeAt(event.point)) if @hitTester.nodeAt(event.point)
+ current = new Date().getTime()
+ if(current - @timer > 90)
+ @timer = current
+ if @drafting
+ @draftLink.extendTo(event.point)
+ @changeSelectionTo(@hitTester.nodeAt(event.point)) if @hitTester.nodeAt(event.point)
onMouseUp: (event) ->
if @drafting
diff --git a/app/controllers/node_tool.coffee b/app/controllers/node_tool.coffee
index 048c292..adf8006 100644
--- a/app/controllers/node_tool.coffee
+++ b/app/controllers/node_tool.coffee
@@ -3,6 +3,7 @@ CreateNode = require('models/commands/create_node')
class NodeTool extends Tool
parameters: {'shape' : null}
+ timer:0
onMouseDown: (event) ->
if @parameters.shape
@@ -11,8 +12,10 @@ class NodeTool extends Tool
@clearSelection()
onMouseMove: (event) ->
- if @parameters.shape
- @clearSelection()
- @select(@hitTester.nodeAt(event.point))
+
+ # if @parameters.shape
+ # @clearSelection()
+ # @select(@hitTester.nodeAt(event.point))
+ #τεστινγ ψομιτ
module.exports = NodeTool
\ No newline at end of file
diff --git a/app/controllers/palettes.coffee b/app/controllers/palettes.coffee
index 5e5c5b1..cf03d09 100644
--- a/app/controllers/palettes.coffee
+++ b/app/controllers/palettes.coffee
@@ -21,7 +21,7 @@ class Define extends Spine.Controller
eugenia: new EugeniaNotation
@notation = 'json'
@active @change
-
+
currentNotation: =>
@notations[@notation]
@@ -125,7 +125,7 @@ class Create extends Define
node: =>
new NodeShape(name: "TheNode", elements: [{}], palette_id: @palette.id)
-
+
link: =>
new LinkShape(name: "TheLink", palette_id: @palette.id)
diff --git a/app/controllers/select_tool.coffee b/app/controllers/select_tool.coffee
index 9f9a571..3de2a54 100644
--- a/app/controllers/select_tool.coffee
+++ b/app/controllers/select_tool.coffee
@@ -4,6 +4,7 @@ MoveNode = require('models/commands/move_node')
class SelectTool extends Tool
parameters: {}
+ timer:0
onMouseDown: (event) ->
@clearSelection()
@@ -12,9 +13,12 @@ class SelectTool extends Tool
@start = event.point
onMouseDrag: (event) ->
- for item in @selection() when item instanceof Node
- @run(new MoveNode(item, event.point.subtract(@current)), undoable: false)
- @current = event.point
+ current = new Date().getTime();
+ if(current-@timer >100)
+ @timer=current
+ for item in @selection() when item instanceof Node
+ @run(new MoveNode(item, event.point.subtract(@current)), undoable: false)
+ @current = event.point
onMouseUp: (event) ->
for item in @selection() when item instanceof Node
diff --git a/app/controllers/toolbox.coffee b/app/controllers/toolbox.coffee
index d2d2cae..1a98b2f 100644
--- a/app/controllers/toolbox.coffee
+++ b/app/controllers/toolbox.coffee
@@ -18,6 +18,8 @@ class Toolbox extends Spine.Controller
select: new SelectTool(commander: @commander, drawing: @item)
link: new LinkTool(commander: @commander, drawing: @item)
@switchTo("select")
+ NodeShape.bind("change", @render)
+ LinkShape.bind("change", @render)
render: =>
@html require('views/drawings/toolbox')(@item) if @item
diff --git a/app/lib/fetch_models.coffee b/app/lib/fetch_models.coffee
index a313b82..9706234 100644
--- a/app/lib/fetch_models.coffee
+++ b/app/lib/fetch_models.coffee
@@ -1,7 +1,12 @@
+Spine = require('spine')
model_names = ["palette_specification", "palette", "link_shape",
"node_shape", "drawing", "node", "link"]
-
+
+
for model_name in model_names
model = require("models/#{model_name}")
- model.extend(Spine.Model.Local) unless model_name is "palette_specification"
- model.fetch()
\ No newline at end of file
+ model.extend(Spine.Model.Realtime) unless model_name is "palette_specification"
+ if(model_name is "palette_specification")
+ model.fetch()
+
+# Add if statement. If model does not extend Realtime, call model.fetch .
\ No newline at end of file
diff --git a/app/lib/setup.coffee b/app/lib/setup.coffee
index 1b741bc..947bc09 100644
--- a/app/lib/setup.coffee
+++ b/app/lib/setup.coffee
@@ -9,3 +9,5 @@ require('spine/lib/ajax')
require('spine/lib/manager')
require('spine/lib/route')
require('spine/lib/relation')
+require('models/realtime')
+
diff --git a/app/models/drawing.coffee b/app/models/drawing.coffee
index a54c351..e86a882 100644
--- a/app/models/drawing.coffee
+++ b/app/models/drawing.coffee
@@ -22,7 +22,7 @@ class Drawing extends Spine.Model
@selection = []
@save()
@trigger("selectionChanged")
-
+
validate: ->
"Name is required" unless @name
@@ -40,8 +40,9 @@ class Drawing extends Spine.Model
updateCanvas: ->
if paper.project
paper.project.activeLayer.selected = false
- for element in @selection
- element.select(paper.project.activeLayer)
+ for element in @selection?
+ # if(element.select)
+ element.select(paper.project.activeLayer)
module.exports = Drawing
\ No newline at end of file
diff --git a/app/models/palette.coffee b/app/models/palette.coffee
index 45de506..d89907c 100644
--- a/app/models/palette.coffee
+++ b/app/models/palette.coffee
@@ -9,7 +9,7 @@ class Palette extends Spine.Model
constructor: ->
super
@bind("destroy", @destroyChildren)
-
+
destroyChildren: ->
ns.destroy() for ns in @nodeShapes().all()
ls.destroy() for ls in @linkShapes().all()
diff --git a/app/models/realtime.coffee b/app/models/realtime.coffee
new file mode 100644
index 0000000..d3456e7
--- /dev/null
+++ b/app/models/realtime.coffee
@@ -0,0 +1,60 @@
+rtc = require("models/rtc")
+
+Spine.Model.Realtime =
+ extended:->
+ @bind('update',rtc.updateJSON)
+ @bind('create',rtc.createJSON)
+ @bind('destroy',rtc.deleteJSON)
+
+ Spine.Model.bind('Model:fileLoad',(map) =>
+ @loadModelMap(map))
+
+ Spine.Model.bind("Model:fileUpdate",(event) =>
+ @modelMapUpdate(event))
+
+ @idCounter = Math.random()
+ # random initial value of counter helps to avoid ID conflicts
+
+ # called when a realtime model is loaded. Finds existing instances of this Model type and loads them
+ loadModelMap: (map) ->
+ console.log("load map "+@className)
+ records = map.get(@className)
+ JSONstring = ""
+ if records
+ JSONstring = records.values().toString()
+ JSONrecord = "[" + JSONstring + "]"
+ @refresh(JSONrecord or [], clear: true)
+ @fetch()
+ # called each time an edit is applied remotely on collaborative map
+ modelMapUpdate: (event) ->
+ # we dont want events to fire again on changes.
+ @unbind('update',rtc.updateJSON)
+ @unbind('destroy',rtc.deleteJSON)
+ @unbind('create',rtc.createJSON)
+ console.log('modelMapUpdate')
+ eventSource = event.property
+ changeOnThisModel = eventSource.lastIndexOf(@className, 0) == 0
+ if(changeOnThisModel)
+ recordOld = @fromJSON(event.oldValue)
+ recordNew = @fromJSON(event.newValue)
+ if(recordNew)
+ if(@exists(recordNew.id))
+ recordLocal = @find(recordNew.id)
+ localSel = recordLocal.attributes().selection
+ recordNew.updateAttribute('selection', localSel) if localSel?
+ recordLocal.updateAttributes(recordNew.attributes())
+ else
+ console.log('create: '+@className)
+ console.log(recordNew.attributes())
+ @create(recordNew.attributes())
+ else
+ recordLocal = @find(recordOld.id)
+ console.log('null delete '+recordLocal?)
+ recordLocal.destroy()
+ # with model updated, add the listeners again.
+ @bind('update',rtc.updateJSON)
+ @bind('create',rtc.createJSON)
+ @bind('destroy',rtc.deleteJSON)
+
+
+module?.exports = Spine.Model.Realtime
diff --git a/app/models/rtc.js b/app/models/rtc.js
new file mode 100644
index 0000000..2e87eed
--- /dev/null
+++ b/app/models/rtc.js
@@ -0,0 +1,598 @@
+ Spine = require("spine");
+ // Script loading currently happens in index.html, but could be moved here.
+
+ // fileref=document.createElement('script');
+ // fileref.setAttribute("type","text/javascript");
+ // fileref.setAttribute("src", "//www.google.com/jsapi");
+ // fileref2=document.createElement('script');
+ // fileref2.setAttribute("type","text/javascript");
+ // fileref2.setAttribute("src", "//apis.google.com/js/api.js");
+ // fileref3=document.createElement('script');
+ // fileref3.setAttribute("type","text/javascript");
+ // fileref3.setAttribute("src", "//apis.google.com/js/api.js");
+
+var rtc = rtc || {};
+ // Pointers to interface button IDs for interacting with realtime code
+ CREATE_SELECTOR = "#createDoc";
+ OPEN_SELECTOR = "#openDoc";
+ AUTH_BUTTON = "authorizeButton";
+ SHARE_SELECTOR = "#share";
+ //appID and clientID should be copied from Google Console
+ realTimeOptions = {
+ appID: '465627783329',
+ clientId: '465627783329-5rt9n9o1m3oum4i1o9quvhe5641u182f.apps.googleusercontent.com',
+ authButtonElementId: AUTH_BUTTON,
+ initializeModel: initializeModel,
+ autoCreate: false,
+ defaultTitle: "Contacts_Map",
+ onFileLoaded: onFileLoaded
+ };
+
+ var realtimeDoc = null;
+ var userId = null;
+ var lastReqTime=0;
+
+
+ var beforeAuth = function (){
+ $(OPEN_SELECTOR).attr('disabled','true');
+ $(CREATE_SELECTOR).attr('disabled','true');
+ }
+
+ function startRealtime() {
+ beforeAuth();
+ realtimeLoader = new rtclient.RealtimeLoader(realTimeOptions);
+ realtimeLoader.start(afterAuth);
+ }
+ // add button listeners after successful authorization and make them enabled
+ var afterAuth = function(){
+ $(SHARE_SELECTOR).click(popupShare);
+ $(OPEN_SELECTOR).click(popupOpen);
+ $(CREATE_SELECTOR).click(function() {
+ var fileName = prompt("New file name:", "");
+ if(fileName){
+ realtimeLoader.createNewFileAndRedirect(fileName);
+ }
+ });
+ $(OPEN_SELECTOR).removeAttr('disabled');
+ $(CREATE_SELECTOR).removeAttr('disabled');
+ };
+ // Called when a realtime model is loaded for the first time
+ function initializeModel (model) {
+ console.log('initializeModel');
+ var map = model.createMap();
+ model.getRoot().set('modelsMap', map);
+ }
+ //Called everytime a realtime model is loaded
+ function onFileLoaded (doc) {
+ console.log('onFileLoaded');
+ var modelsMap = doc.getModel().getRoot().get('modelsMap');
+ modelsMap.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, remoteJSONAdded);
+ var keys = modelsMap.keys();
+ for(var i in keys){
+ var record = modelsMap.get(keys[i]);
+ //add change listeners to every map entry. This only affects local copy of map
+ record.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, remoteJSONUpdate);
+ }
+ realtimeDoc = doc;
+ Spine.Model.trigger('Model:fileLoad',modelsMap);
+ installCollaboratorListener();
+ updateCollaborators();
+ }
+ // Displays Google Picker, for selecting rt file to load
+ var popupOpen = function () {
+ var token = gapi.auth.getToken().access_token;
+ var view = new google.picker.View(google.picker.ViewId.DOCS);
+ view.setMimeTypes(rtclient.REALTIME_MIMETYPE); // filters files, and only displays realtime files
+ var picker = new google.picker.PickerBuilder()
+ .enableFeature(google.picker.Feature.NAV_HIDDEN)
+ .setAppId(realTimeOptions.appId)
+ .setOAuthToken(token)
+ .addView(view)
+ .addView(new google.picker.DocsUploadView())
+ .setCallback(pickerCallback)
+ .build();
+ picker.setVisible(true);
+ };
+ // redirects client to the selected realtime document
+ var pickerCallback = function (data) {
+ if (data.action == google.picker.Action.PICKED) {
+ var fileId = data.docs[0].id;
+ console.log(fileId);
+ rtclient.redirectTo(fileId, realtimeLoader.authorizer.userId);
+ }
+ };
+ // commented code is required for modular share window. Currently not working.
+ // The reason is most likely wrong configuration of Drive SDK within Google Console
+ var popupShare = function() {
+ window.open('https://drive.google.com/#recent');
+ // var fileID = [rtclient.params['fileId']];
+ // if(fileID!=""){
+ // var shareClient = new gapi.drive.share.ShareClient(realTimeOptions.appId);
+ // shareClient.setItemIds(fileID);
+ // shareClient.showSettingsDialog();
+ // }else{
+ // alert("Please open the file you wish to share first.");
+ // }
+ };
+ // deletes a collaborative map entry. Called from realtime.coffee
+ var deleteJSON = function(evt){
+ var modelsMap = realtimeDoc.getModel().getRoot().get('modelsMap');
+ var records = modelsMap.get(this.className);
+ records.delete(this.className+evt.id);
+ };
+ //create a new collaborative map entry. Called from realtime.coffee
+ var createJSON = function(evt){
+ var modelsMap = realtimeDoc.getModel().getRoot().get('modelsMap');
+ if(modelsMap.has(this.className)){
+ var records = modelsMap.get(this.className);
+ records.set(this.className+evt.id,JSON.stringify(evt));
+ }else {
+ var records = realtimeDoc.getModel().createMap();
+ modelsMap.set(this.className,records);
+ records.set(this.className+evt.id,JSON.stringify(evt));
+ records.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, remoteJSONUpdate);
+ }
+ };
+ //update a collaborative map entry. Called from realtime.coffee
+ var updateJSON = function (evt){
+ var current = new Date().getTime();
+ if(true){
+ lastReqTime = current;
+ console.log("updateJSON ");
+ var modelsMap = realtimeDoc.getModel().getRoot().get('modelsMap');
+ var records = modelsMap.get(this.className);
+ records.set(this.className+evt.id,JSON.stringify(evt));
+ };
+ };
+ // called when a new entry is added on outer map. This will happen when a new Model type
+ // is created.
+ var remoteJSONAdded = function(evt){
+ var isLocal = evt.isLocal;
+ if(!isLocal){
+ var modelsMap = realtimeDoc.getModel().getRoot().get('modelsMap');
+ var records = modelsMap.get(evt.property);
+ records.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, remoteJSONUpdate);
+ }
+ };
+ // called when a change is made in the collaborative map
+ var remoteJSONUpdate = function(evt){
+ var isLocal = evt.isLocal;
+ if(!isLocal){
+ Spine.Model.trigger('Model:fileUpdate',evt);
+ }
+ };
+
+var installCollaboratorListener = function()
+{
+ realtimeDoc.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_JOINED, updateCollaborators);
+ realtimeDoc.addEventListener(gapi.drive.realtime.EventType.COLLABORATOR_LEFT, updateCollaborators);
+};
+
+/**
+ * Draw function for the collaborator list. It will search the DOM for the specified
+ location and add it self.
+ */
+var updateCollaborators = function()
+{
+ // Creating the Collaborators container in the Dom if it's not already there.
+ if (!document.getElementById("collaborators") && document.getElementsByClassName)
+ {
+ var toolbarElement = document.getElementsByClassName("brand")[0];
+ var collaboratorsElement = document.createElement('span');
+ collaboratorsElement.id = "collaborators";
+ collaboratorsElement.style.position = "absolute";
+ collaboratorsElement.style.right = "15px";
+ toolbarElement.appendChild(collaboratorsElement);
+ }
+
+ var collaboratorsElement = document.getElementById("collaborators");
+
+ if (collaboratorsElement)
+ {
+
+ var collaboratorsList = realtimeDoc.getCollaborators();
+
+ collaboratorsElement.innerHTML = "";
+
+ if (collaboratorsList.length > 1)
+ {
+ var spanContainer = document.createElement('span');
+
+ spanContainer.style.horizontalAlign = "left";
+ collaboratorsElement.appendChild(spanContainer);
+
+ for (var i = 0; i < collaboratorsList.length; i = i + 1)
+ {
+ var collaborator = collaboratorsList[i];
+
+ if (!collaborator.isMe)
+ {
+ var img = document.createElement('img');
+ img.src = collaborator.photoUrl;
+ img.alt = collaborator.displayName;
+ img.title = collaborator.displayName;
+ img.style.backgroundColor = collaborator.color;
+ img.style.marginLeft = "5px";
+ img.style.height = "25px";
+ img.style.width = "25px";
+ img.style.paddingBottom = "5px";
+ collaboratorsElement.appendChild(img);
+ }
+ else
+ {
+ userId = collaborator.userId;
+ }
+ }
+ }
+ else if (collaboratorsList.length == 1)
+ {
+ var collaborator = collaboratorsList[0];
+
+ if (collaborator.isMe)
+ {
+ userId = collaborator.userId;
+ }
+ }
+ }
+};
+
+
+window.onload = startRealtime;
+// make update/delete/create JSON function available to external files. Used for realtime.coffee
+exports.updateJSON = updateJSON;
+exports.deleteJSON = deleteJSON;
+exports.createJSON = createJSON;
+
+/**
+ *__________________________________________________________________________________________________
+ *_______________________________________ Realtime client utils library____________________________
+ *_________________________________________________________________________________________________
+ */
+"use strict";
+
+
+
+
+var rtclient = rtclient || {}
+
+// Scopes are required when requesting application rights, when a user accesses realtime Eugenia for
+// the first time
+rtclient.INSTALL_SCOPE = 'https://www.googleapis.com/auth/drive.install';
+
+
+/**
+ * OAuth 2.0 scope for opening and creating files.
+ * @const
+ */
+rtclient.FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file';
+
+
+/**
+ * OAuth 2.0 scope for accessing the user's ID.
+ * @const
+ */
+rtclient.OPENID_SCOPE = 'openid';
+
+
+/**
+ * MIME type for newly created Realtime files. Used also for filtering available Google drive files
+ * @const
+ */
+rtclient.REALTIME_MIMETYPE = 'application/vnd.google-apps.drive-sdk.'+realTimeOptions.appID;
+
+
+/**
+ * Parses the query parameters to this page and returns them as an object.
+ * @function
+ */
+rtclient.getParams = function() {
+ var params = {};
+ var queryString = window.location.search;
+ if (queryString) {
+ // split up the query string and store in an object
+ var paramStrs = queryString.slice(1).split("&");
+ for (var i = 0; i < paramStrs.length; i++) {
+ var paramStr = paramStrs[i].split("=");
+ params[paramStr[0]] = unescape(paramStr[1]);
+ }
+ }
+ return params;
+}
+
+
+/**
+ * Instance of the query parameters.
+ */
+rtclient.params = rtclient.getParams();
+
+
+/**
+ * Fetches an option from options or a default value, logging an error if
+ * neither is available.
+ * @param options {Object} containing options.
+ * @param key {string} option key.
+ * @param defaultValue {Object} default option value (optional).
+ */
+rtclient.getOption = function(options, key, defaultValue) {
+ var value = options[key] == undefined ? defaultValue : options[key];
+ if (value == undefined) {
+ console.error(key + ' should be present in the options.');
+ }
+ // console.log(value);
+ return value;
+}
+
+
+/**
+ * Creates a new Authorizer from the options.
+ * @constructor
+ * @param options {Object} for authorizer. Two keys are required as mandatory, these are:
+ *
+ * 1. "clientId", the Client ID from the APIs Console
+ */
+rtclient.Authorizer = function(options) {
+ this.clientId = rtclient.getOption(options, 'clientId');
+ // Get the user ID if it's available in the state query parameter.
+ this.userId = rtclient.params['userId'];
+ this.authButton = document.getElementById(rtclient.getOption(options, 'authButtonElementId'));
+}
+
+
+/**
+ * Start the authorization process.
+ * @param onAuthComplete {Function} to call once authorization has completed.
+ */
+rtclient.Authorizer.prototype.start = function(onAuthComplete) {
+ var _this = this;
+ gapi.load('auth:client,drive-realtime,drive-share', function() {
+ _this.authorize(onAuthComplete);
+ });
+}
+
+
+/**
+ * Reauthorize the client with no callback (used for authorization failure).
+ * @param onAuthComplete {Function} to call once authorization has completed.
+ */
+rtclient.Authorizer.prototype.authorize = function(onAuthComplete) {
+ var clientId = this.clientId;
+ var userId = this.userId;
+ var _this = this;
+
+ var handleAuthResult = function(authResult) {
+ if (authResult && !authResult.error) {
+ _this.authButton.disabled = true;
+ _this.fetchUserId(onAuthComplete);
+ } else {
+ _this.authButton.disabled = false;
+ _this.authButton.onclick = authorizeWithPopup;
+ }
+ };
+
+ var authorizeWithPopup = function() {
+ gapi.auth.authorize({
+ client_id: clientId,
+ scope: [
+ rtclient.INSTALL_SCOPE,
+ rtclient.FILE_SCOPE,
+ rtclient.OPENID_SCOPE
+ ],
+ user_id: userId,
+ immediate: false
+ }, handleAuthResult);
+ // console.log(clientId);
+ };
+
+ // Try with no popups first.
+ gapi.auth.authorize({
+ client_id: clientId,
+ scope: [
+ rtclient.INSTALL_SCOPE,
+ rtclient.FILE_SCOPE,
+ rtclient.OPENID_SCOPE
+ ],
+ user_id: userId,
+ immediate: true
+ }, handleAuthResult);
+};
+
+
+/**
+ * Fetch the user ID using the UserInfo API and save it locally.
+ * @param callback {Function} the callback to call after user ID has been
+ * fetched.
+ */
+rtclient.Authorizer.prototype.fetchUserId = function(callback) {
+ var _this = this;
+ gapi.client.load('oauth2', 'v2', function() {
+ gapi.client.oauth2.userinfo.get().execute(function(resp) {
+ if (resp.id) {
+ _this.userId = resp.id;
+ }
+ if (callback) {
+ callback();
+ }
+ });
+ });
+};
+
+/**
+ * Creates a new Realtime file.
+ * @param title {string} title of the newly created file.
+ * @param callback {Function} the callback to call after creation.
+ */
+rtclient.createRealtimeFile = function(title, callback) {
+ gapi.client.load('drive', 'v2', function() {
+ gapi.client.drive.files.insert({
+ 'resource': {
+ mimeType: rtclient.REALTIME_MIMETYPE,
+ title: title
+ }
+ }).execute(callback);
+ });
+};
+
+
+/**
+ * Fetches the metadata for a Realtime file.
+ * @param fileId {string} the file to load metadata for.
+ * @param callback {Function} the callback to be called on completion, with signature:
+ *
+ * function onGetFileMetadata(file) {}
+ *
+ * where the file parameter is a Google Drive API file resource instance.
+ */
+rtclient.getFileMetadata = function(fileId, callback) {
+ gapi.client.load('drive', 'v2', function() {
+ gapi.client.drive.files.get({
+ 'fileId' : id
+ }).execute(callback);
+ });
+};
+
+
+/**
+ * Parses the state parameter passed from the Drive user interface after Open
+ * With operations.
+ * @param stateParam {Object} the state query parameter as an object or null if
+ * parsing failed.
+ */
+rtclient.parseState = function(stateParam) {
+ try {
+ var stateObj = JSON.parse(stateParam);
+ return stateObj;
+ } catch(e) {
+ return null;
+ }
+};
+
+
+/**
+ * Redirects the browser back to the current page with an appropriate file ID.
+ * @param fileId {string} the file ID to redirect to.
+ * @param userId {string} the user ID to redirect to.
+ */
+rtclient.redirectTo = function(fileId, userId) {
+ var params = [];
+ if (fileId) {
+ params.push('fileId=' + fileId);
+ }
+ if (userId) {
+ params.push('userId=' + userId);
+ }
+ // Naive URL construction.
+ window.location.href = params.length == 0 ? '/' : ('?' + params.join('&'));
+};
+
+
+/**
+ * Handles authorizing, parsing query parameters, loading and creating Realtime
+ * documents.
+ * @constructor
+ * @param options {Object} options for loader. Four keys are required as mandatory, these are:
+ *
+ * 1. "clientId", the Client ID from the APIs Console
+ * 2. "initializeModel", the callback to call when the file is loaded.
+ * 3. "onFileLoaded", the callback to call when the model is first created.
+ *
+ * and one key is optional:
+ *
+ * 1. "defaultTitle", the title of newly created Realtime files.
+ */
+rtclient.RealtimeLoader = function(options) {
+ // Initialize configuration variables.
+ this.onFileLoaded = rtclient.getOption(options, 'onFileLoaded');
+ this.initializeModel = rtclient.getOption(options, 'initializeModel');
+ this.registerTypes = rtclient.getOption(options, 'registerTypes', function(){});
+ this.autoCreate = rtclient.getOption(options, 'autoCreate', false); // This tells us if need to we automatically create a file after auth.
+ this.defaultTitle = rtclient.getOption(options, 'defaultTitle', 'New Realtime File');
+ this.authorizer = new rtclient.Authorizer(options);
+};
+
+
+/**
+ * Starts the loader by authorizing.
+ * @param callback {Function} afterAuth callback called after authorization.
+ */
+rtclient.RealtimeLoader.prototype.start = function(afterAuth) {
+ // Bind to local context to make them suitable for callbacks.
+ var _this = this;
+ this.authorizer.start(function() {
+ if (_this.registerTypes) {
+ _this.registerTypes();
+ }
+ if (afterAuth) {
+ afterAuth();
+ }
+ _this.load();
+ });
+};
+
+
+/**
+ * Loads or creates a Realtime file depending on the fileId and state query
+ * parameters.
+ */
+rtclient.RealtimeLoader.prototype.load = function() {
+ var fileId = rtclient.params['fileId'];
+ var userId = this.authorizer.userId;
+ var state = rtclient.params['state'];
+
+ // Creating the error callback.
+ var authorizer = this.authorizer;
+ var handleErrors = function(e) {
+ if(e.type == gapi.drive.realtime.ErrorType.TOKEN_REFRESH_REQUIRED) {
+ authorizer.authorize();
+ } else if(e.type == gapi.drive.realtime.ErrorType.CLIENT_ERROR) {
+ alert("An Error happened: " + e.message);
+ window.location.href= "/";
+ } else if(e.type == gapi.drive.realtime.ErrorType.NOT_FOUND) {
+ alert("The file was not found. It does not exist or you do not have read access to the file.");
+ window.location.href= "/";
+ }
+ };
+
+
+ // We have a file ID in the query parameters, so we will use it to load a file.
+ if (fileId) {
+ gapi.drive.realtime.load(fileId, this.onFileLoaded, this.initializeModel, handleErrors);
+ return;
+ }
+
+ // We have a state parameter being redirected from the Drive UI. We will parse
+ // it and redirect to the fileId contained.
+ else if (state) {
+ var stateObj = rtclient.parseState(state);
+ // If opening a file from Drive.
+ if (stateObj.action == "open") {
+ fileId = stateObj.ids[0];
+ userId = stateObj.userId;
+ rtclient.redirectTo(fileId, userId);
+ return;
+ }
+ }
+
+ if (this.autoCreate) {
+ this.createNewFileAndRedirect();
+ }
+};
+
+
+/**
+ * Creates a new file and redirects to the URL to load it.
+ */
+rtclient.RealtimeLoader.prototype.createNewFileAndRedirect = function(title) {
+ //No fileId or state have been passed. We create a new Realtime file and
+ // redirect to it.
+ var _this = this;
+ rtclient.createRealtimeFile(title, function(file) {
+ if (file.id) {
+ rtclient.redirectTo(file.id, _this.authorizer.userId);
+ }
+ // File failed to be created, log why and do not attempt to redirect.
+ else {
+ console.error('Error creating file.');
+ console.error(file);
+ }
+ });
+};
diff --git a/deploy.sh b/deploy.sh
index 7799d88..5e47b17 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -1,6 +1,6 @@
echo "Building static application with Hem"
-node_modules/hem/bin/hem build
+hem build
git add public/application.{css,js}
git commit -m "Building for deployment"
echo "Pushing to Heroku"
-git push heroku
+git push heroku master
diff --git a/lib/paper.patch b/lib/paper.patch
new file mode 100644
index 0000000..da248c9
--- /dev/null
+++ b/lib/paper.patch
@@ -0,0 +1,13870 @@
+diff --git a/app/controllers/link_tool.coffee b/app/controllers/link_tool.coffee
+index 7e323eb..9d2aaec 100644
+--- a/app/controllers/link_tool.coffee
++++ b/app/controllers/link_tool.coffee
+@@ -6,22 +6,22 @@ class LinkTool extends Tool
+ parameters: {'shape' : null}
+ drafting: false
+
+- onMouseMove: (event) ->
++ onMouseMove: (event) =>
+ if @parameters.shape
+ @clearSelection()
+ @select(@hitTester.nodeAt(event.point))
+
+- onMouseDown: (event) ->
++ onMouseDown: (event) =>
+ if @parameters.shape and @hitTester.nodeAt(event.point)
+ @drafting = true
+ @draftLink = new DraftLink(event.point)
+
+- onMouseDrag: (event) ->
++ onMouseDrag: (event) =>
+ if @drafting
+ @draftLink.extendTo(event.point)
+ @changeSelectionTo(@hitTester.nodeAt(event.point)) if @hitTester.nodeAt(event.point)
+
+- onMouseUp: (event) ->
++ onMouseUp: (event) =>
+ if @drafting
+ if @hitTester.nodeAt(event.point)
+ path = @draftLink.finalise()
+diff --git a/app/controllers/node_tool.coffee b/app/controllers/node_tool.coffee
+index 048c292..d6d20bc 100644
+--- a/app/controllers/node_tool.coffee
++++ b/app/controllers/node_tool.coffee
+@@ -4,13 +4,13 @@ CreateNode = require('models/commands/create_node')
+ class NodeTool extends Tool
+ parameters: {'shape' : null}
+
+- onMouseDown: (event) ->
++ onMouseDown: (event) =>
+ if @parameters.shape
+ @parameters.position = event.point
+ @run(new CreateNode(@drawing, @parameters))
+ @clearSelection()
+
+- onMouseMove: (event) ->
++ onMouseMove: (event) =>
+ if @parameters.shape
+ @clearSelection()
+ @select(@hitTester.nodeAt(event.point))
+diff --git a/app/controllers/select_tool.coffee b/app/controllers/select_tool.coffee
+index 9f9a571..ba1a1b5 100644
+--- a/app/controllers/select_tool.coffee
++++ b/app/controllers/select_tool.coffee
+@@ -5,18 +5,18 @@ MoveNode = require('models/commands/move_node')
+ class SelectTool extends Tool
+ parameters: {}
+
+- onMouseDown: (event) ->
++ onMouseDown: (event) =>
+ @clearSelection()
+ @select(@hitTester.nodeOrLinkAt(event.point))
+ @current = event.point
+ @start = event.point
+
+- onMouseDrag: (event) ->
++ onMouseDrag: (event) =>
+ for item in @selection() when item instanceof Node
+ @run(new MoveNode(item, event.point.subtract(@current)), undoable: false)
+ @current = event.point
+
+- onMouseUp: (event) ->
++ onMouseUp: (event) =>
+ for item in @selection() when item instanceof Node
+ @commander.add(new MoveNode(item, event.point.subtract(@start)))
+
+diff --git a/app/controllers/tool.coffee b/app/controllers/tool.coffee
+index 4c87f03..c36cb04 100644
+--- a/app/controllers/tool.coffee
++++ b/app/controllers/tool.coffee
+@@ -2,14 +2,24 @@ Node = require('models/node')
+ Link = require('models/link')
+ DeleteElement = require('models/commands/delete_element')
+
+-class Tool extends paper.Tool
++class Tool
+
+ constructor: (options) ->
+- super
+ @commander = options.commander
+ @hitTester = options.hitTester
+ @hitTester or= new PaperHitTester
+ @drawing = options.drawing
++
++ @_tool = new paper.Tool
++ @_tool.onMouseMove = @onMouseMove
++ @_tool.onMouseDrag = @onMouseDrag
++ @_tool.onMouseDown = @onMouseDown
++ @_tool.onMouseUp = @onMouseUp
++ @_tool.onKeyDown = @onKeyDown
++ @_tool.onKeyUp = @onKeyUp
++
++ activate: =>
++ @_tool.activate()
+
+ setParameter: (parameterKey, parameterValue) ->
+ @parameters or= {}
+@@ -18,7 +28,7 @@ class Tool extends paper.Tool
+ run: (command, options={undoable: true}) ->
+ @commander.run(command, options)
+
+- onKeyDown: (event) ->
++ onKeyDown: (event) =>
+ # don't intercept key events if any DOM element
+ # (e.g. form field) has focus
+ # FIXME: don't intercept values in case the canvas is in simulation mode (e.g. data-simulation=true?)
+diff --git a/app/controllers/toolbox.coffee b/app/controllers/toolbox.coffee
+index 5dea93e..dc9f4a3 100644
+--- a/app/controllers/toolbox.coffee
++++ b/app/controllers/toolbox.coffee
+@@ -13,12 +13,23 @@ class Toolbox extends Spine.Controller
+ constructor: ->
+ super
+ @render()
++ @createTools()
++ @switchTo("select")
++
++ createTools: ->
++ # Remove any existing tools. This is a workaround
++ # for when the user performs a refresh (F5) when on
++ # the drawing page. It seems the Spine (possibly Substack)
++ # redirects back to the drawings index, and then when
++ # the user next selects a drawing, another set of
++ # tools will be created
++ paper.tools = []
++
+ @tools =
+ node: new NodeTool(commander: @commander, drawing: @item)
+ select: new SelectTool(commander: @commander, drawing: @item)
+ link: new LinkTool(commander: @commander, drawing: @item)
+- @switchTo("select")
+-
++
+ render: =>
+ @html require('views/drawings/toolbox')(@item) if @item
+
+diff --git a/lib/paper.js b/lib/paper.js
+index 440faf0..65b6b2a 100644
+--- a/lib/paper.js
++++ b/lib/paper.js
+@@ -1,153 +1,111 @@
+ /*!
+- * Paper.js v0.22
+- *
+- * This file is part of Paper.js, a JavaScript Vector Graphics Library,
+- * based on Scriptographer.org and designed to be largely API compatible.
++ * Paper.js v0.9.9 - The Swiss Army Knife of Vector Graphics Scripting.
+ * http://paperjs.org/
+- * http://scriptographer.org/
+ *
+- * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
++ * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey
+ * http://lehni.org/ & http://jonathanpuckey.com/
+ *
+ * Distributed under the MIT license. See LICENSE file for details.
+ *
+ * All rights reserved.
+ *
+- * Date: Thu Nov 10 19:19:25 2011 +0100
++ * Date: Sun Jul 21 16:44:30 2013 -0700
+ *
+ ***
+ *
+- * Bootstrap.js JavaScript Framework.
+- * http://bootstrapjs.org/
++ * straps.js - Class inheritance library with support for bean-style accessors
+ *
+- * Copyright (c) 2006 - 2011 Juerg Lehni
++ * Copyright (c) 2006 - 2013 Juerg Lehni
+ * http://lehni.org/
+ *
+ * Distributed under the MIT license.
+ *
+ ***
+ *
+- * Parse-js
+- *
+- * A JavaScript tokenizer / parser / generator, originally written in Lisp.
+- * Copyright (c) Marijn Haverbeke
+- * http://marijn.haverbeke.nl/parse-js/
++ * acorn.js
++ * http://marijnhaverbeke.nl/acorn/
+ *
+- * Ported by to JavaScript by Mihai Bazon
+- * Copyright (c) 2010, Mihai Bazon
+- * http://mihai.bazon.net/blog/
+- *
+- * Modifications and adaptions to browser (c) 2011, Juerg Lehni
+- * http://lehni.org/
++ * Acorn is a tiny, fast JavaScript parser written in JavaScript,
++ * created by Marijn Haverbeke and released under an MIT license.
+ *
+- * Distributed under the BSD license.
+ */
+
+ var paper = new function() {
+
+-var Base = new function() {
+- var fix = !this.__proto__,
+- hidden = /^(statics|generics|preserve|enumerable|prototype|__proto__|toString|valueOf)$/,
+- proto = Object.prototype,
+- has = fix
+- ? function(name) {
+- return name !== '__proto__' && this.hasOwnProperty(name);
+- }
+- : proto.hasOwnProperty,
+- toString = proto.toString,
++var Base = new function() {
++ var hidden = /^(statics|generics|preserve|enumerable|prototype|toString|valueOf)$/,
++ toString = Object.prototype.toString,
+ proto = Array.prototype,
+- isArray = Array.isArray = Array.isArray || function(obj) {
+- return toString.call(obj) === '[object Array]';
+- },
+ slice = proto.slice,
+- forEach = proto.forEach = proto.forEach || function(iter, bind) {
++
++ forEach = proto.forEach || function(iter, bind) {
+ for (var i = 0, l = this.length; i < l; i++)
+ iter.call(bind, this[i], i, this);
+ },
++
+ forIn = function(iter, bind) {
+ for (var i in this)
+ if (this.hasOwnProperty(i))
+ iter.call(bind, this[i], i, this);
+ },
+- _define = Object.defineProperty,
+- _describe = Object.getOwnPropertyDescriptor;
+
+- function define(obj, name, desc) {
+- if (_define) {
+- try {
+- delete obj[name];
+- return _define(obj, name, desc);
+- } catch (e) {}
+- }
+- if ((desc.get || desc.set) && obj.__defineGetter__) {
+- desc.get && obj.__defineGetter__(name, desc.get);
+- desc.set && obj.__defineSetter__(name, desc.set);
+- } else {
+- obj[name] = desc.value;
+- }
+- return obj;
+- }
++ isArray = Array.isArray = Array.isArray || function(obj) {
++ return toString.call(obj) === '[object Array]';
++ },
+
+- function describe(obj, name) {
+- if (_describe) {
+- try {
+- return _describe(obj, name);
+- } catch (e) {}
+- }
+- var get = obj.__lookupGetter__ && obj.__lookupGetter__(name);
+- return get
+- ? { get: get, set: obj.__lookupSetter__(name), enumerable: true,
+- configurable: true }
+- : has.call(obj, name)
+- ? { value: obj[name], enumerable: true, configurable: true,
+- writable: true }
+- : null;
+- }
++ create = Object.create || function(proto) {
++ return { __proto__: proto };
++ },
++
++ describe = Object.getOwnPropertyDescriptor || function(obj, name) {
++ var get = obj.__lookupGetter__ && obj.__lookupGetter__(name);
++ return get
++ ? { get: get, set: obj.__lookupSetter__(name),
++ enumerable: true, configurable: true }
++ : obj.hasOwnProperty(name)
++ ? { value: obj[name], enumerable: true,
++ configurable: true, writable: true }
++ : null;
++ },
++
++ _define = Object.defineProperty || function(obj, name, desc) {
++ if ((desc.get || desc.set) && obj.__defineGetter__) {
++ if (desc.get)
++ obj.__defineGetter__(name, desc.get);
++ if (desc.set)
++ obj.__defineSetter__(name, desc.set);
++ } else {
++ obj[name] = desc.value;
++ }
++ return obj;
++ },
++
++ define = function(obj, name, desc) {
++ delete obj[name];
++ return _define(obj, name, desc);
++ };
+
+ function inject(dest, src, enumerable, base, preserve, generics) {
+- var beans, bean;
++ var beans;
+
+ function field(name, val, dontCheck, generics) {
+ var val = val || (val = describe(src, name))
+- && (val.get ? val : val.value),
+- func = typeof val === 'function',
++ && (val.get ? val : val.value);
++ if (typeof val === 'string' && val[0] === '#')
++ val = dest[val.substring(1)] || val;
++ var isFunc = typeof val === 'function',
+ res = val,
+- prev = preserve || func
+- ? (val && val.get ? name in dest : dest[name]) : null;
+- if (generics && func && (!preserve || !generics[name])) {
+- generics[name] = function(bind) {
+- return bind && dest[name].apply(bind,
+- slice.call(arguments, 1));
+- }
+- }
+- if ((dontCheck || val !== undefined && has.call(src, name))
++ prev = preserve || isFunc
++ ? (val && val.get ? name in dest : dest[name]) : null,
++ bean;
++ if ((dontCheck || val !== undefined && src.hasOwnProperty(name))
+ && (!preserve || !prev)) {
+- if (func) {
+- if (prev && /\bthis\.base\b/.test(val)) {
+- var fromBase = base && base[name] == prev;
+- res = function() {
+- var tmp = describe(this, 'base');
+- define(this, 'base', { value: fromBase
+- ? base[name] : prev, configurable: true });
+- try {
+- return val.apply(this, arguments);
+- } finally {
+- tmp ? define(this, 'base', tmp)
+- : delete this.base;
+- }
+- };
+- res.toString = function() {
+- return val.toString();
+- }
+- res.valueOf = function() {
+- return val.valueOf();
+- }
+- }
+- if (beans && val.length == 0
+- && (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
+- beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
+- }
+- if (!res || func || !res.get && !res.set)
++ if (isFunc && prev)
++ val.base = prev;
++ if (isFunc && beans && val.length === 0
++ && (bean = name.match(/^(get|is)(([A-Z])(.*))$/)))
++ beans.push([ bean[3].toLowerCase() + bean[4], bean[2] ]);
++ if (!res || isFunc || !res.get)
+ res = { value: res, writable: true };
+ if ((describe(dest, name)
+ || { configurable: true }).configurable) {
+@@ -156,17 +114,24 @@ var Base = new function() {
+ }
+ define(dest, name, res);
+ }
++ if (generics && isFunc && (!preserve || !generics[name])) {
++ generics[name] = function(bind) {
++ return bind && dest[name].apply(bind,
++ slice.call(arguments, 1));
++ };
++ }
+ }
+ if (src) {
+ beans = [];
+ for (var name in src)
+- if (has.call(src, name) && !hidden.test(name))
++ if (src.hasOwnProperty(name) && !hidden.test(name))
+ field(name, null, true, generics);
+ field('toString');
+ field('valueOf');
+ for (var i = 0, l = beans && beans.length; i < l; i++)
+ try {
+- var bean = beans[i], part = bean[1];
++ var bean = beans[i],
++ part = bean[1];
+ field(bean[0], {
+ get: dest['get' + part] || dest['is' + part],
+ set: dest['set' + part]
+@@ -176,35 +141,14 @@ var Base = new function() {
+ return dest;
+ }
+
+- function extend(obj) {
+- var ctor = function(dont) {
+- if (fix) define(this, '__proto__', { value: obj });
+- if (this.initialize && dont !== ctor.dont)
+- return this.initialize.apply(this, arguments);
+- }
+- ctor.prototype = obj;
+- ctor.toString = function() {
+- return (this.prototype.initialize || function() {}).toString();
+- }
+- return ctor;
+- }
+-
+- function iterator(iter) {
+- return !iter
+- ? function(val) { return val }
+- : typeof iter !== 'function'
+- ? function(val) { return val == iter }
+- : iter;
+- }
+-
+ function each(obj, iter, bind, asArray) {
+ try {
+ if (obj)
+- (asArray || asArray === undefined && isArray(obj)
+- ? forEach : forIn).call(obj, iterator(iter),
+- bind = bind || obj);
++ (asArray || typeof asArray === 'undefined' && isArray(obj)
++ ? forEach : forIn).call(obj, iter, bind = bind || obj);
+ } catch (e) {
+- if (e !== Base.stop) throw e;
++ if (e !== Base.stop)
++ throw e;
+ }
+ return bind;
+ }
+@@ -215,12 +159,12 @@ var Base = new function() {
+ }, new obj.constructor());
+ }
+
+- return inject(function() {}, {
++ return inject(function Base() {}, {
+ inject: function(src) {
+ if (src) {
+ var proto = this.prototype,
+- base = proto.__proto__ && proto.__proto__.constructor,
+- statics = src.statics == true ? src : src.statics;
++ base = Object.getPrototypeOf(proto).constructor,
++ statics = src.statics === true ? src : src.statics;
+ if (statics != src)
+ inject(proto, src, src.enumerable, base && base.prototype,
+ src.preserve, src.generics && this);
+@@ -231,27 +175,30 @@ var Base = new function() {
+ return this;
+ },
+
+- extend: function(src) {
+- var proto = new this(this.dont),
+- ctor = extend(proto);
+- define(proto, 'constructor',
++ extend: function() {
++ var base = this,
++ ctor;
++ for (var i = 0, l = arguments.length; i < l; i++)
++ if (ctor = arguments[i].initialize)
++ break;
++ ctor = ctor || function() {
++ base.apply(this, arguments);
++ };
++ ctor.prototype = create(this.prototype);
++ define(ctor.prototype, 'constructor',
+ { value: ctor, writable: true, configurable: true });
+- ctor.dont = {};
+ inject(ctor, this, true);
+ return arguments.length ? this.inject.apply(ctor, arguments) : ctor;
+ }
+ }, true).inject({
+- has: has,
+- each: each,
+-
+ inject: function() {
+ for (var i = 0, l = arguments.length; i < l; i++)
+- inject(this, arguments[i]);
++ inject(this, arguments[i], arguments[i].enumerable);
+ return this;
+ },
+
+ extend: function() {
+- var res = new (extend(this));
++ var res = create(this);
+ return res.inject.apply(res, arguments);
+ },
+
+@@ -268,14 +215,15 @@ var Base = new function() {
+ clone: clone,
+ define: define,
+ describe: describe,
+- iterator: iterator,
+
+- has: function(obj, name) {
+- return has.call(obj, name);
++ create: function(ctor) {
++ return create(ctor.prototype);
+ },
+
+- type: function(obj) {
+- return (obj || obj === 0) && (obj._type || typeof obj) || null;
++ isPlainObject: function(obj) {
++ var ctor = obj != null && obj.constructor;
++ return ctor && (ctor === Object || ctor === Base
++ || ctor.name === 'Object');
+ },
+
+ check: function(obj) {
+@@ -292,9 +240,12 @@ var Base = new function() {
+ stop: {}
+ }
+ });
+-}
++};
+
+-this.Base = Base.inject({
++if (typeof module !== 'undefined')
++ module.exports = Base;
++
++Base.inject({
+ generics: true,
+
+ clone: function() {
+@@ -302,44 +253,257 @@ this.Base = Base.inject({
+ },
+
+ toString: function() {
+- return '{ ' + Base.each(this, function(value, key) {
+- if (key.charAt(0) != '_') {
+- var type = typeof value;
+- this.push(key + ': ' + (type === 'number'
+- ? Base.formatNumber(value)
+- : type === 'string' ? "'" + value + "'" : value));
+- }
+- }, []).join(', ') + ' }';
++ return this._id != null
++ ? (this._class || 'Object') + (this._name
++ ? " '" + this._name + "'"
++ : ' @' + this._id)
++ : '{ ' + Base.each(this, function(value, key) {
++ if (!/^_/.test(key)) {
++ var type = typeof value;
++ this.push(key + ': ' + (type === 'number'
++ ? Formatter.instance.number(value)
++ : type === 'string' ? "'" + value + "'" : value));
++ }
++ }, []).join(', ') + ' }';
++ },
++
++ exportJSON: function(options) {
++ return Base.exportJSON(this, options);
++ },
++
++ toJSON: function() {
++ return Base.serialize(this);
++ },
++
++ _set: function(props, exclude) {
++ if (props && Base.isPlainObject(props)) {
++ for (var key in props)
++ if (props.hasOwnProperty(key) && key in this
++ && (!exclude || !exclude[key]))
++ this[key] = props[key];
++ return true;
++ }
+ },
+
+ statics: {
+- read: function(list, start, length) {
+- var start = start || 0,
+- length = length || list.length - start;
+- var obj = list[start];
++
++ exports: {},
++
++ extend: function extend() {
++ var res = extend.base.apply(this, arguments),
++ name = res.prototype._class;
++ if (name && !Base.exports[name])
++ Base.exports[name] = res;
++ return res;
++ },
++
++ equals: function(obj1, obj2) {
++ function checkKeys(o1, o2) {
++ for (var i in o1)
++ if (o1.hasOwnProperty(i) && typeof o2[i] === 'undefined')
++ return false;
++ return true;
++ }
++ if (obj1 === obj2)
++ return true;
++ if (obj1 && obj1.equals)
++ return obj1.equals(obj2);
++ if (obj2 && obj2.equals)
++ return obj2.equals(obj1);
++ if (Array.isArray(obj1) && Array.isArray(obj2)) {
++ if (obj1.length !== obj2.length)
++ return false;
++ for (var i = 0, l = obj1.length; i < l; i++) {
++ if (!Base.equals(obj1[i], obj2[i]))
++ return false;
++ }
++ return true;
++ }
++ if (obj1 && typeof obj1 === 'object'
++ && obj2 && typeof obj2 === 'object') {
++ if (!checkKeys(obj1, obj2) || !checkKeys(obj2, obj1))
++ return false;
++ for (var i in obj1) {
++ if (obj1.hasOwnProperty(i) && !Base.equals(obj1[i], obj2[i]))
++ return false;
++ }
++ return true;
++ }
++ return false;
++ },
++
++ read: function(list, start, length, options) {
++ if (this === Base) {
++ var value = this.peek(list, start);
++ list._index++;
++ list.__read = 1;
++ return value;
++ }
++ var proto = this.prototype,
++ readIndex = proto._readIndex,
++ index = start || readIndex && list._index || 0;
++ if (!length)
++ length = list.length - index;
++ var obj = list[index];
+ if (obj instanceof this
+- || this.prototype._readNull && obj == null && length <= 1)
+- return obj;
+- obj = new this(this.dont);
+- return obj.initialize.apply(obj, start > 0 || length < list.length
+- ? Array.prototype.slice.call(list, start, start + length)
++ || options && options.readNull && obj == null && length <= 1) {
++ if (readIndex)
++ list._index = index + 1;
++ return obj && options && options.clone ? obj.clone() : obj;
++ }
++ obj = Base.create(this);
++ if (readIndex)
++ obj.__read = true;
++ if (options)
++ obj.__options = options;
++ obj = obj.initialize.apply(obj, index > 0 || length < list.length
++ ? Array.prototype.slice.call(list, index, index + length)
+ : list) || obj;
++ if (readIndex) {
++ list._index = index + obj.__read;
++ list.__read = obj.__read;
++ delete obj.__read;
++ if (options)
++ delete obj.__options;
++ }
++ return obj;
+ },
+
+- readAll: function(list, start) {
++ peek: function(list, start) {
++ return list[list._index = start || list._index || 0];
++ },
++
++ readAll: function(list, start, options) {
+ var res = [], entry;
+ for (var i = start || 0, l = list.length; i < l; i++) {
+ res.push(Array.isArray(entry = list[i])
+- ? this.read(entry, 0)
+- : this.read(list, i, 1));
++ ? this.read(entry, 0, 0, options)
++ : this.read(list, i, 1, options));
++ }
++ return res;
++ },
++
++ readNamed: function(list, name, start, length, options) {
++ var value = this.getNamed(list, name);
++ return this.read(value != null ? [value] : list, start, length,
++ options);
++ },
++
++ getNamed: function(list, name) {
++ var arg = list[0];
++ if (list._hasObject === undefined)
++ list._hasObject = list.length === 1 && Base.isPlainObject(arg);
++ if (list._hasObject)
++ return name ? arg[name] : arg;
++ },
++
++ hasNamed: function(list, name) {
++ return !!this.getNamed(list, name);
++ },
++
++ isPlainValue: function(obj) {
++ return this.isPlainObject(obj) || Array.isArray(obj);
++ },
++
++ serialize: function(obj, options, compact, dictionary) {
++ options = options || {};
++
++ var root = !dictionary,
++ res;
++ if (root) {
++ options.formatter = new Formatter(options.precision);
++ dictionary = {
++ length: 0,
++ definitions: {},
++ references: {},
++ add: function(item, create) {
++ var id = '#' + item._id,
++ ref = this.references[id];
++ if (!ref) {
++ this.length++;
++ var res = create.call(item),
++ name = item._class;
++ if (name && res[0] !== name)
++ res.unshift(name);
++ this.definitions[id] = res;
++ ref = this.references[id] = [id];
++ }
++ return ref;
++ }
++ };
++ }
++ if (obj && obj._serialize) {
++ res = obj._serialize(options, dictionary);
++ var name = obj._class;
++ if (name && !compact && !res._compact && res[0] !== name)
++ res.unshift(name);
++ } else if (Array.isArray(obj)) {
++ res = [];
++ for (var i = 0, l = obj.length; i < l; i++)
++ res[i] = Base.serialize(obj[i], options, compact,
++ dictionary);
++ if (compact)
++ res._compact = true;
++ } else if (Base.isPlainObject(obj)) {
++ res = {};
++ for (var i in obj)
++ if (obj.hasOwnProperty(i))
++ res[i] = Base.serialize(obj[i], options, compact,
++ dictionary);
++ } else if (typeof obj === 'number') {
++ res = options.formatter.number(obj, options.precision);
++ } else {
++ res = obj;
++ }
++ return root && dictionary.length > 0
++ ? [['dictionary', dictionary.definitions], res]
++ : res;
++ },
++
++ deserialize: function(obj, data) {
++ var res = obj;
++ data = data || {};
++ if (Array.isArray(obj)) {
++ var type = obj[0],
++ isDictionary = type === 'dictionary';
++ if (!isDictionary) {
++ if (data.dictionary && obj.length == 1 && /^#/.test(type))
++ return data.dictionary[type];
++ type = Base.exports[type];
++ }
++ res = [];
++ for (var i = type ? 1 : 0, l = obj.length; i < l; i++)
++ res.push(Base.deserialize(obj[i], data));
++ if (isDictionary) {
++ data.dictionary = res[0];
++ } else if (type) {
++ var args = res;
++ res = Base.create(type);
++ type.apply(res, args);
++ }
++ } else if (Base.isPlainObject(obj)) {
++ res = {};
++ for (var key in obj)
++ res[key] = Base.deserialize(obj[key], data);
+ }
+ return res;
+ },
+
++ exportJSON: function(obj, options) {
++ return JSON.stringify(Base.serialize(obj, options));
++ },
++
++ importJSON: function(json) {
++ return Base.deserialize(
++ typeof json === 'string' ? JSON.parse(json) : json);
++ },
++
+ splice: function(list, items, index, remove) {
+ var amount = items && items.length,
+ append = index === undefined;
+ index = append ? list.length : index;
++ if (index > list.length)
++ index = list.length;
+ for (var i = 0; i < amount; i++)
+ items[i]._index = index + i;
+ if (append) {
+@@ -373,46 +537,159 @@ this.Base = Base.inject({
+ },
+
+ camelize: function(str) {
+- return str.replace(/-(\w)/g, function(all, chr) {
++ return str.replace(/-(.)/g, function(all, chr) {
+ return chr.toUpperCase();
+ });
+ },
+
+ hyphenate: function(str) {
+- return str.replace(/[a-z][A-Z0-9]|[0-9][a-zA-Z]|[A-Z]{2}[a-z]/g,
+- function(match) {
+- return match.charAt(0) + '-' + match.substring(1);
+- }
+- ).toLowerCase();
+- },
+-
+- formatNumber: function(num) {
+- return (Math.round(num * 100000) / 100000).toString();
++ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
+ }
+ }
+ });
+
+-var PaperScope = this.PaperScope = Base.extend({
++var Callback = {
++ attach: function(type, func) {
++ if (typeof type !== 'string') {
++ Base.each(type, function(value, key) {
++ this.attach(key, value);
++ }, this);
++ return;
++ }
++ var entry = this._eventTypes[type];
++ if (entry) {
++ var handlers = this._handlers = this._handlers || {};
++ handlers = handlers[type] = handlers[type] || [];
++ if (handlers.indexOf(func) == -1) {
++ handlers.push(func);
++ if (entry.install && handlers.length == 1)
++ entry.install.call(this, type);
++ }
++ }
++ },
++
++ detach: function(type, func) {
++ if (typeof type !== 'string') {
++ Base.each(type, function(value, key) {
++ this.detach(key, value);
++ }, this);
++ return;
++ }
++ var entry = this._eventTypes[type],
++ handlers = this._handlers && this._handlers[type],
++ index;
++ if (entry && handlers) {
++ if (!func || (index = handlers.indexOf(func)) != -1
++ && handlers.length == 1) {
++ if (entry.uninstall)
++ entry.uninstall.call(this, type);
++ delete this._handlers[type];
++ } else if (index != -1) {
++ handlers.splice(index, 1);
++ }
++ }
++ },
++
++ once: function(type, func) {
++ this.attach(type, function() {
++ func.apply(this, arguments);
++ this.detach(type, func);
++ });
++ },
++
++ fire: function(type, event) {
++ var handlers = this._handlers && this._handlers[type];
++ if (!handlers)
++ return false;
++ var args = [].slice.call(arguments, 1);
++ Base.each(handlers, function(func) {
++ if (func.apply(this, args) === false && event && event.stop)
++ event.stop();
++ }, this);
++ return true;
++ },
++
++ responds: function(type) {
++ return !!(this._handlers && this._handlers[type]);
++ },
++
++ on: '#attach',
++ off: '#detach',
++ trigger: '#fire',
++
++ statics: {
++ inject: function inject() {
++ for (var i = 0, l = arguments.length; i < l; i++) {
++ var src = arguments[i],
++ events = src._events;
++ if (events) {
++ var types = {};
++ Base.each(events, function(entry, key) {
++ var isString = typeof entry === 'string',
++ name = isString ? entry : key,
++ part = Base.capitalize(name),
++ type = name.substring(2).toLowerCase();
++ types[type] = isString ? {} : entry;
++ name = '_' + name;
++ src['get' + part] = function() {
++ return this[name];
++ };
++ src['set' + part] = function(func) {
++ if (func) {
++ this.attach(type, func);
++ } else if (this[name]) {
++ this.detach(type, this[name]);
++ }
++ this[name] = func;
++ };
++ });
++ src._eventTypes = types;
++ }
++ inject.base.call(this, src);
++ }
++ return this;
++ }
++ }
++};
++
++var PaperScope = Base.extend({
++ _class: 'PaperScope',
+
+- initialize: function(script) {
++ initialize: function PaperScope(script) {
+ paper = this;
+- this.view = null;
+- this.views = [];
+ this.project = null;
+ this.projects = [];
+- this.tool = null;
+ this.tools = [];
++ this.palettes = [];
+ this._id = script && (script.getAttribute('id') || script.src)
+ || ('paperscope-' + (PaperScope._id++));
+ if (script)
+ script.setAttribute('id', this._id);
+ PaperScope._scopes[this._id] = this;
++ if (!this.support) {
++ var ctx = CanvasProvider.getContext(1, 1);
++ PaperScope.prototype.support = {
++ nativeDash: 'setLineDash' in ctx || 'mozDash' in ctx,
++ nativeBlendModes: BlendMode.nativeModes
++ };
++ CanvasProvider.release(ctx);
++ }
++ },
++
++ version: '0.9.9',
++
++ getView: function() {
++ return this.project && this.project.view;
+ },
+
+- version: 0.22,
++ getTool: function() {
++ if (!this._tool)
++ this._tool = new Tool();
++ return this._tool;
++ },
+
+ evaluate: function(code) {
+- var res = PaperScript.evaluate(code, this);
++ var res = paper.PaperScript.evaluate(code, this);
+ View.updateFocus();
+ return res;
+ },
+@@ -422,32 +699,34 @@ var PaperScope = this.PaperScope = Base.extend({
+ Base.each(['project', 'view', 'tool'], function(key) {
+ Base.define(scope, key, {
+ configurable: true,
+- writable: true,
+ get: function() {
+ return that[key];
+ }
+ });
+ });
+ for (var key in this) {
+- if (!/^(version|_id|load)/.test(key) && !(key in scope))
++ if (!/^(version|_id)/.test(key) && !(key in scope))
+ scope[key] = this[key];
+ }
+ },
+
+ setup: function(canvas) {
+ paper = this;
+- this.project = new Project();
+- if (canvas)
+- this.view = new View(canvas);
++ this.project = new Project(canvas);
++ return this;
++ },
++
++ activate: function() {
++ paper = this;
+ },
+
+ clear: function() {
+ for (var i = this.projects.length - 1; i >= 0; i--)
+ this.projects[i].remove();
+- for (var i = this.views.length - 1; i >= 0; i--)
+- this.views[i].remove();
+ for (var i = this.tools.length - 1; i >= 0; i--)
+ this.tools[i].remove();
++ for (var i = this.palettes.length - 1; i >= 0; i--)
++ this.palettes[i].remove();
+ },
+
+ remove: function() {
+@@ -455,31 +734,31 @@ var PaperScope = this.PaperScope = Base.extend({
+ delete PaperScope._scopes[this._id];
+ },
+
+- _needsRedraw: function() {
+- if (!this._redrawNotified) {
+- for (var i = this.views.length - 1; i >= 0; i--)
+- this.views[i]._redrawNeeded = true;
+- this._redrawNotified = true;
++ statics: new function() {
++ function handleAttribute(name) {
++ name += 'Attribute';
++ return function(el, attr) {
++ return el[name](attr) || el[name]('data-paper-' + attr);
++ };
+ }
+- },
+
+- statics: {
+- _scopes: {},
+- _id: 0,
++ return {
++ _scopes: {},
++ _id: 0,
+
+- get: function(id) {
+- if (typeof id === 'object')
+- id = id.getAttribute('id');
+- return this._scopes[id] || null;
+- },
++ get: function(id) {
++ if (typeof id === 'object')
++ id = id.getAttribute('id');
++ return this._scopes[id] || null;
++ },
+
+- each: function(iter) {
+- Base.each(this._scopes, iter);
+- }
++ getAttribute: handleAttribute('get'),
++ hasAttribute: handleAttribute('has')
++ };
+ }
+ });
+
+-var PaperScopeItem = Base.extend({
++var PaperScopeItem = Base.extend(Callback, {
+
+ initialize: function(activate) {
+ this._scope = paper;
+@@ -491,10 +770,18 @@ var PaperScopeItem = Base.extend({
+ activate: function() {
+ if (!this._scope)
+ return false;
++ var prev = this._scope[this._reference];
++ if (prev && prev != this)
++ prev.fire('deactivate');
+ this._scope[this._reference] = this;
++ this.fire('activate', prev);
+ return true;
+ },
+
++ isActive: function() {
++ return this._scope[this._reference] === this;
++ },
++
+ remove: function() {
+ if (this._index == null)
+ return false;
+@@ -506,96 +793,292 @@ var PaperScopeItem = Base.extend({
+ }
+ });
+
+-var Point = this.Point = Base.extend({
+- initialize: function(arg0, arg1) {
+- if (arg1 !== undefined) {
+- this.x = arg0;
+- this.y = arg1;
+- } else if (arg0 !== undefined) {
+- if (arg0 == null) {
+- this.x = this.y = 0;
+- } else if (arg0.x !== undefined) {
+- this.x = arg0.x;
+- this.y = arg0.y;
+- } else if (arg0.width !== undefined) {
+- this.x = arg0.width;
+- this.y = arg0.height;
+- } else if (Array.isArray(arg0)) {
+- this.x = arg0[0];
+- this.y = arg0.length > 1 ? arg0[1] : arg0[0];
+- } else if (arg0.angle !== undefined) {
+- this.x = arg0.length;
+- this.y = 0;
+- this.setAngle(arg0.angle);
+- } else if (typeof arg0 === 'number') {
+- this.x = this.y = arg0;
+- } else {
+- this.x = this.y = 0;
+- }
+- } else {
+- this.x = this.y = 0;
+- }
+- },
+-
+- set: function(x, y) {
+- this.x = x;
+- this.y = y;
+- return this;
+- },
+-
+- clone: function() {
+- return Point.create(this.x, this.y);
+- },
+-
+- toString: function() {
+- var format = Base.formatNumber;
+- return '{ x: ' + format(this.x) + ', y: ' + format(this.y) + ' }';
++var Formatter = Base.extend({
++ initialize: function(precision) {
++ this.precision = precision || 5;
++ this.multiplier = Math.pow(10, this.precision);
+ },
+
+- add: function(point) {
+- point = Point.read(arguments);
+- return Point.create(this.x + point.x, this.y + point.y);
++ number: function(val) {
++ return Math.round(val * this.multiplier) / this.multiplier;
+ },
+
+- subtract: function(point) {
+- point = Point.read(arguments);
+- return Point.create(this.x - point.x, this.y - point.y);
++ point: function(val, separator) {
++ return this.number(val.x) + (separator || ',') + this.number(val.y);
+ },
+
+- multiply: function(point) {
+- point = Point.read(arguments);
+- return Point.create(this.x * point.x, this.y * point.y);
++ size: function(val, separator) {
++ return this.number(val.width) + (separator || ',')
++ + this.number(val.height);
+ },
+
+- divide: function(point) {
+- point = Point.read(arguments);
+- return Point.create(this.x / point.x, this.y / point.y);
+- },
++ rectangle: function(val, separator) {
++ return this.point(val, separator) + (separator || ',')
++ + this.size(val, separator);
++ }
++});
+
+- modulo: function(point) {
+- point = Point.read(arguments);
+- return Point.create(this.x % point.x, this.y % point.y);
+- },
++Formatter.instance = new Formatter(5);
+
+- negate: function() {
+- return Point.create(-this.x, -this.y);
+- },
++var Numerical = new function() {
+
+- transform: function(matrix) {
+- return matrix ? matrix._transformPoint(this) : this;
+- },
++ var abscissas = [
++ [ 0.5773502691896257645091488],
++ [0,0.7745966692414833770358531],
++ [ 0.3399810435848562648026658,0.8611363115940525752239465],
++ [0,0.5384693101056830910363144,0.9061798459386639927976269],
++ [ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016],
++ [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897],
++ [ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609],
++ [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762],
++ [ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640],
++ [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380],
++ [ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491],
++ [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294],
++ [ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973],
++ [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657],
++ [ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542]
++ ];
+
+- getDistance: function(point, squared) {
+- point = Point.read(arguments);
+- var x = point.x - this.x,
++ var weights = [
++ [1],
++ [0.8888888888888888888888889,0.5555555555555555555555556],
++ [0.6521451548625461426269361,0.3478548451374538573730639],
++ [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640],
++ [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961],
++ [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114],
++ [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314],
++ [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922],
++ [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688],
++ [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537],
++ [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160],
++ [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216],
++ [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329],
++ [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284],
++ [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806]
++ ];
++
++ var abs = Math.abs,
++ sqrt = Math.sqrt,
++ pow = Math.pow,
++ cos = Math.cos,
++ PI = Math.PI;
++
++ return {
++ TOLERANCE: 10e-6,
++ EPSILON: 10e-12,
++ KAPPA: 4 * (sqrt(2) - 1) / 3,
++
++ isZero: function(val) {
++ return abs(val) <= this.EPSILON;
++ },
++
++ integrate: function(f, a, b, n) {
++ var x = abscissas[n - 2],
++ w = weights[n - 2],
++ A = 0.5 * (b - a),
++ B = A + a,
++ i = 0,
++ m = (n + 1) >> 1,
++ sum = n & 1 ? w[i++] * f(B) : 0;
++ while (i < m) {
++ var Ax = A * x[i];
++ sum += w[i++] * (f(B + Ax) + f(B - Ax));
++ }
++ return A * sum;
++ },
++
++ findRoot: function(f, df, x, a, b, n, tolerance) {
++ for (var i = 0; i < n; i++) {
++ var fx = f(x),
++ dx = fx / df(x);
++ if (abs(dx) < tolerance)
++ return x;
++ var nx = x - dx;
++ if (fx > 0) {
++ b = x;
++ x = nx <= a ? 0.5 * (a + b) : nx;
++ } else {
++ a = x;
++ x = nx >= b ? 0.5 * (a + b) : nx;
++ }
++ }
++ },
++
++ solveQuadratic: function(a, b, c, roots) {
++ var epsilon = this.EPSILON;
++ if (abs(a) < epsilon) {
++ if (abs(b) >= epsilon) {
++ roots[0] = -c / b;
++ return 1;
++ }
++ return abs(c) < epsilon ? -1 : 0;
++ }
++ var q = b * b - 4 * a * c;
++ if (q < 0)
++ return 0;
++ q = sqrt(q);
++ a *= 2;
++ var n = 0;
++ roots[n++] = (-b - q) / a;
++ if (q > 0)
++ roots[n++] = (-b + q) / a;
++ return n;
++ },
++
++ solveCubic: function(a, b, c, d, roots) {
++ var epsilon = this.EPSILON;
++ if (abs(a) < epsilon)
++ return Numerical.solveQuadratic(b, c, d, roots);
++ b /= a;
++ c /= a;
++ d /= a;
++ var bb = b * b,
++ p = (bb - 3 * c) / 9,
++ q = (2 * bb * b - 9 * b * c + 27 * d) / 54,
++ ppp = p * p * p,
++ D = q * q - ppp;
++ b /= 3;
++ if (abs(D) < epsilon) {
++ if (abs(q) < epsilon) {
++ roots[0] = - b;
++ return 1;
++ }
++ var sqp = sqrt(p),
++ snq = q > 0 ? 1 : -1;
++ roots[0] = -snq * 2 * sqp - b;
++ roots[1] = snq * sqp - b;
++ return 2;
++ }
++ if (D < 0) {
++ var sqp = sqrt(p),
++ phi = Math.acos(q / (sqp * sqp * sqp)) / 3,
++ t = -2 * sqp,
++ o = 2 * PI / 3;
++ roots[0] = t * cos(phi) - b;
++ roots[1] = t * cos(phi + o) - b;
++ roots[2] = t * cos(phi - o) - b;
++ return 3;
++ }
++ var A = (q > 0 ? -1 : 1) * pow(abs(q) + sqrt(D), 1 / 3);
++ roots[0] = A + p / A - b;
++ return 1;
++ }
++ };
++};
++
++var Point = Base.extend({
++ _class: 'Point',
++ _readIndex: true,
++
++ initialize: function Point(arg0, arg1) {
++ var type = typeof arg0;
++ if (type === 'number') {
++ var hasY = typeof arg1 === 'number';
++ this.x = arg0;
++ this.y = hasY ? arg1 : arg0;
++ if (this.__read)
++ this.__read = hasY ? 2 : 1;
++ } else if (type === 'undefined' || arg0 === null) {
++ this.x = this.y = 0;
++ if (this.__read)
++ this.__read = arg0 === null ? 1 : 0;
++ } else {
++ if (Array.isArray(arg0)) {
++ this.x = arg0[0];
++ this.y = arg0.length > 1 ? arg0[1] : arg0[0];
++ } else if (arg0.x != null) {
++ this.x = arg0.x;
++ this.y = arg0.y;
++ } else if (arg0.width != null) {
++ this.x = arg0.width;
++ this.y = arg0.height;
++ } else if (arg0.angle != null) {
++ this.x = arg0.length;
++ this.y = 0;
++ this.setAngle(arg0.angle);
++ } else {
++ this.x = this.y = 0;
++ if (this.__read)
++ this.__read = 0;
++ }
++ if (this.__read)
++ this.__read = 1;
++ }
++ },
++
++ set: function(x, y) {
++ this.x = x;
++ this.y = y;
++ return this;
++ },
++
++ equals: function(point) {
++ return point === this || point && (this.x === point.x
++ && this.y === point.y
++ || Array.isArray(point) && this.x === point[0]
++ && this.y === point[1]) || false;
++ },
++
++ clone: function() {
++ return new Point(this.x, this.y);
++ },
++
++ toString: function() {
++ var f = Formatter.instance;
++ return '{ x: ' + f.number(this.x) + ', y: ' + f.number(this.y) + ' }';
++ },
++
++ _serialize: function(options) {
++ var f = options.formatter;
++ return [f.number(this.x),
++ f.number(this.y)];
++ },
++
++ add: function(point) {
++ point = Point.read(arguments);
++ return new Point(this.x + point.x, this.y + point.y);
++ },
++
++ subtract: function(point) {
++ point = Point.read(arguments);
++ return new Point(this.x - point.x, this.y - point.y);
++ },
++
++ multiply: function(point) {
++ point = Point.read(arguments);
++ return new Point(this.x * point.x, this.y * point.y);
++ },
++
++ divide: function(point) {
++ point = Point.read(arguments);
++ return new Point(this.x / point.x, this.y / point.y);
++ },
++
++ modulo: function(point) {
++ point = Point.read(arguments);
++ return new Point(this.x % point.x, this.y % point.y);
++ },
++
++ negate: function() {
++ return new Point(-this.x, -this.y);
++ },
++
++ transform: function(matrix) {
++ return matrix ? matrix._transformPoint(this) : this;
++ },
++
++ getDistance: function(point, squared) {
++ point = Point.read(arguments);
++ var x = point.x - this.x,
+ y = point.y - this.y,
+ d = x * x + y * y;
+ return squared ? d : Math.sqrt(d);
+ },
+
+ getLength: function() {
+- var l = this.x * this.x + this.y * this.y;
+- return arguments[0] ? l : Math.sqrt(l);
++ var length = this.x * this.x + this.y * this.y;
++ return arguments.length && arguments[0] ? length : Math.sqrt(length);
+ },
+
+ setLength: function(length) {
+@@ -607,7 +1090,7 @@ var Point = this.Point = Base.extend({
+ );
+ } else {
+ var scale = length / this.getLength();
+- if (scale == 0)
++ if (Numerical.isZero(scale))
+ this.getAngle();
+ this.set(
+ this.x * scale,
+@@ -621,8 +1104,8 @@ var Point = this.Point = Base.extend({
+ if (length === undefined)
+ length = 1;
+ var current = this.getLength(),
+- scale = current != 0 ? length / current : 0,
+- point = Point.create(this.x * scale, this.y * scale);
++ scale = current !== 0 ? length / current : 0,
++ point = new Point(this.x * scale, this.y * scale);
+ point._angle = this._angle;
+ return point;
+ },
+@@ -651,7 +1134,7 @@ var Point = this.Point = Base.extend({
+ } else {
+ var point = Point.read(arguments),
+ div = this.getLength() * point.getLength();
+- if (div == 0) {
++ if (Numerical.isZero(div)) {
+ return NaN;
+ } else {
+ return Math.acos(this.dot(point) / div);
+@@ -673,22 +1156,19 @@ var Point = this.Point = Base.extend({
+ },
+
+ rotate: function(angle, center) {
++ if (angle === 0)
++ return this.clone();
+ angle = angle * Math.PI / 180;
+ var point = center ? this.subtract(center) : this,
+ s = Math.sin(angle),
+ c = Math.cos(angle);
+- point = Point.create(
++ point = new Point(
+ point.x * c - point.y * s,
+ point.y * c + point.x * s
+ );
+ return center ? point.add(center) : point;
+ },
+
+- equals: function(point) {
+- point = Point.read(arguments);
+- return this.x == point.x && this.y == point.y;
+- },
+-
+ isInside: function(rect) {
+ return rect.contains(this);
+ },
+@@ -698,15 +1178,15 @@ var Point = this.Point = Base.extend({
+ },
+
+ isColinear: function(point) {
+- return this.cross(point) < Numerical.TOLERANCE;
++ return this.cross(point) < 0.00001;
+ },
+
+ isOrthogonal: function(point) {
+- return this.dot(point) < Numerical.TOLERANCE;
++ return this.dot(point) < 0.00001;
+ },
+
+ isZero: function() {
+- return this.x == 0 && this.y == 0;
++ return Numerical.isZero(this.x) && Numerical.isZero(this.y);
+ },
+
+ isNaN: function() {
+@@ -726,10 +1206,10 @@ var Point = this.Point = Base.extend({
+ project: function(point) {
+ point = Point.read(arguments);
+ if (point.isZero()) {
+- return Point.create(0, 0);
++ return new Point(0, 0);
+ } else {
+ var scale = this.dot(point) / point.dot(point);
+- return Point.create(
++ return new Point(
+ point.x * scale,
+ point.y * scale
+ );
+@@ -737,46 +1217,43 @@ var Point = this.Point = Base.extend({
+ },
+
+ statics: {
+- create: function(x, y) {
+- var point = new Point(Point.dont);
+- point.x = x;
+- point.y = y;
+- return point;
+- },
+-
+- min: function(point1, point2) {
+- point1 = Point.read(arguments, 0, 1);
+- point2 = Point.read(arguments, 1, 1);
+- return Point.create(
++ min: function() {
++ var point1 = Point.read(arguments);
++ point2 = Point.read(arguments);
++ return new Point(
+ Math.min(point1.x, point2.x),
+ Math.min(point1.y, point2.y)
+ );
+ },
+
+- max: function(point1, point2) {
+- point1 = Point.read(arguments, 0, 1);
+- point2 = Point.read(arguments, 1, 1);
+- return Point.create(
++ max: function() {
++ var point1 = Point.read(arguments);
++ point2 = Point.read(arguments);
++ return new Point(
+ Math.max(point1.x, point2.x),
+ Math.max(point1.y, point2.y)
+ );
+ },
+
+ random: function() {
+- return Point.create(Math.random(), Math.random());
++ return new Point(Math.random(), Math.random());
+ }
+ }
+-}, new function() {
+-
+- return Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
+- var op = Math[name];
+- this[name] = function() {
+- return Point.create(op(this.x), op(this.y));
+- };
+- }, {});
+-});
++}, Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
++ var op = Math[name];
++ this[name] = function() {
++ return new Point(op(this.x), op(this.y));
++ };
++}, {}));
+
+ var LinkedPoint = Point.extend({
++ initialize: function Point(x, y, owner, setter) {
++ this._x = x;
++ this._y = y;
++ this._owner = owner;
++ this._setter = setter;
++ },
++
+ set: function(x, y, dontNotify) {
+ this._x = x;
+ this._y = y;
+@@ -801,101 +1278,105 @@ var LinkedPoint = Point.extend({
+ setY: function(y) {
+ this._y = y;
+ this._owner[this._setter](this);
+- },
+-
+- statics: {
+- create: function(owner, setter, x, y, dontLink) {
+- if (dontLink)
+- return Point.create(x, y);
+- var point = new LinkedPoint(LinkedPoint.dont);
+- point._x = x;
+- point._y = y;
+- point._owner = owner;
+- point._setter = setter;
+- return point;
+- }
+ }
+ });
+
+-var Size = this.Size = Base.extend({
+- initialize: function(arg0, arg1) {
+- if (arg1 !== undefined) {
++var Size = Base.extend({
++ _class: 'Size',
++ _readIndex: true,
++
++ initialize: function Size(arg0, arg1) {
++ var type = typeof arg0;
++ if (type === 'number') {
++ var hasHeight = typeof arg1 === 'number';
+ this.width = arg0;
+- this.height = arg1;
+- } else if (arg0 !== undefined) {
+- if (arg0 == null) {
+- this.width = this.height = 0;
+- } else if (arg0.width !== undefined) {
++ this.height = hasHeight ? arg1 : arg0;
++ if (this.__read)
++ this.__read = hasHeight ? 2 : 1;
++ } else if (type === 'undefined' || arg0 === null) {
++ this.width = this.height = 0;
++ if (this.__read)
++ this.__read = arg0 === null ? 1 : 0;
++ } else {
++ if (Array.isArray(arg0)) {
++ this.width = arg0[0];
++ this.height = arg0.length > 1 ? arg0[1] : arg0[0];
++ } else if (arg0.width != null) {
+ this.width = arg0.width;
+ this.height = arg0.height;
+- } else if (arg0.x !== undefined) {
++ } else if (arg0.x != null) {
+ this.width = arg0.x;
+ this.height = arg0.y;
+- } else if (Array.isArray(arg0)) {
+- this.width = arg0[0];
+- this.height = arg0.length > 1 ? arg0[1] : arg0[0];
+- } else if (typeof arg0 === 'number') {
+- this.width = this.height = arg0;
+ } else {
+ this.width = this.height = 0;
++ if (this.__read)
++ this.__read = 0;
+ }
+- } else {
+- this.width = this.height = 0;
++ if (this.__read)
++ this.__read = 1;
+ }
+ },
+
+- toString: function() {
+- var format = Base.formatNumber;
+- return '{ width: ' + format(this.width)
+- + ', height: ' + format(this.height) + ' }';
+- },
+-
+ set: function(width, height) {
+ this.width = width;
+ this.height = height;
+ return this;
+ },
+
++ equals: function(size) {
++ return size === this || size && (this.width === size.width
++ && this.height === size.height
++ || Array.isArray(size) && this.width === size[0]
++ && this.height === size[1]) || false;
++ },
++
+ clone: function() {
+- return Size.create(this.width, this.height);
++ return new Size(this.width, this.height);
++ },
++
++ toString: function() {
++ var f = Formatter.instance;
++ return '{ width: ' + f.number(this.width)
++ + ', height: ' + f.number(this.height) + ' }';
++ },
++
++ _serialize: function(options) {
++ var f = options.formatter;
++ return [f.number(this.width),
++ f.number(this.height)];
+ },
+
+ add: function(size) {
+ size = Size.read(arguments);
+- return Size.create(this.width + size.width, this.height + size.height);
++ return new Size(this.width + size.width, this.height + size.height);
+ },
+
+ subtract: function(size) {
+ size = Size.read(arguments);
+- return Size.create(this.width - size.width, this.height - size.height);
++ return new Size(this.width - size.width, this.height - size.height);
+ },
+
+ multiply: function(size) {
+ size = Size.read(arguments);
+- return Size.create(this.width * size.width, this.height * size.height);
++ return new Size(this.width * size.width, this.height * size.height);
+ },
+
+ divide: function(size) {
+ size = Size.read(arguments);
+- return Size.create(this.width / size.width, this.height / size.height);
++ return new Size(this.width / size.width, this.height / size.height);
+ },
+
+ modulo: function(size) {
+ size = Size.read(arguments);
+- return Size.create(this.width % size.width, this.height % size.height);
++ return new Size(this.width % size.width, this.height % size.height);
+ },
+
+ negate: function() {
+- return Size.create(-this.width, -this.height);
+- },
+-
+- equals: function(size) {
+- size = Size.read(arguments);
+- return this.width == size.width && this.height == size.height;
++ return new Size(-this.width, -this.height);
+ },
+
+ isZero: function() {
+- return this.width == 0 && this.height == 0;
++ return Numerical.isZero(this.width) && Numerical.isZero(this.height);
+ },
+
+ isNaN: function() {
+@@ -903,37 +1384,37 @@ var Size = this.Size = Base.extend({
+ },
+
+ statics: {
+- create: function(width, height) {
+- return new Size(Size.dont).set(width, height);
+- },
+-
+ min: function(size1, size2) {
+- return Size.create(
++ return new Size(
+ Math.min(size1.width, size2.width),
+ Math.min(size1.height, size2.height));
+ },
+
+ max: function(size1, size2) {
+- return Size.create(
++ return new Size(
+ Math.max(size1.width, size2.width),
+ Math.max(size1.height, size2.height));
+ },
+
+ random: function() {
+- return Size.create(Math.random(), Math.random());
++ return new Size(Math.random(), Math.random());
+ }
+ }
+-}, new function() {
+-
+- return Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
+- var op = Math[name];
+- this[name] = function() {
+- return Size.create(op(this.width), op(this.height));
+- };
+- }, {});
+-});
++}, Base.each(['round', 'ceil', 'floor', 'abs'], function(name) {
++ var op = Math[name];
++ this[name] = function() {
++ return new Size(op(this.width), op(this.height));
++ };
++}, {}));
+
+ var LinkedSize = Size.extend({
++ initialize: function Size(width, height, owner, setter) {
++ this._width = width;
++ this._height = height;
++ this._owner = owner;
++ this._setter = setter;
++ },
++
+ set: function(width, height, dontNotify) {
+ this._width = width;
+ this._height = height;
+@@ -958,61 +1439,70 @@ var LinkedSize = Size.extend({
+ setHeight: function(height) {
+ this._height = height;
+ this._owner[this._setter](this);
+- },
+-
+- statics: {
+- create: function(owner, setter, width, height, dontLink) {
+- if (dontLink)
+- return Size.create(width, height);
+- var size = new LinkedSize(LinkedSize.dont);
+- size._width = width;
+- size._height = height;
+- size._owner = owner;
+- size._setter = setter;
+- return size;
+- }
+ }
+ });
+
+-var Rectangle = this.Rectangle = Base.extend({
+- initialize: function(arg0, arg1, arg2, arg3) {
+- if (arguments.length == 4) {
++var Rectangle = Base.extend({
++ _class: 'Rectangle',
++ _readIndex: true,
++
++ initialize: function Rectangle(arg0, arg1, arg2, arg3) {
++ var type = typeof arg0,
++ read = 0;
++ if (type === 'number') {
+ this.x = arg0;
+ this.y = arg1;
+ this.width = arg2;
+ this.height = arg3;
+- } else if (arguments.length == 2) {
+- if (arg1 && arg1.x !== undefined) {
+- var point1 = Point.read(arguments, 0, 1);
+- var point2 = Point.read(arguments, 1, 1);
+- this.x = point1.x;
+- this.y = point1.y;
+- this.width = point2.x - point1.x;
+- this.height = point2.y - point1.y;
++ read = 4;
++ } else if (type === 'undefined' || arg0 === null) {
++ this.x = this.y = this.width = this.height = 0;
++ read = arg0 === null ? 1 : 0;
++ } else if (arguments.length === 1) {
++ if (Array.isArray(arg0)) {
++ this.x = arg0[0];
++ this.y = arg0[1];
++ this.width = arg0[2];
++ this.height = arg0[3];
++ read = 1;
++ } else if (arg0.x !== undefined || arg0.width !== undefined) {
++ this.x = arg0.x || 0;
++ this.y = arg0.y || 0;
++ this.width = arg0.width || 0;
++ this.height = arg0.height || 0;
++ read = 1;
++ } else if (arg0.from === undefined && arg0.to === undefined) {
++ this.x = this.y = this.width = this.height = 0;
++ this._set(arg0);
++ read = 1;
++ }
++ }
++ if (!read) {
++ var point = Point.readNamed(arguments, 'from'),
++ next = Base.peek(arguments);
++ this.x = point.x;
++ this.y = point.y;
++ if (next && next.x !== undefined || Base.hasNamed(arguments, 'to')) {
++ var to = Point.readNamed(arguments, 'to');
++ this.width = to.x - point.x;
++ this.height = to.y - point.y;
+ if (this.width < 0) {
+- this.x = point2.x;
++ this.x = to.x;
+ this.width = -this.width;
+ }
+ if (this.height < 0) {
+- this.y = point2.y;
++ this.y = to.y;
+ this.height = -this.height;
+ }
+ } else {
+- var point = Point.read(arguments, 0, 1);
+- var size = Size.read(arguments, 1, 1);
+- this.x = point.x;
+- this.y = point.y;
++ var size = Size.read(arguments);
+ this.width = size.width;
+ this.height = size.height;
+ }
+- } else if (arg0) {
+- this.x = arg0.x || 0;
+- this.y = arg0.y || 0;
+- this.width = arg0.width || 0;
+- this.height = arg0.height || 0;
+- } else {
+- this.x = this.y = this.width = this.height = 0;
++ read = arguments._index;
+ }
++ if (this.__read)
++ this.__read = read;
+ },
+
+ set: function(x, y, width, height) {
+@@ -1023,28 +1513,62 @@ var Rectangle = this.Rectangle = Base.extend({
+ return this;
+ },
+
++ clone: function() {
++ return new Rectangle(this.x, this.y, this.width, this.height);
++ },
++
++ equals: function(rect) {
++ if (Base.isPlainValue(rect))
++ rect = Rectangle.read(arguments);
++ return rect === this
++ || rect && this.x === rect.x && this.y === rect.y
++ && this.width === rect.width && this.height=== rect.height
++ || false;
++ },
++
++ toString: function() {
++ var f = Formatter.instance;
++ return '{ x: ' + f.number(this.x)
++ + ', y: ' + f.number(this.y)
++ + ', width: ' + f.number(this.width)
++ + ', height: ' + f.number(this.height)
++ + ' }';
++ },
++
++ _serialize: function(options) {
++ var f = options.formatter;
++ return [f.number(this.x),
++ f.number(this.y),
++ f.number(this.width),
++ f.number(this.height)];
++ },
++
+ getPoint: function() {
+- return LinkedPoint.create(this, 'setPoint', this.x, this.y,
+- arguments[0]);
++ return new (arguments[0] ? Point : LinkedPoint)
++ (this.x, this.y, this, 'setPoint');
+ },
+
+ setPoint: function(point) {
+ point = Point.read(arguments);
+ this.x = point.x;
+ this.y = point.y;
+- return this;
+ },
+
+ getSize: function() {
+- return LinkedSize.create(this, 'setSize', this.width, this.height,
+- arguments[0]);
++ return new (arguments[0] ? Size : LinkedSize)
++ (this.width, this.height, this, 'setSize');
+ },
+
+ setSize: function(size) {
+ size = Size.read(arguments);
++ if (this._fixX)
++ this.x += (this.width - size.width) * this._fixX;
++ if (this._fixY)
++ this.y += (this.height - size.height) * this._fixY;
+ this.width = size.width;
+ this.height = size.height;
+- return this;
++ this._fixW = 1;
++ this._fixH = 1;
+ },
+
+ getLeft: function() {
+@@ -1052,9 +1576,10 @@ var Rectangle = this.Rectangle = Base.extend({
+ },
+
+ setLeft: function(left) {
+- this.width -= left - this.x;
++ if (!this._fixW)
++ this.width -= left - this.x;
+ this.x = left;
+- return this;
++ this._fixX = 0;
+ },
+
+ getTop: function() {
+@@ -1062,9 +1587,10 @@ var Rectangle = this.Rectangle = Base.extend({
+ },
+
+ setTop: function(top) {
+- this.height -= top - this.y;
++ if (!this._fixH)
++ this.height -= top - this.y;
+ this.y = top;
+- return this;
++ this._fixY = 0;
+ },
+
+ getRight: function() {
+@@ -1072,8 +1598,13 @@ var Rectangle = this.Rectangle = Base.extend({
+ },
+
+ setRight: function(right) {
+- this.width = right - this.x;
+- return this;
++ if (this._fixX !== undefined && this._fixX !== 1)
++ this._fixW = 0;
++ if (this._fixW)
++ this.x = right - this.width;
++ else
++ this.width = right - this.x;
++ this._fixX = 1;
+ },
+
+ getBottom: function() {
+@@ -1081,8 +1612,13 @@ var Rectangle = this.Rectangle = Base.extend({
+ },
+
+ setBottom: function(bottom) {
+- this.height = bottom - this.y;
+- return this;
++ if (this._fixY !== undefined && this._fixY !== 1)
++ this._fixH = 0;
++ if (this._fixH)
++ this.y = bottom - this.height;
++ else
++ this.height = bottom - this.y;
++ this._fixY = 1;
+ },
+
+ getCenterX: function() {
+@@ -1091,7 +1627,7 @@ var Rectangle = this.Rectangle = Base.extend({
+
+ setCenterX: function(x) {
+ this.x = x - this.width * 0.5;
+- return this;
++ this._fixX = 0.5;
+ },
+
+ getCenterY: function() {
+@@ -1100,38 +1636,25 @@ var Rectangle = this.Rectangle = Base.extend({
+
+ setCenterY: function(y) {
+ this.y = y - this.height * 0.5;
+- return this;
++ this._fixY = 0.5;
+ },
+
+ getCenter: function() {
+- return LinkedPoint.create(this, 'setCenter',
+- this.getCenterX(), this.getCenterY(), arguments[0]);
++ return new (arguments[0] ? Point : LinkedPoint)
++ (this.getCenterX(), this.getCenterY(), this, 'setCenter');
+ },
+
+ setCenter: function(point) {
+ point = Point.read(arguments);
+- return this.setCenterX(point.x).setCenterY(point.y);
+- },
+-
+- equals: function(rect) {
+- rect = Rectangle.read(arguments);
+- return this.x == rect.x && this.y == rect.y
+- && this.width == rect.width && this.height == rect.height;
++ this.setCenterX(point.x);
++ this.setCenterY(point.y);
++ return this;
+ },
+
+ isEmpty: function() {
+ return this.width == 0 || this.height == 0;
+ },
+
+- toString: function() {
+- var format = Base.formatNumber;
+- return '{ x: ' + format(this.x)
+- + ', y: ' + format(this.y)
+- + ', width: ' + format(this.width)
+- + ', height: ' + format(this.height)
+- + ' }';
+- },
+-
+ contains: function(arg) {
+ return arg && arg.width !== undefined
+ || (Array.isArray(arg) ? arg : arguments).length == 4
+@@ -1163,13 +1686,21 @@ var Rectangle = this.Rectangle = Base.extend({
+ && rect.y < this.y + this.height;
+ },
+
++ touches: function(rect) {
++ rect = Rectangle.read(arguments);
++ return rect.x + rect.width >= this.x
++ && rect.y + rect.height >= this.y
++ && rect.x <= this.x + this.width
++ && rect.y <= this.y + this.height;
++ },
++
+ intersect: function(rect) {
+ rect = Rectangle.read(arguments);
+ var x1 = Math.max(this.x, rect.x),
+ y1 = Math.max(this.y, rect.y),
+ x2 = Math.min(this.x + this.width, rect.x + rect.width),
+ y2 = Math.min(this.y + this.height, rect.y + rect.height);
+- return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
++ return new Rectangle(x1, y1, x2 - x1, y2 - y1);
+ },
+
+ unite: function(rect) {
+@@ -1178,7 +1709,7 @@ var Rectangle = this.Rectangle = Base.extend({
+ y1 = Math.min(this.y, rect.y),
+ x2 = Math.max(this.x + this.width, rect.x + rect.width),
+ y2 = Math.max(this.y + this.height, rect.y + rect.height);
+- return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
++ return new Rectangle(x1, y1, x2 - x1, y2 - y1);
+ },
+
+ include: function(point) {
+@@ -1187,25 +1718,19 @@ var Rectangle = this.Rectangle = Base.extend({
+ y1 = Math.min(this.y, point.y),
+ x2 = Math.max(this.x + this.width, point.x),
+ y2 = Math.max(this.y + this.height, point.y);
+- return Rectangle.create(x1, y1, x2 - x1, y2 - y1);
++ return new Rectangle(x1, y1, x2 - x1, y2 - y1);
+ },
+
+ expand: function(hor, ver) {
+ if (ver === undefined)
+ ver = hor;
+- return Rectangle.create(this.x - hor / 2, this.y - ver / 2,
++ return new Rectangle(this.x - hor / 2, this.y - ver / 2,
+ this.width + hor, this.height + ver);
+ },
+
+ scale: function(hor, ver) {
+ return this.expand(this.width * hor - this.width,
+ this.height * (ver === undefined ? hor : ver) - this.height);
+- },
+-
+- statics: {
+- create: function(x, y, width, height) {
+- return new Rectangle(Rectangle.dont).set(x, y, width, height);
+- }
+ }
+ }, new function() {
+ return Base.each([
+@@ -1228,17 +1753,24 @@ var Rectangle = this.Rectangle = Base.extend({
+ get = 'get' + part,
+ set = 'set' + part;
+ this[get] = function() {
+- return LinkedPoint.create(this, set,
+- this[getX](), this[getY](), arguments[0]);
++ return new (arguments[0] ? Point : LinkedPoint)
++ (this[getX](), this[getY](), this, set);
+ };
+ this[set] = function(point) {
+ point = Point.read(arguments);
+- return this[setX](point.x)[setY](point.y);
++ this[setX](point.x);
++ this[setY](point.y);
+ };
+ }, {});
+ });
+
+ var LinkedRectangle = Rectangle.extend({
++ initialize: function Rectangle(x, y, width, height, owner, setter) {
++ this.set(x, y, width, height, true);
++ this._owner = owner;
++ this._setter = setter;
++ },
++
+ set: function(x, y, width, height, dontNotify) {
+ this._x = x;
+ this._y = y;
+@@ -1247,16 +1779,6 @@ var LinkedRectangle = Rectangle.extend({
+ if (!dontNotify)
+ this._owner[this._setter](this);
+ return this;
+- },
+-
+- statics: {
+- create: function(owner, setter, x, y, width, height) {
+- var rect = new LinkedRectangle(LinkedRectangle.dont).set(
+- x, y, width, height, true);
+- rect._owner = owner;
+- rect._setter = setter;
+- return rect;
+- }
+ }
+ }, new function() {
+ var proto = Rectangle.prototype;
+@@ -1279,19 +1801,32 @@ var LinkedRectangle = Rectangle.extend({
+ 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'],
+ function(key) {
+ var name = 'set' + key;
+- this[name] = function(value) {
++ this[name] = function() {
+ this._dontNotify = true;
+ proto[name].apply(this, arguments);
+ delete this._dontNotify;
+ this._owner[this._setter](this);
+- return this;
+ };
+- }, {})
++ }, {
++ isSelected: function() {
++ return this._owner._boundsSelected;
++ },
++
++ setSelected: function(selected) {
++ var owner = this._owner;
++ if (owner.setSelected) {
++ owner._boundsSelected = selected;
++ owner.setSelected(selected || owner._selectedSegmentState > 0);
++ }
++ }
++ })
+ );
+ });
+
+-var Matrix = this.Matrix = Base.extend({
+- initialize: function(arg) {
++var Matrix = Base.extend({
++ _class: 'Matrix',
++
++ initialize: function Matrix(arg) {
+ var count = arguments.length,
+ ok = true;
+ if (count == 6) {
+@@ -1305,8 +1840,7 @@ var Matrix = this.Matrix = Base.extend({
+ ok = false;
+ }
+ } else if (count == 0) {
+- this._a = this._d = 1;
+- this._c = this._b = this._tx = this._ty = 0;
++ this.reset();
+ } else {
+ ok = false;
+ }
+@@ -1314,11 +1848,6 @@ var Matrix = this.Matrix = Base.extend({
+ throw new Error('Unsupported matrix parameters');
+ },
+
+- clone: function() {
+- return Matrix.create(this._a, this._c, this._b, this._d,
+- this._tx, this._ty);
+- },
+-
+ set: function(a, c, b, d, tx, ty) {
+ this._a = a;
+ this._c = c;
+@@ -1329,19 +1858,45 @@ var Matrix = this.Matrix = Base.extend({
+ return this;
+ },
+
+- scale: function( hor, ver, center) {
+- if (arguments.length < 2 || typeof ver === 'object') {
+- center = Point.read(arguments, 1);
+- ver = hor;
+- } else {
+- center = Point.read(arguments, 2);
+- }
++ _serialize: function(options) {
++ return Base.serialize(this.getValues(), options);
++ },
++
++ clone: function() {
++ return new Matrix(this._a, this._c, this._b, this._d,
++ this._tx, this._ty);
++ },
++
++ equals: function(mx) {
++ return mx === this || mx && this._a == mx._a && this._b == mx._b
++ && this._c == mx._c && this._d == mx._d && this._tx == mx._tx
++ && this._ty == mx._ty
++ || false;
++ },
++
++ toString: function() {
++ var f = Formatter.instance;
++ return '[[' + [f.number(this._a), f.number(this._b),
++ f.number(this._tx)].join(', ') + '], ['
++ + [f.number(this._c), f.number(this._d),
++ f.number(this._ty)].join(', ') + ']]';
++ },
++
++ reset: function() {
++ this._a = this._d = 1;
++ this._c = this._b = this._tx = this._ty = 0;
++ return this;
++ },
++
++ scale: function() {
++ var scale = Point.read(arguments),
++ center = Point.read(arguments, 0, 0, { readNull: true });
+ if (center)
+ this.translate(center);
+- this._a *= hor;
+- this._c *= hor;
+- this._b *= ver;
+- this._d *= ver;
++ this._a *= scale.x;
++ this._c *= scale.x;
++ this._b *= scale.y;
++ this._d *= scale.y;
+ if (center)
+ this.translate(center.negate());
+ return this;
+@@ -1349,47 +1904,62 @@ var Matrix = this.Matrix = Base.extend({
+
+ translate: function(point) {
+ point = Point.read(arguments);
+- var x = point.x, y = point.y;
++ var x = point.x,
++ y = point.y;
+ this._tx += x * this._a + y * this._b;
+ this._ty += x * this._c + y * this._d;
+ return this;
+ },
+
+ rotate: function(angle, center) {
+- return this.concatenate(
+- Matrix.getRotateInstance.apply(Matrix, arguments));
++ center = Point.read(arguments, 1);
++ angle = angle * Math.PI / 180;
++ var x = center.x,
++ y = center.y,
++ cos = Math.cos(angle),
++ sin = Math.sin(angle),
++ tx = x - x * cos + y * sin,
++ ty = y - x * sin - y * cos,
++ a = this._a,
++ b = this._b,
++ c = this._c,
++ d = this._d;
++ this._a = cos * a + sin * b;
++ this._b = -sin * a + cos * b;
++ this._c = cos * c + sin * d;
++ this._d = -sin * c + cos * d;
++ this._tx += tx * a + ty * b;
++ this._ty += tx * c + ty * d;
++ return this;
+ },
+
+- shear: function( hor, ver, center) {
+- if (arguments.length < 2 || typeof ver === 'object') {
+- center = Point.read(arguments, 1);
+- ver = hor;
+- } else {
+- center = Point.read(arguments, 2);
+- }
++ shear: function() {
++ var point = Point.read(arguments),
++ center = Point.read(arguments, 0, 0, { readNull: true });
+ if (center)
+ this.translate(center);
+ var a = this._a,
+ c = this._c;
+- this._a += ver * this._b;
+- this._c += ver * this._d;
+- this._b += hor * a;
+- this._d += hor * c;
++ this._a += point.y * this._b;
++ this._c += point.y * this._d;
++ this._b += point.x * a;
++ this._d += point.x * c;
+ if (center)
+ this.translate(center.negate());
+ return this;
+ },
+
+- toString: function() {
+- var format = Base.formatNumber;
+- return '[[' + [format(this._a), format(this._b),
+- format(this._tx)].join(', ') + '], ['
+- + [format(this._c), format(this._d),
+- format(this._ty)].join(', ') + ']]';
++ isIdentity: function() {
++ return this._a == 1 && this._c == 0 && this._b == 0 && this._d == 1
++ && this._tx == 0 && this._ty == 0;
+ },
+
+- getValues: function() {
+- return [ this._a, this._c, this._b, this._d, this._tx, this._ty ];
++ isInvertible: function() {
++ return !!this._getDeterminant();
++ },
++
++ isSingular: function() {
++ return !this._getDeterminant();
+ },
+
+ concatenate: function(mx) {
+@@ -1399,9 +1969,9 @@ var Matrix = this.Matrix = Base.extend({
+ d = this._d;
+ this._a = mx._a * a + mx._c * b;
+ this._b = mx._b * a + mx._d * b;
+- this._tx += mx._tx * a + mx._ty * b;
+ this._c = mx._a * c + mx._c * d;
+ this._d = mx._b * c + mx._d * d;
++ this._tx += mx._tx * a + mx._ty * b;
+ this._ty += mx._tx * c + mx._ty * d;
+ return this;
+ },
+@@ -1414,8 +1984,8 @@ var Matrix = this.Matrix = Base.extend({
+ tx = this._tx,
+ ty = this._ty;
+ this._a = mx._a * a + mx._b * c;
+- this._c = mx._c * a + mx._d * c;
+ this._b = mx._a * b + mx._b * d;
++ this._c = mx._c * a + mx._d * c;
+ this._d = mx._c * b + mx._d * d;
+ this._tx = mx._a * tx + mx._b * ty + mx._tx;
+ this._ty = mx._c * tx + mx._d * ty + mx._ty;
+@@ -1432,7 +2002,7 @@ var Matrix = this.Matrix = Base.extend({
+ var x = point.x,
+ y = point.y;
+ if (!dest)
+- dest = new Point(Point.dont);
++ dest = new Point();
+ return dest.set(
+ x * this._a + y * this._b + this._tx,
+ x * this._c + y * this._d + this._ty,
+@@ -1444,8 +2014,8 @@ var Matrix = this.Matrix = Base.extend({
+ var i = srcOff, j = dstOff,
+ srcEnd = srcOff + 2 * numPts;
+ while (i < srcEnd) {
+- var x = src[i++];
+- var y = src[i++];
++ var x = src[i++],
++ y = src[i++];
+ dst[j++] = x * this._a + y * this._b + this._tx;
+ dst[j++] = x * this._c + y * this._d + this._ty;
+ }
+@@ -1461,10 +2031,10 @@ var Matrix = this.Matrix = Base.extend({
+ return this._transformCoordinates(coords, 0, coords, 0, 4);
+ },
+
+- _transformBounds: function(bounds) {
++ _transformBounds: function(bounds, dest, dontNotify) {
+ var coords = this._transformCorners(bounds),
+ min = coords.slice(0, 2),
+- max = coords.slice(0);
++ max = coords.slice();
+ for (var i = 2; i < 8; i++) {
+ var val = coords[i],
+ j = i & 1;
+@@ -1473,17 +2043,19 @@ var Matrix = this.Matrix = Base.extend({
+ else if (val > max[j])
+ max[j] = val;
+ }
+- return Rectangle.create(min[0], min[1],
+- max[0] - min[0], max[1] - min[1]);
++ if (!dest)
++ dest = new Rectangle();
++ return dest.set(min[0], min[1], max[0] - min[0], max[1] - min[1],
++ dontNotify);
+ },
+
+- inverseTransform: function(point) {
++ inverseTransform: function() {
+ return this._inverseTransform(Point.read(arguments));
+ },
+
+ _getDeterminant: function() {
+ var det = this._a * this._d - this._b * this._c;
+- return isFinite(det) && Math.abs(det) > Numerical.EPSILON
++ return isFinite(det) && !Numerical.isZero(det)
+ && isFinite(this._tx) && isFinite(this._ty)
+ ? det : null;
+ },
+@@ -1495,7 +2067,7 @@ var Matrix = this.Matrix = Base.extend({
+ var x = point.x - this._tx,
+ y = point.y - this._ty;
+ if (!dest)
+- dest = new Point(Point.dont);
++ dest = new Point();
+ return dest.set(
+ (x * this._d - y * this._b) / det,
+ (y * this._a - x * this._c) / det,
+@@ -1503,39 +2075,58 @@ var Matrix = this.Matrix = Base.extend({
+ );
+ },
+
+- getTranslation: function() {
+- return new Point(this._tx, this._ty);
+- },
++ decompose: function() {
++ var a = this._a, b = this._b, c = this._c, d = this._d;
++ if (Numerical.isZero(a * d - b * c))
++ return null;
+
+- getScaling: function() {
+- var hor = Math.sqrt(this._a * this._a + this._c * this._c),
+- ver = Math.sqrt(this._b * this._b + this._d * this._d);
+- return new Point(this._a < 0 ? -hor : hor, this._b < 0 ? -ver : ver);
++ var scaleX = Math.sqrt(a * a + b * b);
++ a /= scaleX;
++ b /= scaleX;
++
++ var shear = a * c + b * d;
++ c -= a * shear;
++ d -= b * shear;
++
++ var scaleY = Math.sqrt(c * c + d * d);
++ c /= scaleY;
++ d /= scaleY;
++ shear /= scaleY;
++
++ if (a * d < b * c) {
++ a = -a;
++ b = -b;
++ shear = -shear;
++ scaleX = -scaleX;
++ }
++
++ return {
++ translation: this.getTranslation(),
++ scaling: new Point(scaleX, scaleY),
++ rotation: -Math.atan2(b, a) * 180 / Math.PI,
++ shearing: shear
++ };
+ },
+
+- getRotation: function() {
+- var angle1 = -Math.atan2(this._b, this._d),
+- angle2 = Math.atan2(this._c, this._a);
+- return Math.abs(angle1 - angle2) < Numerical.TOLERANCE
+- ? angle1 * 180 / Math.PI : undefined;
++ getValues: function() {
++ return [ this._a, this._c, this._b, this._d, this._tx, this._ty ];
+ },
+
+- isIdentity: function() {
+- return this._a == 1 && this._c == 0 && this._b == 0 && this._d == 1
+- && this._tx == 0 && this._ty == 0;
++ getTranslation: function() {
++ return new Point(this._tx, this._ty);
+ },
+
+- isInvertible: function() {
+- return !!this._getDeterminant();
++ getScaling: function() {
++ return (this.decompose() || {}).scaling;
+ },
+
+- isSingular: function() {
+- return !this._getDeterminant();
++ getRotation: function() {
++ return (this.decompose() || {}).rotation;
+ },
+
+- createInverse: function() {
++ inverted: function() {
+ var det = this._getDeterminant();
+- return det && Matrix.create(
++ return det && new Matrix(
+ this._d / det,
+ -this._c / det,
+ -this._b / det,
+@@ -1544,65 +2135,12 @@ var Matrix = this.Matrix = Base.extend({
+ (this._c * this._tx - this._a * this._ty) / det);
+ },
+
+- createShiftless: function() {
+- return Matrix.create(this._a, this._c, this._b, this._d, 0, 0);
+- },
+-
+- setToScale: function(hor, ver) {
+- return this.set(hor, 0, 0, ver, 0, 0);
+- },
+-
+- setToTranslation: function(delta) {
+- delta = Point.read(arguments);
+- return this.set(1, 0, 0, 1, delta.x, delta.y);
+- },
+-
+- setToShear: function(hor, ver) {
+- return this.set(1, ver, hor, 1, 0, 0);
+- },
+-
+- setToRotation: function(angle, center) {
+- center = Point.read(arguments, 1);
+- angle = angle * Math.PI / 180;
+- var x = center.x,
+- y = center.y,
+- cos = Math.cos(angle),
+- sin = Math.sin(angle);
+- return this.set(cos, sin, -sin, cos,
+- x - x * cos + y * sin,
+- y - x * sin - y * cos);
+- },
+-
+- applyToContext: function(ctx, reset) {
+- ctx[reset ? 'setTransform' : 'transform'](
+- this._a, this._c, this._b, this._d, this._tx, this._ty);
+- return this;
++ shiftless: function() {
++ return new Matrix(this._a, this._c, this._b, this._d, 0, 0);
+ },
+
+- statics: {
+- create: function(a, c, b, d, tx, ty) {
+- return new Matrix(Matrix.dont).set(a, c, b, d, tx, ty);
+- },
+-
+- getScaleInstance: function(hor, ver) {
+- var mx = new Matrix();
+- return mx.setToScale.apply(mx, arguments);
+- },
+-
+- getTranslateInstance: function(delta) {
+- var mx = new Matrix();
+- return mx.setToTranslation.apply(mx, arguments);
+- },
+-
+- getShearInstance: function(hor, ver, center) {
+- var mx = new Matrix();
+- return mx.setToShear.apply(mx, arguments);
+- },
+-
+- getRotateInstance: function(angle, center) {
+- var mx = new Matrix();
+- return mx.setToRotation.apply(mx, arguments);
+- }
++ applyToContext: function(ctx) {
++ ctx.transform(this._a, this._c, this._b, this._d, this._tx, this._ty);
+ }
+ }, new function() {
+ return Base.each({
+@@ -1623,75 +2161,152 @@ var Matrix = this.Matrix = Base.extend({
+ }, {});
+ });
+
+-var Line = this.Line = Base.extend({
+- initialize: function(point1, point2, infinite) {
+- point1 = Point.read(arguments, 0, 1);
+- point2 = Point.read(arguments, 1, 1);
+- if (arguments.length == 3) {
+- this.point = point1;
+- this.vector = point2.subtract(point1);
+- this.infinite = infinite;
++var Line = Base.extend({
++ _class: 'Line',
++
++ initialize: function Line(arg0, arg1, arg2, arg3, arg4) {
++ var asVector = false;
++ if (arguments.length >= 4) {
++ this._px = arg0;
++ this._py = arg1;
++ this._vx = arg2;
++ this._vy = arg3;
++ asVector = arg4;
+ } else {
+- this.point = point1;
+- this.vector = point2;
+- this.infinite = true;
++ this._px = arg0.x;
++ this._py = arg0.y;
++ this._vx = arg1.x;
++ this._vy = arg1.y;
++ asVector = arg2;
++ }
++ if (!asVector) {
++ this._vx -= this._px;
++ this._vy -= this._py;
+ }
+ },
+
+- intersect: function(line) {
+- var cross = this.vector.cross(line.vector);
+- if (Math.abs(cross) <= Numerical.EPSILON)
+- return null;
+- var v = line.point.subtract(this.point),
+- t1 = v.cross(line.vector) / cross,
+- t2 = v.cross(this.vector) / cross;
+- return (this.infinite || 0 <= t1 && t1 <= 1)
+- && (line.infinite || 0 <= t2 && t2 <= 1)
+- ? this.point.add(this.vector.multiply(t1)) : null;
++ getPoint: function() {
++ return new Point(this._px, this._py);
++ },
++
++ getVector: function() {
++ return new Point(this._vx, this._vy);
++ },
++
++ getLength: function() {
++ return this.getVector().getLength();
++ },
++
++ intersect: function(line, isInfinite) {
++ return Line.intersect(
++ this._px, this._py, this._vx, this._vy,
++ line._px, line._py, line._vx, line._vy,
++ true, isInfinite);
+ },
+
+ getSide: function(point) {
+- var v1 = this.vector,
+- v2 = point.subtract(this.point),
+- ccw = v2.cross(v1);
+- if (ccw == 0) {
+- ccw = v2.dot(v1);
+- if (ccw > 0) {
+- ccw = v2.subtract(v1).dot(v1);
+- if (ccw < 0)
+- ccw = 0;
+- }
+- }
+- return ccw < 0 ? -1 : ccw > 0 ? 1 : 0;
++ return Line.getSide(
++ this._px, this._py, this._vx, this._vy,
++ point.x, point.y, true);
+ },
+
+ getDistance: function(point) {
+- var m = this.vector.y / this.vector.x,
+- b = this.point.y - (m * this.point.x);
+- var dist = Math.abs(point.y - (m * point.x) - b) / Math.sqrt(m * m + 1);
+- return this.infinite ? dist : Math.min(dist,
+- point.getDistance(this.point),
+- point.getDistance(this.point.add(this.vector)));
++ return Math.abs(Line.getSignedDistance(
++ this._px, this._py, this._vx, this._vy,
++ point.x, point.y, true));
++ },
++
++ statics: {
++ intersect: function(apx, apy, avx, avy, bpx, bpy, bvx, bvy, asVector,
++ isInfinite) {
++ if (!asVector) {
++ avx -= apx;
++ avy -= apy;
++ bvx -= bpx;
++ bvy -= bpy;
++ }
++ var cross = bvy * avx - bvx * avy;
++ if (!Numerical.isZero(cross)) {
++ var dx = apx - bpx,
++ dy = apy - bpy,
++ ta = (bvx * dy - bvy * dx) / cross,
++ tb = (avx * dy - avy * dx) / cross;
++ if ((isInfinite || 0 <= ta && ta <= 1)
++ && (isInfinite || 0 <= tb && tb <= 1))
++ return new Point(
++ apx + ta * avx,
++ apy + ta * avy);
++ }
++ },
++
++ getSide: function(px, py, vx, vy, x, y, asVector) {
++ if (!asVector) {
++ vx -= px;
++ vy -= py;
++ }
++ var v2x = x - px,
++ v2y = y - py,
++ ccw = v2x * vy - v2y * vx;
++ if (ccw === 0) {
++ ccw = v2x * vx + v2y * vy;
++ if (ccw > 0) {
++ v2x -= vx;
++ v2y -= vy;
++ ccw = v2x * vx + v2y * vy;
++ if (ccw < 0)
++ ccw = 0;
++ }
++ }
++ return ccw < 0 ? -1 : ccw > 0 ? 1 : 0;
++ },
++
++ getSignedDistance: function(px, py, vx, vy, x, y, asVector) {
++ if (!asVector) {
++ vx -= px;
++ vy -= py;
++ }
++ var m = vy / vx,
++ b = py - m * px;
++ return (y - (m * x) - b) / Math.sqrt(m * m + 1);
++ }
+ }
+ });
+
+-var Project = this.Project = PaperScopeItem.extend({
++var Project = PaperScopeItem.extend({
++ _class: 'Project',
+ _list: 'projects',
+ _reference: 'project',
+
+- initialize: function() {
+- this.base(true);
+- this._currentStyle = new PathStyle();
+- this._selectedItems = {};
+- this._selectedItemCount = 0;
++ initialize: function Project(view) {
++ PaperScopeItem.call(this, true);
+ this.layers = [];
+ this.symbols = [];
++ this._currentStyle = new Style();
+ this.activeLayer = new Layer();
++ if (view)
++ this.view = view instanceof View ? view : View.create(view);
++ this._selectedItems = {};
++ this._selectedItemCount = 0;
++ this._drawCount = 0;
++ this.options = {};
++ },
++
++ _serialize: function(options, dictionary) {
++ return Base.serialize(this.layers, options, true, dictionary);
++ },
++
++ clear: function() {
++ for (var i = this.layers.length - 1; i >= 0; i--)
++ this.layers[i].remove();
++ this.symbols = [];
+ },
+
+- _needsRedraw: function() {
+- if (this._scope)
+- this._scope._needsRedraw();
++ remove: function remove() {
++ if (!remove.base.call(this))
++ return false;
++ if (this.view)
++ this.view.remove();
++ return true;
+ },
+
+ getCurrentStyle: function() {
+@@ -1708,19 +2323,23 @@ var Project = this.Project = PaperScopeItem.extend({
+
+ getSelectedItems: function() {
+ var items = [];
+- Base.each(this._selectedItems, function(item) {
+- items.push(item);
+- });
++ for (var id in this._selectedItems) {
++ var item = this._selectedItems[id];
++ if (item._drawCount === this._drawCount)
++ items.push(item);
++ }
+ return items;
+ },
+
+ _updateSelection: function(item) {
+ if (item._selected) {
+ this._selectedItemCount++;
+- this._selectedItems[item.getId()] = item;
++ this._selectedItems[item._id] = item;
++ if (item.isInserted())
++ item._drawCount = this._drawCount;
+ } else {
+ this._selectedItemCount--;
+- delete this._selectedItems[item.getId()];
++ delete this._selectedItems[item._id];
+ }
+ },
+
+@@ -1735,8 +2354,8 @@ var Project = this.Project = PaperScopeItem.extend({
+ },
+
+ hitTest: function(point, options) {
+- options = HitResult.getOptions(point, options);
+- point = options.point;
++ point = Point.read(arguments);
++ options = HitResult.getOptions(Base.read(arguments));
+ for (var i = this.layers.length - 1; i >= 0; i--) {
+ var res = this.layers[i].hitTest(point, options);
+ if (res) return res;
+@@ -1744,34 +2363,78 @@ var Project = this.Project = PaperScopeItem.extend({
+ return null;
+ },
+
+- draw: function(ctx) {
++ importJSON: function(json) {
++ this.activate();
++ return Base.importJSON(json);
++ },
++
++ draw: function(ctx, matrix) {
++ this._drawCount++;
+ ctx.save();
+- var param = { offset: new Point(0, 0) };
++ matrix.applyToContext(ctx);
++ var param = Base.merge({
++ offset: new Point(0, 0),
++ transforms: [matrix]
++ });
+ for (var i = 0, l = this.layers.length; i < l; i++)
+- Item.draw(this.layers[i], ctx, param);
++ this.layers[i].draw(ctx, param);
+ ctx.restore();
+
+ if (this._selectedItemCount > 0) {
+ ctx.save();
+ ctx.strokeWidth = 1;
+- ctx.strokeStyle = ctx.fillStyle = '#009dec';
+- param = { selection: true };
+- Base.each(this._selectedItems, function(item) {
+- item.draw(ctx, param);
+- });
++ for (var id in this._selectedItems) {
++ var item = this._selectedItems[id];
++ if (item._drawCount === this._drawCount
++ && (item._drawSelected || item._boundsSelected)) {
++ var color = item.getSelectedColor()
++ || item.getLayer().getSelectedColor();
++ ctx.strokeStyle = ctx.fillStyle = color
++ ? color.toCanvasStyle(ctx) : '#009dec';
++ var mx = item._globalMatrix;
++ if (item._drawSelected)
++ item._drawSelected(ctx, mx);
++ if (item._boundsSelected) {
++ var coords = mx._transformCorners(
++ item._getBounds('getBounds'));
++ ctx.beginPath();
++ for (var i = 0; i < 8; i++)
++ ctx[i === 0 ? 'moveTo' : 'lineTo'](
++ coords[i], coords[++i]);
++ ctx.closePath();
++ ctx.stroke();
++ for (var i = 0; i < 8; i++) {
++ ctx.beginPath();
++ ctx.rect(coords[i] - 2, coords[++i] - 2, 4, 4);
++ ctx.fill();
++ }
++ }
++ }
++ }
+ ctx.restore();
+ }
+ }
+ });
+
+-var Symbol = this.Symbol = Base.extend({
+- initialize: function(item) {
++var Symbol = Base.extend({
++ _class: 'Symbol',
++
++ initialize: function Symbol(item, dontCenter) {
++ this._id = Symbol._id = (Symbol._id || 0) + 1;
+ this.project = paper.project;
+ this.project.symbols.push(this);
+- this.setDefinition(item);
++ if (item)
++ this.setDefinition(item, dontCenter);
+ this._instances = {};
+ },
+
++ _serialize: function(options, dictionary) {
++ return dictionary.add(this, function() {
++ return Base.serialize([this._class, this._definition],
++ options, false, dictionary);
++ });
++ },
++
+ _changed: function(flags) {
+ Base.each(this._instances, function(item) {
+ item._changed(flags);
+@@ -1782,16 +2445,18 @@ var Symbol = this.Symbol = Base.extend({
+ return this._definition;
+ },
+
+- setDefinition: function(item) {
++ setDefinition: function(item ) {
+ if (item._parentSymbol)
+ item = item.clone();
+ if (this._definition)
+ delete this._definition._parentSymbol;
+ this._definition = item;
+ item.remove();
+- item.setPosition(new Point());
++ item.setSelected(false);
++ if (!arguments[1])
++ item.setPosition(new Point());
+ item._parentSymbol = this;
+- this._changed(Change.GEOMETRY);
++ this._changed(5);
+ },
+
+ place: function(position) {
+@@ -1799,93 +2464,209 @@ var Symbol = this.Symbol = Base.extend({
+ },
+
+ clone: function() {
+- return new Symbol(this._definition.clone());
++ return new Symbol(this._definition.clone(false));
+ }
+ });
+
+-var ChangeFlag = {
+- APPEARANCE: 1,
+- HIERARCHY: 2,
+- GEOMETRY: 4,
+- STROKE: 8,
+- STYLE: 16,
+- ATTRIBUTE: 32,
+- CONTENT: 64,
+- PIXELS: 128,
+- CLIPPING: 256
+-};
++var Item = Base.extend(Callback, {
++ statics: {
++ extend: function extend(src) {
++ if (src._serializeFields)
++ src._serializeFields = Base.merge(
++ this.prototype._serializeFields, src._serializeFields);
++ var res = extend.base.apply(this, arguments),
++ proto = res.prototype,
++ name = proto._class;
++ if (name)
++ proto._type = Base.hyphenate(name);
++ return res;
++ }
++ },
+
+-var Change = {
+- HIERARCHY: ChangeFlag.HIERARCHY | ChangeFlag.APPEARANCE,
+- GEOMETRY: ChangeFlag.GEOMETRY | ChangeFlag.APPEARANCE,
+- STROKE: ChangeFlag.STROKE | ChangeFlag.STYLE | ChangeFlag.APPEARANCE,
+- STYLE: ChangeFlag.STYLE | ChangeFlag.APPEARANCE,
+- ATTRIBUTE: ChangeFlag.ATTRIBUTE | ChangeFlag.APPEARANCE,
+- CONTENT: ChangeFlag.CONTENT | ChangeFlag.APPEARANCE,
+- PIXELS: ChangeFlag.PIXELS | ChangeFlag.APPEARANCE
+-};
++ _class: 'Item',
++ _transformContent: true,
++ _boundsSelected: false,
++ _serializeFields: {
++ name: null,
++ matrix: new Matrix(),
++ locked: false,
++ visible: true,
++ blendMode: 'normal',
++ opacity: 1,
++ guide: false,
++ clipMask: false,
++ data: {}
++ },
+
+-var Item = this.Item = Base.extend({
+- initialize: function() {
+- this._id = ++Item._id;
+- if (!this._project)
+- paper.project.activeLayer.addChild(this);
+- this._style = PathStyle.create(this);
+- this.setStyle(this._project.getCurrentStyle());
++ initialize: function Item() {
++ },
++
++ _initialize: function(props, point) {
++ this._id = Item._id = (Item._id || 0) + 1;
++ if (!this._project) {
++ var project = paper.project,
++ layer = project.activeLayer;
++ if (layer && !(props && props.insert === false)) {
++ layer.addChild(this);
++ } else {
++ this._setProject(project);
++ }
++ }
++ this._style = new Style(this._project._currentStyle, this);
++ this._matrix = new Matrix();
++ if (point)
++ this._matrix.translate(point);
++ return props ? this._set(props, { insert: true }) : true;
++ },
++
++ _events: new function() {
++
++ var mouseFlags = {
++ mousedown: {
++ mousedown: 1,
++ mousedrag: 1,
++ click: 1,
++ doubleclick: 1
++ },
++ mouseup: {
++ mouseup: 1,
++ mousedrag: 1,
++ click: 1,
++ doubleclick: 1
++ },
++ mousemove: {
++ mousedrag: 1,
++ mousemove: 1,
++ mouseenter: 1,
++ mouseleave: 1
++ }
++ };
++
++ var mouseEvent = {
++ install: function(type) {
++ var counters = this._project.view._eventCounters;
++ if (counters) {
++ for (var key in mouseFlags) {
++ counters[key] = (counters[key] || 0)
++ + (mouseFlags[key][type] || 0);
++ }
++ }
++ },
++ uninstall: function(type) {
++ var counters = this._project.view._eventCounters;
++ if (counters) {
++ for (var key in mouseFlags)
++ counters[key] -= mouseFlags[key][type] || 0;
++ }
++ }
++ };
++
++ return Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
++ 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'],
++ function(name) {
++ this[name] = mouseEvent;
++ }, {
++ onFrame: {
++ install: function() {
++ this._project.view._animateItem(this, true);
++ },
++ uninstall: function() {
++ this._project.view._animateItem(this, false);
++ }
++ },
++
++ onLoad: {}
++ }
++ );
++ },
++
++ _serialize: function(options, dictionary) {
++ var props = {},
++ that = this;
++
++ function serialize(fields) {
++ for (var key in fields) {
++ var value = that[key];
++ if (!Base.equals(value, fields[key]))
++ props[key] = Base.serialize(value, options,
++ key !== 'data', dictionary);
++ }
++ }
++
++ serialize(this._serializeFields);
++ if (!(this instanceof Group))
++ serialize(this._style._defaults);
++ return [ this._class, props ];
+ },
+
+ _changed: function(flags) {
+- if (flags & ChangeFlag.GEOMETRY) {
++ var parent = this._parent,
++ project = this._project,
++ symbol = this._parentSymbol;
++ if (flags & 4) {
+ delete this._bounds;
+ delete this._position;
+ }
+- if (flags & ChangeFlag.APPEARANCE) {
+- this._project._needsRedraw();
++ if (parent && (flags
++ & (4 | 8))) {
++ parent._clearBoundsCache();
+ }
+- if (this._parentSymbol)
+- this._parentSymbol._changed(flags);
+- if (this._project._changes) {
+- var entry = this._project._changesById[this._id];
+- if (entry) {
+- entry.flags |= flags;
+- } else {
+- entry = { item: this, flags: flags };
+- this._project._changesById[this._id] = entry;
+- this._project._changes.push(entry);
++ if (flags & 2) {
++ this._clearBoundsCache();
++ }
++ if (project) {
++ if (flags & 1) {
++ project._needsRedraw = true;
++ }
++ if (project._changes) {
++ var entry = project._changesById[this._id];
++ if (entry) {
++ entry.flags |= flags;
++ } else {
++ entry = { item: this, flags: flags };
++ project._changesById[this._id] = entry;
++ project._changes.push(entry);
++ }
+ }
+ }
++ if (symbol)
++ symbol._changed(flags);
++ },
++
++ set: function(props) {
++ if (props)
++ this._set(props);
++ return this;
+ },
+
+ getId: function() {
+ return this._id;
+ },
+
++ getType: function() {
++ return this._type;
++ },
++
+ getName: function() {
+ return this._name;
+ },
+
+- setName: function(name) {
++ setName: function(name, unique) {
+
+ if (this._name)
+ this._removeFromNamed();
+- this._name = name || undefined;
+- if (name) {
++ if (name && this._parent) {
+ var children = this._parent._children,
+- namedChildren = this._parent._namedChildren;
++ namedChildren = this._parent._namedChildren,
++ orig = name,
++ i = 1;
++ while (unique && children[name])
++ name = orig + ' ' + (i++);
+ (namedChildren[name] = namedChildren[name] || []).push(this);
+ children[name] = this;
+ }
+- this._changed(ChangeFlag.ATTRIBUTE);
+- },
+-
+- getPosition: function() {
+- var pos = this._position
+- || (this._position = this.getBounds().getCenter());
+- return LinkedPoint.create(this, 'setPosition', pos._x, pos._y);
+- },
+-
+- setPosition: function(point) {
+- this.translate(Point.read(arguments).subtract(this.getPosition()));
++ this._name = name || undefined;
++ this._changed(32);
+ },
+
+ getStyle: function() {
+@@ -1893,29 +2674,32 @@ var Item = this.Item = Base.extend({
+ },
+
+ setStyle: function(style) {
+- this._style.initialize(style);
++ this.getStyle().set(style);
+ },
+
+- statics: {
+- _id: 0
++ hasFill: function() {
++ return !!this.getStyle().getFillColor();
++ },
++
++ hasStroke: function() {
++ var style = this.getStyle();
++ return !!style.getStrokeColor() && style.getStrokeWidth() > 0;
+ }
+-}, new function() {
+- return Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'],
+- function(name) {
+- var part = Base.capitalize(name),
+- name = '_' + name;
+- this['get' + part] = function() {
+- return this[name];
+- };
+- this['set' + part] = function(value) {
+- if (value != this[name]) {
+- this[name] = value;
+- this._changed(name === '_locked'
+- ? ChangeFlag.ATTRIBUTE : Change.ATTRIBUTE);
+- }
+- };
+- }, {});
+-}, {
++}, Base.each(['locked', 'visible', 'blendMode', 'opacity', 'guide'],
++ function(name) {
++ var part = Base.capitalize(name),
++ name = '_' + name;
++ this['get' + part] = function() {
++ return this[name];
++ };
++ this['set' + part] = function(value) {
++ if (value != this[name]) {
++ this[name] = value;
++ this._changed(name === '_locked'
++ ? 32 : 33);
++ }
++ };
++}, {}), {
+
+ _locked: false,
+
+@@ -1936,15 +2720,15 @@ var Item = this.Item = Base.extend({
+ return this._selected;
+ },
+
+- setSelected: function(selected) {
+- if (this._children) {
+- for (var i = 0, l = this._children.length; i < l; i++) {
++ setSelected: function(selected ) {
++ if (this._children && !arguments[1]) {
++ for (var i = 0, l = this._children.length; i < l; i++)
+ this._children[i].setSelected(selected);
+- }
+- } else if ((selected = !!selected) != this._selected) {
++ }
++ if ((selected = !!selected) != this._selected) {
+ this._selected = selected;
+ this._project._updateSelection(this);
+- this._changed(Change.ATTRIBUTE);
++ this._changed(33);
+ }
+ },
+
+@@ -1962,11 +2746,10 @@ var Item = this.Item = Base.extend({
+
+ setFullySelected: function(selected) {
+ if (this._children) {
+- for (var i = 0, l = this._children.length; i < l; i++) {
++ for (var i = 0, l = this._children.length; i < l; i++)
+ this._children[i].setFullySelected(selected);
+- }
+ }
+- this.setSelected(selected);
++ this.setSelected(selected, true);
+ },
+
+ isClipMask: function() {
+@@ -1980,14 +2763,142 @@ var Item = this.Item = Base.extend({
+ this.setFillColor(null);
+ this.setStrokeColor(null);
+ }
+- this._changed(Change.ATTRIBUTE);
++ this._changed(33);
+ if (this._parent)
+- this._parent._changed(ChangeFlag.CLIPPING);
++ this._parent._changed(256);
+ }
+ },
+
+ _clipMask: false,
+
++ getData: function() {
++ if (!this._data)
++ this._data = {};
++ return this._data;
++ },
++
++ setData: function(data) {
++ this._data = data;
++ },
++
++ getPosition: function() {
++ var pos = this._position
++ || (this._position = this.getBounds().getCenter(true));
++ return new (arguments[0] ? Point : LinkedPoint)
++ (pos.x, pos.y, this, 'setPosition');
++ },
++
++ setPosition: function() {
++ this.translate(Point.read(arguments).subtract(this.getPosition(true)));
++ },
++
++ getMatrix: function() {
++ return this._matrix;
++ },
++
++ setMatrix: function(matrix) {
++ this._matrix.initialize(matrix);
++ this._changed(5);
++ },
++
++ isEmpty: function() {
++ return this._children.length == 0;
++ }
++}, Base.each(['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'],
++ function(name) {
++ this[name] = function() {
++ var getter = this._boundsGetter,
++ bounds = this._getCachedBounds(typeof getter == 'string'
++ ? getter : getter && getter[name] || name, arguments[0]);
++ return name === 'getBounds'
++ ? new LinkedRectangle(bounds.x, bounds.y, bounds.width,
++ bounds.height, this, 'setBounds')
++ : bounds;
++ };
++ },
++{
++ _getCachedBounds: function(getter, matrix, cacheItem) {
++ var cache = (!matrix || matrix.equals(this._matrix)) && getter;
++ if (cacheItem && this._parent) {
++ var id = cacheItem._id,
++ ref = this._parent._boundsCache
++ = this._parent._boundsCache || {
++ ids: {},
++ list: []
++ };
++ if (!ref.ids[id]) {
++ ref.list.push(cacheItem);
++ ref.ids[id] = cacheItem;
++ }
++ }
++ if (cache && this._bounds && this._bounds[cache])
++ return this._bounds[cache].clone();
++ var identity = this._matrix.isIdentity();
++ matrix = !matrix || matrix.isIdentity()
++ ? identity ? null : this._matrix
++ : identity ? matrix : matrix.clone().concatenate(this._matrix);
++ var bounds = this._getBounds(getter, matrix, cache ? this : cacheItem);
++ if (cache) {
++ if (!this._bounds)
++ this._bounds = {};
++ this._bounds[cache] = bounds.clone();
++ }
++ return bounds;
++ },
++
++ _clearBoundsCache: function() {
++ if (this._boundsCache) {
++ for (var i = 0, list = this._boundsCache.list, l = list.length;
++ i < l; i++) {
++ var item = list[i];
++ delete item._bounds;
++ if (item != this && item._boundsCache)
++ item._clearBoundsCache();
++ }
++ delete this._boundsCache;
++ }
++ },
++
++ _getBounds: function(getter, matrix, cacheItem) {
++ var children = this._children;
++ if (!children || children.length == 0)
++ return new Rectangle();
++ var x1 = Infinity,
++ x2 = -x1,
++ y1 = x1,
++ y2 = x2;
++ for (var i = 0, l = children.length; i < l; i++) {
++ var child = children[i];
++ if (child._visible && !child.isEmpty()) {
++ var rect = child._getCachedBounds(getter, matrix, cacheItem);
++ x1 = Math.min(rect.x, x1);
++ y1 = Math.min(rect.y, y1);
++ x2 = Math.max(rect.x + rect.width, x2);
++ y2 = Math.max(rect.y + rect.height, y2);
++ }
++ }
++ return isFinite(x1)
++ ? new Rectangle(x1, y1, x2 - x1, y2 - y1)
++ : new Rectangle();
++ },
++
++ setBounds: function(rect) {
++ rect = Rectangle.read(arguments);
++ var bounds = this.getBounds(),
++ matrix = new Matrix(),
++ center = rect.getCenter();
++ matrix.translate(center);
++ if (rect.width != bounds.width || rect.height != bounds.height) {
++ matrix.scale(
++ bounds.width != 0 ? rect.width / bounds.width : 1,
++ bounds.height != 0 ? rect.height / bounds.height : 1);
++ }
++ center = bounds.getCenter();
++ matrix.translate(-center.x, -center.y);
++ this.transform(matrix);
++ }
++
++}), {
+ getProject: function() {
+ return this._project;
+ },
+@@ -2016,6 +2927,10 @@ var Item = this.Item = Base.extend({
+ return this._parent;
+ },
+
++ setParent: function(item) {
++ return item.addChild(this);
++ },
++
+ getChildren: function() {
+ return this._children;
+ },
+@@ -2046,16 +2961,22 @@ var Item = this.Item = Base.extend({
+ return this._index;
+ },
+
+- clone: function() {
+- return this._clone(new this.constructor());
++ isInserted: function() {
++ return this._parent ? this._parent.isInserted() : false;
++ },
++
++ clone: function(insert) {
++ return this._clone(new this.constructor({ insert: false }), insert);
+ },
+
+- _clone: function(copy) {
++ _clone: function(copy, insert) {
+ copy.setStyle(this._style);
+ if (this._children) {
+ for (var i = 0, l = this._children.length; i < l; i++)
+- copy.addChild(this._children[i].clone());
++ copy.addChild(this._children[i].clone(false), true);
+ }
++ if (insert || insert === undefined)
++ copy.insertAbove(this);
+ var keys = ['_locked', '_visible', '_blendMode', '_opacity',
+ '_clipMask', '_guide'];
+ for (var i = 0, l = keys.length; i < l; i++) {
+@@ -2063,9 +2984,10 @@ var Item = this.Item = Base.extend({
+ if (this.hasOwnProperty(key))
+ copy[key] = this[key];
+ }
++ copy._matrix.initialize(this._matrix);
+ copy.setSelected(this._selected);
+ if (this._name)
+- copy.setName(this._name);
++ copy.setName(this._name, true);
+ return copy;
+ },
+
+@@ -2082,113 +3004,169 @@ var Item = this.Item = Base.extend({
+ rasterize: function(resolution) {
+ var bounds = this.getStrokeBounds(),
+ scale = (resolution || 72) / 72,
+- canvas = CanvasProvider.getCanvas(bounds.getSize().multiply(scale)),
++ topLeft = bounds.getTopLeft().floor(),
++ bottomRight = bounds.getBottomRight().ceil()
++ size = new Size(bottomRight.subtract(topLeft)),
++ canvas = CanvasProvider.getCanvas(size),
+ ctx = canvas.getContext('2d'),
+- matrix = new Matrix().scale(scale).translate(-bounds.x, -bounds.y);
++ matrix = new Matrix().scale(scale).translate(topLeft.negate());
++ ctx.save();
+ matrix.applyToContext(ctx);
+- this.draw(ctx, {});
++ this.draw(ctx, Base.merge({ transforms: [matrix] }));
+ var raster = new Raster(canvas);
+- raster.setBounds(bounds);
++ raster.setPosition(topLeft.add(size.divide(2)));
++ ctx.restore();
+ return raster;
+ },
+
+- hitTest: function(point, options, matrix) {
+- options = HitResult.getOptions(point, options);
+- point = options.point;
+- if (!this._children && !this.getRoughBounds(matrix)
++ contains: function() {
++ return !!this._contains(
++ this._matrix._inverseTransform(Point.read(arguments)));
++ },
++
++ _contains: function(point) {
++ if (this._children) {
++ for (var i = this._children.length - 1; i >= 0; i--) {
++ if (this._children[i].contains(point))
++ return true;
++ }
++ return false;
++ }
++ return point.isInside(this._getBounds('getBounds'));
++ },
++
++ hitTest: function(point, options) {
++ point = Point.read(arguments);
++ options = HitResult.getOptions(Base.read(arguments));
++
++ if (this._locked || !this._visible || this._guide && !options.guides)
++ return null;
++
++ if (!this._children && !this.getRoughBounds()
+ .expand(options.tolerance)._containsPoint(point))
+ return null;
++ point = this._matrix._inverseTransform(point);
++
++ var that = this,
++ res;
++ function checkBounds(type, part) {
++ var pt = bounds['get' + part]();
++ if (point.getDistance(pt) < options.tolerance)
++ return new HitResult(type, that,
++ { name: Base.hyphenate(part), point: pt });
++ }
++
+ if ((options.center || options.bounds) &&
+ !(this instanceof Layer && !this._parent)) {
+- var bounds = this.getBounds(),
+- that = this,
+- points = ['TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',
+- 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'],
+- res;
+- function checkBounds(type, part) {
+- var pt = bounds['get' + part]().transform(matrix);
+- if (point.getDistance(pt) < options.tolerance)
+- return new HitResult(type, that,
+- { name: Base.hyphenate(part), point: pt });
+- }
+- if (options.center && (res = checkBounds('center', 'Center')))
+- return res;
+- if (options.bounds) {
+- for (var i = 0; i < 8; i++)
+- if (res = checkBounds('bounds', points[i]))
+- return res;
++ var bounds = this._getBounds('getBounds');
++ if (options.center)
++ res = checkBounds('center', 'Center');
++ if (!res && options.bounds) {
++ var points = [
++ 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',
++ 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'
++ ];
++ for (var i = 0; i < 8 && !res; i++)
++ res = checkBounds('bounds', points[i]);
+ }
+ }
+
+- return this._children || !(options.guides && !this._guide
++ if ((res || (res = this._children || !(options.guides && !this._guide
+ || options.selected && !this._selected)
+- ? this._hitTest(point, options, matrix) : null;
++ ? this._hitTest(point, options) : null))
++ && res.point) {
++ res.point = that._matrix.transform(res.point);
++ }
++ return res;
+ },
+
+- _hitTest: function(point, options, matrix) {
++ _hitTest: function(point, options) {
+ if (this._children) {
+- for (var i = this._children.length - 1; i >= 0; i--) {
+- var res = this._children[i].hitTest(point, options, matrix);
+- if (res) return res;
+- }
++ for (var i = this._children.length - 1, res; i >= 0; i--)
++ if (res = this._children[i].hitTest(point, options))
++ return res;
++ } else if (options.fill && this.hasFill() && this._contains(point)) {
++ return new HitResult('fill', this);
+ }
+ },
+
+- addChild: function(item) {
+- return this.insertChild(undefined, item);
++ importJSON: function(json) {
++ return this.addChild(Base.importJSON(json));
+ },
+
+- insertChild: function(index, item) {
+- if (this._children) {
+- item._remove(false, true);
+- Base.splice(this._children, [item], index, 0);
+- item._parent = this;
+- item._setProject(this._project);
+- if (item._name)
+- item.setName(item._name);
+- this._changed(Change.HIERARCHY);
+- return true;
+- }
+- return false;
++ addChild: function(item, _preserve) {
++ return this.insertChild(undefined, item, _preserve);
++ },
++
++ insertChild: function(index, item, _preserve) {
++ var res = this.insertChildren(index, [item], _preserve);
++ return res && res[0];
+ },
+
+- addChildren: function(items) {
+- for (var i = 0, l = items && items.length; i < l; i++)
+- this.insertChild(undefined, items[i]);
++ addChildren: function(items, _preserve) {
++ return this.insertChildren(this._children.length, items, _preserve);
+ },
+
+- insertChildren: function(index, items) {
+- for (var i = 0, l = items && items.length; i < l; i++) {
+- if (this.insertChild(index, items[i]))
+- index++;
++ insertChildren: function(index, items, _preserve, _type) {
++ var children = this._children;
++ if (children && items && items.length > 0) {
++ items = Array.prototype.slice.apply(items);
++ for (var i = items.length - 1; i >= 0; i--) {
++ var item = items[i];
++ if (_type && item._type !== _type)
++ items.splice(i, 1);
++ else
++ item._remove(true);
++ }
++ Base.splice(children, items, index, 0);
++ for (var i = 0, l = items.length; i < l; i++) {
++ var item = items[i];
++ item._parent = this;
++ item._setProject(this._project);
++ if (item._name)
++ item.setName(item._name);
++ }
++ this._changed(7);
++ } else {
++ items = null;
+ }
++ return items;
+ },
+
+- insertAbove: function(item) {
+- return item._parent && item._parent.insertChild(
+- item._index + 1, this);
++ _insert: function(above, item, _preserve) {
++ if (!item._parent)
++ return null;
++ var index = item._index + (above ? 1 : 0);
++ if (item._parent === this._parent && index > this._index)
++ index--;
++ return item._parent.insertChild(index, this, _preserve);
++ },
++
++ insertAbove: function(item, _preserve) {
++ return this._insert(true, item, _preserve);
+ },
+
+- insertBelow: function(item) {
+- return item._parent && item._parent.insertChild(
+- item._index - 1, this);
++ insertBelow: function(item, _preserve) {
++ return this._insert(false, item, _preserve);
++ },
++
++ sendToBack: function() {
++ return this._parent.insertChild(0, this);
+ },
+
+- appendTop: function(item) {
+- return this.addChild(item);
++ bringToFront: function() {
++ return this._parent.addChild(this);
+ },
+
++ appendTop: '#addChild',
++
+ appendBottom: function(item) {
+ return this.insertChild(0, item);
+ },
+
+- moveAbove: function(item) {
+- return this.insertAbove(item);
+- },
++ moveAbove: '#insertAbove',
+
+- moveBelow: function(item) {
+- return this.insertBelow(item);
+- },
++ moveBelow: '#insertBelow',
+
+ _removeFromNamed: function() {
+ var children = this._parent._children,
+@@ -2208,15 +3186,14 @@ var Item = this.Item = Base.extend({
+ }
+ },
+
+- _remove: function(deselect, notify) {
++ _remove: function(notify) {
+ if (this._parent) {
+- if (deselect)
+- this.setSelected(false);
+ if (this._name)
+ this._removeFromNamed();
+- Base.splice(this._parent._children, null, this._index, 1);
++ if (this._index != null)
++ Base.splice(this._parent._children, null, this._index, 1);
+ if (notify)
+- this._parent._changed(Change.HIERARCHY);
++ this._parent._changed(7);
+ this._parent = null;
+ return true;
+ }
+@@ -2224,19 +3201,19 @@ var Item = this.Item = Base.extend({
+ },
+
+ remove: function() {
+- return this._remove(true, true);
++ return this._remove(true);
+ },
+
+ removeChildren: function(from, to) {
+ if (!this._children)
+ return null;
+ from = from || 0;
+- to = Base.pick(to, this._children.length);
+- var removed = this._children.splice(from, to - from);
++ to = Base.pick(to, this._children.length);
++ var removed = Base.splice(this._children, null, from, to - from);
+ for (var i = removed.length - 1; i >= 0; i--)
+- removed[i]._remove(true, false);
++ removed[i]._remove(false);
+ if (removed.length > 0)
+- this._changed(Change.HIERARCHY);
++ this._changed(7);
+ return removed;
+ },
+
+@@ -2245,7 +3222,7 @@ var Item = this.Item = Base.extend({
+ this._children.reverse();
+ for (var i = 0, l = this._children.length; i < l; i++)
+ this._children[i]._index = i;
+- this._changed(Change.HIERARCHY);
++ this._changed(7);
+ }
+ },
+
+@@ -2264,7 +3241,7 @@ var Item = this.Item = Base.extend({
+ var list = [];
+ do {
+ list.unshift(item);
+- } while (item = item._parent)
++ } while (item = item._parent);
+ return list;
+ }
+ var list1 = getList(this),
+@@ -2282,19 +3259,19 @@ var Item = this.Item = Base.extend({
+ },
+
+ isAbove: function(item) {
+- return this._getOrder(item) == -1;
++ return this._getOrder(item) === -1;
+ },
+
+ isBelow: function(item) {
+- return this._getOrder(item) == 1;
++ return this._getOrder(item) === 1;
+ },
+
+ isParent: function(item) {
+- return this._parent == item;
++ return this._parent === item;
+ },
+
+ isChild: function(item) {
+- return item && item._parent == this;
++ return item && item._parent === this;
+ },
+
+ isDescendant: function(item) {
+@@ -2314,7 +3291,7 @@ var Item = this.Item = Base.extend({
+ var parent = this._parent;
+ while (parent) {
+ if (parent._parent
+- && (parent instanceof Group || parent instanceof CompoundPath)
++ && /^(group|layer|compound-path)$/.test(parent._type)
+ && item.isDescendant(parent))
+ return true;
+ parent = parent._parent;
+@@ -2322,82 +3299,23 @@ var Item = this.Item = Base.extend({
+ return false;
+ },
+
+- _getBounds: function(getter, cacheName, args) {
+- var children = this._children;
+- if (!children || children.length == 0)
+- return new Rectangle();
+- var x1 = Infinity,
+- x2 = -x1,
+- y1 = x1,
+- y2 = x2;
+- for (var i = 0, l = children.length; i < l; i++) {
+- var child = children[i];
+- if (child._visible) {
+- var rect = child[getter](args[0]);
+- x1 = Math.min(rect.x, x1);
+- y1 = Math.min(rect.y, y1);
+- x2 = Math.max(rect.x + rect.width, x2);
+- y2 = Math.max(rect.y + rect.height, y2);
+- }
+- }
+- var bounds = Rectangle.create(x1, y1, x2 - x1, y2 - y1);
+- return getter == 'getBounds' ? this._createBounds(bounds) : bounds;
+- },
+-
+- _createBounds: function(rect) {
+- return LinkedRectangle.create(this, 'setBounds',
+- rect.x, rect.y, rect.width, rect.height);
+- },
+-
+- getBounds: function() {
+- return this._getBounds('getBounds', '_bounds', arguments);
+- },
+-
+- setBounds: function(rect) {
+- rect = Rectangle.read(arguments);
+- var bounds = this.getBounds(),
+- matrix = new Matrix(),
+- center = rect.getCenter();
+- matrix.translate(center);
+- if (rect.width != bounds.width || rect.height != bounds.height) {
+- matrix.scale(
+- bounds.width != 0 ? rect.width / bounds.width : 1,
+- bounds.height != 0 ? rect.height / bounds.height : 1);
+- }
+- center = bounds.getCenter();
+- matrix.translate(-center.x, -center.y);
+- this.transform(matrix);
+- },
+-
+- getStrokeBounds: function() {
+- return this._getBounds('getStrokeBounds', '_strokeBounds', arguments);
+- },
+-
+- getHandleBounds: function() {
+- return this._getBounds('getHandleBounds', '_handleBounds', arguments);
+- },
+-
+- getRoughBounds: function() {
+- return this._getBounds('getRoughBounds', '_roughBounds', arguments);
+- },
+-
+ scale: function(hor, ver , center) {
+ if (arguments.length < 2 || typeof ver === 'object') {
+ center = ver;
+ ver = hor;
+ }
+ return this.transform(new Matrix().scale(hor, ver,
+- center || this.getPosition()));
++ center || this.getPosition(true)));
+ },
+
+- translate: function(delta) {
++ translate: function() {
+ var mx = new Matrix();
+ return this.transform(mx.translate.apply(mx, arguments));
+ },
+
+ rotate: function(angle, center) {
+ return this.transform(new Matrix().rotate(angle,
+- center || this.getPosition()));
++ center || this.getPosition(true)));
+ },
+
+ shear: function(hor, ver, center) {
+@@ -2406,29 +3324,57 @@ var Item = this.Item = Base.extend({
+ ver = hor;
+ }
+ return this.transform(new Matrix().shear(hor, ver,
+- center || this.getPosition()));
++ center || this.getPosition(true)));
+ },
+
+- transform: function(matrix, flags) {
++ transform: function(matrix ) {
+ var bounds = this._bounds,
+- position = this._position,
+- children = this._children;
+- if (this._transform) {
+- this._transform(matrix, flags);
+- this._changed(Change.GEOMETRY);
+- }
++ position = this._position;
++ this._matrix.preConcatenate(matrix);
++ if (this._transformContent || arguments[1])
++ this.applyMatrix(true);
++ this._changed(5);
+ if (bounds && matrix.getRotation() % 90 === 0) {
+- this._bounds = this._createBounds(
+- matrix._transformBounds(bounds));
+- this._position = this._bounds.getCenter();
++ for (var key in bounds) {
++ var rect = bounds[key];
++ matrix._transformBounds(rect, rect);
++ }
++ var getter = this._boundsGetter,
++ rect = bounds[getter && getter.getBounds || getter || 'getBounds'];
++ if (rect)
++ this._position = rect.getCenter(true);
++ this._bounds = bounds;
+ } else if (position) {
+- this._position = matrix._transformPoint(position, position, true);
++ this._position = matrix._transformPoint(position, position);
+ }
+- for (var i = 0, l = children && children.length; i < l; i++)
+- children[i].transform(matrix, flags);
+ return this;
+ },
+
++ _applyMatrix: function(matrix, applyMatrix) {
++ var children = this._children;
++ if (children && children.length > 0) {
++ for (var i = 0, l = children.length; i < l; i++)
++ children[i].transform(matrix, applyMatrix);
++ return true;
++ }
++ },
++
++ applyMatrix: function(_dontNotify) {
++ var matrix = this._matrix;
++ if (this._applyMatrix(matrix, true)) {
++ var style = this._style,
++ fillColor = style.getFillColor(true),
++ strokeColor = style.getStrokeColor(true);
++ if (fillColor)
++ fillColor.transform(matrix);
++ if (strokeColor)
++ strokeColor.transform(matrix);
++ matrix.reset();
++ }
++ if (!_dontNotify)
++ this._changed(5);
++ },
++
+ fitBounds: function(rectangle, fill) {
+ rectangle = Rectangle.read(arguments);
+ var bounds = this.getBounds(),
+@@ -2437,156 +3383,149 @@ var Item = this.Item = Base.extend({
+ scale = (fill ? itemRatio > rectRatio : itemRatio < rectRatio)
+ ? rectangle.width / bounds.width
+ : rectangle.height / bounds.height,
+- delta = rectangle.getCenter().subtract(bounds.getCenter()),
+ newBounds = new Rectangle(new Point(),
+ new Size(bounds.width * scale, bounds.height * scale));
+ newBounds.setCenter(rectangle.getCenter());
+ this.setBounds(newBounds);
+ },
+
+- toString: function() {
+- return (this.constructor._name || 'Item') + (this._name
+- ? " '" + this._name + "'"
+- : ' @' + this._id);
+- },
+-
+- statics: {
+- drawSelectedBounds: function(bounds, ctx, matrix) {
+- var coords = matrix._transformCorners(bounds);
+- ctx.beginPath();
+- for (var i = 0; i < 8; i++)
+- ctx[i == 0 ? 'moveTo' : 'lineTo'](coords[i], coords[++i]);
+- ctx.closePath();
+- ctx.stroke();
+- for (var i = 0; i < 8; i++) {
+- ctx.beginPath();
+- ctx.rect(coords[i] - 2, coords[++i] - 2, 4, 4);
+- ctx.fill();
+- }
+- },
+-
+- draw: function(item, ctx, param) {
+- if (!item._visible || item._opacity == 0)
+- return;
+-
+- var tempCanvas, parentCtx;
+- if (item._blendMode !== 'normal'
+- || item._opacity < 1
+- && !(item._segments && (!item.getFillColor()
+- || !item.getStrokeColor()))) {
+- var bounds = item.getStrokeBounds() || item.getBounds();
+- if (!bounds.width || !bounds.height)
+- return;
+-
+- var itemOffset = bounds.getTopLeft().floor(),
+- size = bounds.getSize().ceil().add(new Size(1, 1));
+- tempCanvas = CanvasProvider.getCanvas(size);
+-
+- parentCtx = ctx;
+-
+- ctx = tempCanvas.getContext('2d');
+- ctx.save();
+-
+- ctx.translate(-itemOffset.x, -itemOffset.y);
+- }
+- var savedOffset;
+- if (itemOffset) {
+- savedOffset = param.offset;
+- param.offset = itemOffset;
+- }
+- item.draw(ctx, param);
+- if (itemOffset)
+- param.offset = savedOffset;
+-
+- if (tempCanvas) {
+-
+- ctx.restore();
+-
+- if (item._blendMode !== 'normal') {
+- var pixelOffset = itemOffset.subtract(param.offset);
+- BlendMode.process(item._blendMode, ctx, parentCtx,
+- item._opacity, pixelOffset);
++ _setStyles: function(ctx) {
++ var style = this._style,
++ width = style.getStrokeWidth(),
++ join = style.getStrokeJoin(),
++ cap = style.getStrokeCap(),
++ limit = style.getMiterLimit(),
++ fillColor = style.getFillColor(),
++ strokeColor = style.getStrokeColor(),
++ shadowColor = style.getShadowColor();
++ if (width != null)
++ ctx.lineWidth = width;
++ if (join)
++ ctx.lineJoin = join;
++ if (cap)
++ ctx.lineCap = cap;
++ if (limit)
++ ctx.miterLimit = limit;
++ if (fillColor)
++ ctx.fillStyle = fillColor.toCanvasStyle(ctx);
++ if (strokeColor) {
++ ctx.strokeStyle = strokeColor.toCanvasStyle(ctx);
++ var dashArray = style.getDashArray(),
++ dashOffset = style.getDashOffset();
++ if (paper.support.nativeDash && dashArray && dashArray.length) {
++ if ('setLineDash' in ctx) {
++ ctx.setLineDash(dashArray);
++ ctx.lineDashOffset = dashOffset;
+ } else {
+- parentCtx.save();
+- parentCtx.globalAlpha = item._opacity;
+- parentCtx.drawImage(tempCanvas,
+- itemOffset.x, itemOffset.y);
+- parentCtx.restore();
++ ctx.mozDash = dashArray;
++ ctx.mozDashOffset = dashOffset;
+ }
+-
+- CanvasProvider.returnCanvas(tempCanvas);
+ }
+ }
+- }
+-}, new function() {
+-
+- var sets = {
+- down: {}, drag: {}, up: {}, move: {}
+- };
++ if (shadowColor) {
++ ctx.shadowColor = shadowColor.toCanvasStyle(ctx);
++ ctx.shadowBlur = style.getShadowBlur();
++ var offset = this.getShadowOffset();
++ ctx.shadowOffsetX = offset.x;
++ ctx.shadowOffsetY = offset.y;
++ }
++ },
+
+- function removeAll(set) {
+- for (var id in set) {
+- var item = set[id];
+- item.remove();
+- for (var type in sets) {
+- var other = sets[type];
+- if (other != set && other[item.getId()])
+- delete other[item.getId()];
+- }
+- }
+- }
+-
+- function installHandler(name) {
+- var handler = 'onMouse' + Base.capitalize(name);
+- var func = paper.tool[handler];
+- if (!func || !func._installed) {
+- var hash = {};
+- hash[handler] = function(event) {
+- if (name === 'up')
+- sets.drag = {};
+- removeAll(sets[name]);
+- sets[name] = {};
+- if (this.base)
+- this.base(event);
+- };
+- paper.tool.inject(hash);
+- paper.tool[handler]._installed = true;
++ draw: function(ctx, param) {
++ if (!this._visible || this._opacity === 0)
++ return;
++ this._drawCount = this._project._drawCount;
++ var transforms = param.transforms,
++ parentMatrix = transforms[transforms.length - 1],
++ globalMatrix = parentMatrix.clone().concatenate(this._matrix);
++ transforms.push(this._globalMatrix = globalMatrix);
++ var blendMode = this._blendMode,
++ opacity = this._opacity,
++ normalBlend = blendMode === 'normal',
++ nativeBlend = BlendMode.nativeModes[blendMode],
++ direct = normalBlend && opacity === 1
++ || (nativeBlend || normalBlend && opacity < 1)
++ && this._canComposite(),
++ mainCtx, itemOffset, prevOffset;
++ if (!direct) {
++ var bounds = this.getStrokeBounds(parentMatrix);
++ if (!bounds.width || !bounds.height)
++ return;
++ prevOffset = param.offset;
++ itemOffset = param.offset = bounds.getTopLeft().floor();
++ mainCtx = ctx;
++ ctx = CanvasProvider.getContext(
++ bounds.getSize().ceil().add(new Size(1, 1)));
++ }
++ ctx.save();
++ if (direct) {
++ ctx.globalAlpha = opacity;
++ if (nativeBlend)
++ ctx.globalCompositeOperation = blendMode;
++ } else {
++ ctx.translate(-itemOffset.x, -itemOffset.y);
++ }
++ (direct ? this._matrix : globalMatrix).applyToContext(ctx);
++ if (!direct && param.clipItem)
++ param.clipItem.draw(ctx, param.extend({ clip: true }));
++ this._draw(ctx, param);
++ ctx.restore();
++ transforms.pop();
++ if (param.clip)
++ ctx.clip();
++ if (!direct) {
++ BlendMode.process(blendMode, ctx, mainCtx, opacity,
++ itemOffset.subtract(prevOffset));
++ CanvasProvider.release(ctx);
++ param.offset = prevOffset;
+ }
++ },
++
++ _canComposite: function() {
++ return false;
+ }
++}, Base.each(['down', 'drag', 'up', 'move'], function(name) {
++ this['removeOn' + Base.capitalize(name)] = function() {
++ var hash = {};
++ hash[name] = true;
++ return this.removeOn(hash);
++ };
++}, {
+
+- return Base.each(['down', 'drag', 'up', 'move'], function(name) {
+- this['removeOn' + Base.capitalize(name)] = function() {
+- var hash = {};
+- hash[name] = true;
+- return this.removeOn(hash);
+- };
+- }, {
+- removeOn: function(obj) {
+- for (var name in obj) {
+- if (obj[name]) {
+- sets[name][this.getId()] = this;
+- if (name === 'drag')
+- installHandler('up');
+- installHandler(name);
+- }
++ removeOn: function(obj) {
++ for (var name in obj) {
++ if (obj[name]) {
++ var key = 'mouse' + name,
++ project = this._project,
++ sets = project._removeSets = project._removeSets || {};
++ sets[key] = sets[key] || {};
++ sets[key][this._id] = this;
+ }
+- return this;
+ }
+- });
+-});
++ return this;
++ }
++}));
++
++var Group = Item.extend({
++ _class: 'Group',
++ _serializeFields: {
++ children: []
++ },
+
+-var Group = this.Group = Item.extend({
+- initialize: function(items) {
+- this.base();
++ initialize: function Group(arg) {
+ this._children = [];
+ this._namedChildren = {};
+- this.addChildren(!items || !Array.isArray(items)
+- || typeof items[0] !== 'object' ? arguments : items);
++ if (!this._initialize(arg))
++ this.addChildren(Array.isArray(arg) ? arg : arguments);
+ },
+
+- _changed: function(flags) {
+- Item.prototype._changed.call(this, flags);
+- if (flags & (ChangeFlag.HIERARCHY | ChangeFlag.CLIPPING)) {
++ _changed: function _changed(flags) {
++ _changed.base.call(this, flags);
++ if (flags & 2 && this._transformContent
++ && !this._matrix.isIdentity()) {
++ this.applyMatrix();
++ }
++ if (flags & (2 | 256)) {
+ delete this._clipItem;
+ }
+ },
+@@ -2602,6 +3541,16 @@ var Group = this.Group = Item.extend({
+ return this._clipItem = null;
+ },
+
++ getTransformContent: function() {
++ return this._transformContent;
++ },
++
++ setTransformContent: function(transform) {
++ this._transformContent = transform;
++ if (transform)
++ this.applyMatrix();
++ },
++
+ isClipped: function() {
+ return !!this._getClipItem();
+ },
+@@ -2610,157 +3559,279 @@ var Group = this.Group = Item.extend({
+ var child = this.getFirstChild();
+ if (child)
+ child.setClipMask(clipped);
+- return this;
+ },
+
+- draw: function(ctx, param) {
+- var clipItem = this._getClipItem();
++ _draw: function(ctx, param) {
++ var clipItem = param.clipItem = this._getClipItem();
+ if (clipItem)
+- Item.draw(clipItem, ctx, param);
++ clipItem.draw(ctx, param.extend({ clip: true }));
+ for (var i = 0, l = this._children.length; i < l; i++) {
+ var item = this._children[i];
+- if (item != clipItem)
+- Item.draw(item, ctx, param);
++ if (item !== clipItem)
++ item.draw(ctx, param);
+ }
++ param.clipItem = null;
+ }
+ });
+
+-var Layer = this.Layer = Group.extend({
+- initialize: function(items) {
++var Layer = Group.extend({
++ _class: 'Layer',
++
++ initialize: function Layer() {
+ this._project = paper.project;
+ this._index = this._project.layers.push(this) - 1;
+- this.base.apply(this, arguments);
++ Group.apply(this, arguments);
+ this.activate();
+ },
+
+- _remove: function(deselect, notify) {
++ _remove: function _remove(notify) {
+ if (this._parent)
+- return this.base(deselect, notify);
++ return _remove.base.call(this, notify);
+ if (this._index != null) {
+- if (deselect)
+- this.setSelected(false);
++ if (this._project.activeLayer === this)
++ this._project.activeLayer = this.getNextSibling()
++ || this.getPreviousSibling();
+ Base.splice(this._project.layers, null, this._index, 1);
+- this._project._needsRedraw();
++ this._project._needsRedraw = true;
+ return true;
+ }
+ return false;
+ },
+
+- getNextSibling: function() {
+- return this._parent ? this.base()
++ getNextSibling: function getNextSibling() {
++ return this._parent ? getNextSibling.base.call(this)
+ : this._project.layers[this._index + 1] || null;
+ },
+
+- getPreviousSibling: function() {
+- return this._parent ? this.base()
++ getPreviousSibling: function getPreviousSibling() {
++ return this._parent ? getPreviousSibling.base.call(this)
+ : this._project.layers[this._index - 1] || null;
+ },
+
++ isInserted: function isInserted() {
++ return this._parent ? isInserted.base.call(this) : this._index != null;
++ },
++
+ activate: function() {
+ this._project.activeLayer = this;
++ },
++
++ _insert: function _insert(above, item, _preserve) {
++ if (item instanceof Layer && !item._parent && this._remove(true)) {
++ Base.splice(item._project.layers, [this],
++ item._index + (above ? 1 : 0), 0);
++ this._setProject(item._project);
++ return this;
++ }
++ return _insert.base.call(this, above, item, _preserve);
+ }
+-}, new function () {
+- function insert(above) {
+- return function(item) {
+- if (item instanceof Layer && !item._parent
+- && this._remove(false, true)) {
+- Base.splice(item._project.layers, [this],
+- item._index + (above ? 1 : -1), 0);
+- this._setProject(item._project);
+- return true;
+- }
+- return this.base(item);
+- };
+- }
++});
+
+- return {
+- insertAbove: insert(true),
++var Shape = Item.extend({
++ _class: 'Shape',
++ _transformContent: false,
+
+- insertBelow: insert(false)
+- };
+-});
++ initialize: function Shape(type, point, size, props) {
++ this._initialize(props, point);
++ this._type = type;
++ this._size = size;
++ },
+
+-var PlacedItem = this.PlacedItem = Item.extend({
++ getSize: function() {
++ var size = this._size;
++ return new LinkedSize(size.width, size.height, this, 'setSize');
++ },
+
+- _transform: function(matrix, flags) {
+- this._matrix.preConcatenate(matrix);
++ setSize: function() {
++ var size = Size.read(arguments);
++ if (!this._size.equals(size)) {
++ this._size.set(size.width, size.height);
++ this._changed(5);
++ }
+ },
+
+- _changed: function(flags) {
+- Item.prototype._changed.call(this, flags);
+- if (flags & ChangeFlag.GEOMETRY) {
+- delete this._strokeBounds;
+- delete this._handleBounds;
+- delete this._roughBounds;
++ getRadius: function() {
++ var size = this._size;
++ return (size.width + size.height) / 4;
++ },
++
++ setRadius: function(radius) {
++ var size = radius * 2;
++ this.setSize(size, size);
++ },
++
++ _draw: function(ctx, param) {
++ var style = this._style,
++ size = this._size,
++ width = size.width,
++ height = size.height,
++ fillColor = style.getFillColor(),
++ strokeColor = style.getStrokeColor();
++ if (fillColor || strokeColor || param.clip) {
++ ctx.beginPath();
++ switch (this._type) {
++ case 'rect':
++ ctx.rect(-width / 2, -height / 2, width, height);
++ break;
++ case 'circle':
++ ctx.arc(0, 0, (width + height) / 4, 0, Math.PI * 2, true);
++ break;
++ case 'ellipse':
++ var mx = width / 2,
++ my = height / 2,
++ kappa = Numerical.KAPPA,
++ cx = mx * kappa,
++ cy = my * kappa;
++ ctx.moveTo(-mx, 0);
++ ctx.bezierCurveTo(-mx, -cy, -cx, -my, 0, -my);
++ ctx.bezierCurveTo(cx, -my, mx, -cy, mx, 0);
++ ctx.bezierCurveTo(mx, cy, cx, my, 0, my);
++ ctx.bezierCurveTo(-cx, my, -mx, cy, -mx, 0);
++ break;
++ }
++ }
++ if (!param.clip && (fillColor || strokeColor)) {
++ this._setStyles(ctx);
++ if (fillColor)
++ ctx.fill();
++ if (strokeColor)
++ ctx.stroke();
+ }
+ },
+
+- getMatrix: function() {
+- return this._matrix;
++ _canComposite: function() {
++ return !(this.hasFill() && this.hasStroke());
+ },
+
+- setMatrix: function(matrix) {
+- this._matrix = matrix.clone();
+- this._changed(Change.GEOMETRY);
++ _getBounds: function(getter, matrix) {
++ var rect = new Rectangle(this._size).setCenter(0, 0);
++ if (getter !== 'getBounds' && this.hasStroke())
++ rect = rect.expand(this.getStrokeWidth());
++ return matrix ? matrix._transformBounds(rect) : rect;
+ },
+
+- getBounds: function() {
+- var useCache = arguments[0] === undefined;
+- if (useCache && this._bounds)
+- return this._bounds;
+- var bounds = this.getStrokeBounds(arguments[0]);
+- if (useCache)
+- bounds = this._bounds = this._createBounds(bounds);
+- return bounds;
++ _contains: function _contains(point) {
++ switch (this._type) {
++ case 'rect':
++ return _contains.base.call(this, point);
++ case 'circle':
++ case 'ellipse':
++ return point.divide(this._size).getLength() <= 0.5;
++ }
+ },
+
+- _getBounds: function(getter, cacheName, args) {
+- var matrix = args[0],
+- useCache = matrix === undefined;
+- if (useCache && this[cacheName])
+- return this[cacheName];
+- matrix = matrix ? matrix.clone().concatenate(this._matrix)
+- : this._matrix;
+- var bounds = this._calculateBounds(getter, matrix);
+- if (useCache)
+- this[cacheName] = bounds;
+- return bounds;
++ _hitTest: function _hitTest(point) {
++ if (this.hasStroke()) {
++ var type = this._type,
++ strokeWidth = this.getStrokeWidth();
++ switch (type) {
++ case 'rect':
++ var rect = new Rectangle(this._size).setCenter(0, 0),
++ outer = rect.expand(strokeWidth),
++ inner = rect.expand(-strokeWidth);
++ if (outer._containsPoint(point) && !inner._containsPoint(point))
++ return new HitResult('stroke', this);
++ break;
++ case 'circle':
++ case 'ellipse':
++ var size = this._size,
++ width = size.width,
++ height = size.height,
++ radius;
++ if (type === 'ellipse') {
++ var angle = point.getAngleInRadians(),
++ x = width * Math.sin(angle),
++ y = height * Math.cos(angle);
++ radius = width * height / (2 * Math.sqrt(x * x + y * y));
++ } else {
++ radius = (width + height) / 4;
++ }
++ if (2 * Math.abs(point.getLength() - radius) <= strokeWidth)
++ return new HitResult('stroke', this);
++ break;
++ }
++ }
++ return _hitTest.base.apply(this, arguments);
++ },
++
++ statics: new function() {
++ function createShape(type, point, size, args) {
++ return new Shape(type, point, size, Base.getNamed(args));
++ }
++
++ return {
++ Circle: function() {
++ var center = Point.readNamed(arguments, 'center'),
++ radius = Base.readNamed(arguments, 'radius');
++ return createShape('circle', center, new Size(radius * 2),
++ arguments);
++ },
++
++ Rectangle: function() {
++ var rect = Rectangle.readNamed(arguments, 'rectangle');
++ return createShape('rect', rect.getCenter(true),
++ rect.getSize(true), arguments);
++ },
++
++ Ellipse: function() {
++ var rect = Rectangle.readNamed(arguments, 'rectangle');
++ return createShape('ellipse', rect.getCenter(true),
++ rect.getSize(true), arguments);
++ }
++ };
+ }
+ });
+
+-var Raster = this.Raster = PlacedItem.extend({
+- initialize: function(object) {
+- this.base();
+- if (object.getContext) {
+- this.setCanvas(object);
+- } else {
+- if (typeof object === 'string')
+- object = document.getElementById(object);
+- this.setImage(object);
++var Raster = Item.extend({
++ _class: 'Raster',
++ _transformContent: false,
++ _boundsGetter: 'getBounds',
++ _boundsSelected: true,
++ _serializeFields: {
++ source: null
++ },
++
++ initialize: function Raster(object, position) {
++ if (!this._initialize(object,
++ position !== undefined && Point.read(arguments, 1))) {
++ if (object.getContext) {
++ this.setCanvas(object);
++ } else if (typeof object === 'string') {
++ this.setSource(object);
++ } else {
++ this.setImage(object);
++ }
+ }
+- this._matrix = new Matrix();
++ if (!this._size)
++ this._size = new Size();
+ },
+
+- clone: function() {
+- var image = this._image;
+- if (!image) {
+- image = CanvasProvider.getCanvas(this._size);
+- image.getContext('2d').drawImage(this._canvas, 0, 0);
++ clone: function(insert) {
++ var param = { insert: false },
++ image = this._image;
++ if (image) {
++ param.image = image;
++ } else if (this._canvas) {
++ var canvas = param.canvas = CanvasProvider.getCanvas(this._size);
++ canvas.getContext('2d').drawImage(this._canvas, 0, 0);
+ }
+- var copy = new Raster(image);
+- copy._matrix = this._matrix.clone();
+- return this._clone(copy);
++ return this._clone(new Raster(param), insert);
+ },
+
+ getSize: function() {
+- return this._size;
++ var size = this._size;
++ return new LinkedSize(size.width, size.height, this, 'setSize');
+ },
+
+ setSize: function() {
+- var size = Size.read(arguments),
+- image = this.getImage();
+- this.setCanvas(CanvasProvider.getCanvas(size));
+- this.getContext(true).drawImage(image, 0, 0, size.width, size.height);
++ var size = Size.read(arguments);
++ if (!this._size.equals(size)) {
++ var element = this.getElement();
++ this.setCanvas(CanvasProvider.getCanvas(size));
++ if (element)
++ this.getContext(true).drawImage(element, 0, 0,
++ size.width, size.height);
++ }
+ },
+
+ getWidth: function() {
+@@ -2771,6 +3842,10 @@ var Raster = this.Raster = PlacedItem.extend({
+ return this._size.height;
+ },
+
++ isEmpty: function() {
++ return this._size.width == 0 && this._size.height == 0;
++ },
++
+ getPpi: function() {
+ var matrix = this._matrix,
+ orig = new Point(0, 0).transform(matrix),
+@@ -2785,8 +3860,10 @@ var Raster = this.Raster = PlacedItem.extend({
+ getContext: function() {
+ if (!this._context)
+ this._context = this.getCanvas().getContext('2d');
+- if (arguments[0])
+- this._changed(Change.PIXELS);
++ if (arguments[0]) {
++ this._image = null;
++ this._changed(129);
++ }
+ return this._context;
+ },
+
+@@ -2796,43 +3873,88 @@ var Raster = this.Raster = PlacedItem.extend({
+
+ getCanvas: function() {
+ if (!this._canvas) {
+- this._canvas = CanvasProvider.getCanvas(this._size);
+- if (this._image)
+- this.getContext(true).drawImage(this._image, 0, 0);
++ var ctx = CanvasProvider.getContext(this._size);
++ try {
++ if (this._image)
++ ctx.drawImage(this._image, 0, 0);
++ this._canvas = ctx.canvas;
++ } catch (e) {
++ CanvasProvider.release(ctx);
++ }
+ }
+ return this._canvas;
+ },
+
+ setCanvas: function(canvas) {
+ if (this._canvas)
+- CanvasProvider.returnCanvas(this._canvas);
++ CanvasProvider.release(this._canvas);
+ this._canvas = canvas;
+ this._size = new Size(canvas.width, canvas.height);
+ this._image = null;
+ this._context = null;
+- this._changed(Change.GEOMETRY);
++ this._changed(5 | 129);
+ },
+
+ getImage: function() {
+- return this._image || this.getCanvas();
++ return this._image;
+ },
+
+ setImage: function(image) {
+ if (this._canvas)
+- CanvasProvider.returnCanvas(this._canvas);
++ CanvasProvider.release(this._canvas);
+ this._image = image;
+- this._size = new Size(image.naturalWidth, image.naturalHeight);
++ this._size = new Size(image.width, image.height);
+ this._canvas = null;
+ this._context = null;
+- this._changed(Change.GEOMETRY);
++ this._changed(5);
++ },
++
++ getSource: function() {
++ return this._image && this._image.src || this.toDataURL();
++ },
++
++ setSource: function(src) {
++ var that = this,
++ image = document.getElementById(src) || new Image();
++
++ function loaded() {
++ that.fire('load');
++ if (that._project.view)
++ that._project.view.draw(true);
++ }
++
++ if (image.width && image.height) {
++ setTimeout(loaded, 0);
++ } else if (!image.src) {
++ DomEvent.add(image, {
++ load: function() {
++ that.setImage(image);
++ loaded();
++ }
++ });
++ image.src = src;
++ }
++ this.setImage(image);
++ },
++
++ getElement: function() {
++ return this._canvas || this._image;
+ },
+
+ getSubImage: function(rect) {
+ rect = Rectangle.read(arguments);
+- var canvas = CanvasProvider.getCanvas(rect.getSize());
+- canvas.getContext('2d').drawImage(this.getCanvas(), rect.x, rect.y,
+- canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
+- return canvas;
++ var ctx = CanvasProvider.getContext(rect.getSize());
++ ctx.drawImage(this.getCanvas(), rect.x, rect.y,
++ rect.width, rect.height, 0, 0, rect.width, rect.height);
++ return ctx.canvas;
++ },
++
++ toDataURL: function() {
++ var src = this._image && this._image.src;
++ if (/^data:/.test(src))
++ return src;
++ var canvas = this.getCanvas();
++ return canvas ? canvas.toDataURL() : null;
+ },
+
+ drawImage: function(image, point) {
+@@ -2841,34 +3963,36 @@ var Raster = this.Raster = PlacedItem.extend({
+ },
+
+ getAverageColor: function(object) {
+- if (!object)
+- object = this.getBounds();
+ var bounds, path;
+- if (object instanceof PathItem) {
++ if (!object) {
++ bounds = this.getBounds();
++ } else if (object instanceof PathItem) {
+ path = object;
+ bounds = object.getBounds();
+ } else if (object.width) {
+ bounds = new Rectangle(object);
+ } else if (object.x) {
+- bounds = Rectangle.create(object.x - 0.5, object.y - 0.5, 1, 1);
++ bounds = new Rectangle(object.x - 0.5, object.y - 0.5, 1, 1);
+ }
+ var sampleSize = 32,
+ width = Math.min(bounds.width, sampleSize),
+ height = Math.min(bounds.height, sampleSize);
+ var ctx = Raster._sampleContext;
+ if (!ctx) {
+- ctx = Raster._sampleContext = CanvasProvider.getCanvas(
+- new Size(sampleSize)).getContext('2d');
++ ctx = Raster._sampleContext = CanvasProvider.getContext(
++ new Size(sampleSize));
+ } else {
+- ctx.clearRect(0, 0, sampleSize, sampleSize);
++ ctx.clearRect(0, 0, sampleSize + 1, sampleSize + 1);
+ }
+ ctx.save();
+- ctx.scale(width / bounds.width, height / bounds.height);
+- ctx.translate(-bounds.x, -bounds.y);
++ var matrix = new Matrix()
++ .scale(width / bounds.width, height / bounds.height)
++ .translate(-bounds.x, -bounds.y);
++ matrix.applyToContext(ctx);
+ if (path)
+- path.draw(ctx, { clip: true });
++ path.draw(ctx, Base.merge({ clip: true, transforms: [matrix] }));
+ this._matrix.applyToContext(ctx);
+- ctx.drawImage(this._canvas || this._image,
++ ctx.drawImage(this.getElement(),
+ -this._size.width / 2, -this._size.height / 2);
+ ctx.restore();
+ var pixels = ctx.getImageData(0.5, 0.5, Math.ceil(width),
+@@ -2890,81 +4014,90 @@ var Raster = this.Raster = PlacedItem.extend({
+
+ getPixel: function(point) {
+ point = Point.read(arguments);
+- var pixels = this.getContext().getImageData(point.x, point.y, 1, 1).data,
+- channels = new Array(4);
+- for (var i = 0; i < 4; i++)
+- channels[i] = pixels[i] / 255;
+- return RgbColor.read(channels);
+- },
+-
+- setPixel: function(point, color) {
+- var hasPoint = arguments.length == 2;
+- point = Point.read(arguments, 0, hasPoint ? 1 : 2);
+- color = Color.read(arguments, hasPoint ? 1 : 2);
+- var ctx = this.getContext(true),
++ var data = this.getContext().getImageData(point.x, point.y, 1, 1).data;
++ return new Color('rgb', [data[0] / 255, data[1] / 255, data[2] / 255],
++ data[3] / 255);
++ },
++
++ setPixel: function() {
++ var point = Point.read(arguments),
++ color = Color.read(arguments),
++ components = color._convert('rgb'),
++ alpha = color._alpha,
++ ctx = this.getContext(true),
+ imageData = ctx.createImageData(1, 1),
+- alpha = color.getAlpha();
+- imageData.data[0] = color.getRed() * 255;
+- imageData.data[1] = color.getGreen() * 255;
+- imageData.data[2] = color.getBlue() * 255;
+- imageData.data[3] = alpha != null ? alpha * 255 : 255;
++ data = imageData.data;
++ data[0] = components[0] * 255;
++ data[1] = components[1] * 255;
++ data[2] = components[2] * 255;
++ data[3] = alpha != null ? alpha * 255 : 255;
+ ctx.putImageData(imageData, point.x, point.y);
+ },
+
+- createData: function(size) {
++ createImageData: function(size) {
+ size = Size.read(arguments);
+ return this.getContext().createImageData(size.width, size.height);
+ },
+
+- getData: function(rect) {
++ getImageData: function(rect) {
+ rect = Rectangle.read(arguments);
+ if (rect.isEmpty())
+- rect = new Rectangle(this.getSize());
++ rect = new Rectangle(this._size);
+ return this.getContext().getImageData(rect.x, rect.y,
+ rect.width, rect.height);
+ },
+
+- setData: function(data, point) {
++ setImageData: function(data, point) {
+ point = Point.read(arguments, 1);
+ this.getContext(true).putImageData(data, point.x, point.y);
+ },
+
+- _calculateBounds: function(getter, matrix) {
+- return matrix._transformBounds(
+- new Rectangle(this._size).setCenter(0, 0));
++ _getBounds: function(getter, matrix) {
++ var rect = new Rectangle(this._size).setCenter(0, 0);
++ return matrix ? matrix._transformBounds(rect) : rect;
+ },
+
+- getHandleBounds: function() {
+- return this.getStrokeBounds(arguments[0]);
+- },
+-
+- getRoughBounds: function() {
+- return this.getStrokeBounds(arguments[0]);
++ _hitTest: function(point) {
++ if (this._contains(point)) {
++ var that = this;
++ return new HitResult('pixel', that, {
++ offset: point.add(that._size.divide(2)).round(),
++ color: {
++ get: function() {
++ return that.getPixel(this.offset);
++ }
++ }
++ });
++ }
+ },
+
+- draw: function(ctx, param) {
+- if (param.selection) {
+- var bounds = new Rectangle(this._size).setCenter(0, 0);
+- Item.drawSelectedBounds(bounds, ctx, this._matrix);
+- } else {
+- ctx.save();
+- this._matrix.applyToContext(ctx);
+- ctx.drawImage(this._canvas || this._image,
++ _draw: function(ctx) {
++ var element = this.getElement();
++ if (element) {
++ ctx.globalAlpha = this._opacity;
++ ctx.drawImage(element,
+ -this._size.width / 2, -this._size.height / 2);
+- ctx.restore();
+ }
++ },
++
++ _canComposite: function() {
++ return true;
+ }
+ });
+
+-var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend({
+- initialize: function(symbol, matrixOrOffset) {
+- this.base();
+- this.setSymbol(symbol instanceof Symbol ? symbol : new Symbol(symbol));
+- this._matrix = matrixOrOffset !== undefined
+- ? matrixOrOffset instanceof Matrix
+- ? matrixOrOffset
+- : new Matrix().translate(Point.read(arguments, 1))
+- : new Matrix();
++var PlacedSymbol = Item.extend({
++ _class: 'PlacedSymbol',
++ _transformContent: false,
++ _boundsGetter: { getBounds: 'getStrokeBounds' },
++ _boundsSelected: true,
++ _serializeFields: {
++ symbol: null
++ },
++
++ initialize: function PlacedSymbol(arg0, arg1) {
++ if (!this._initialize(arg0,
++ arg1 !== undefined && Point.read(arguments, 1)))
++ this.setSymbol(arg0 instanceof Symbol ? arg0 : new Symbol(arg0));
+ },
+
+ getSymbol: function() {
+@@ -2978,45 +4111,51 @@ var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend({
+ symbol._instances[this._id] = this;
+ },
+
+- clone: function() {
+- return this._clone(new PlacedSymbol(this.symbol, this._matrix.clone()));
++ clone: function(insert) {
++ return this._clone(new PlacedSymbol({
++ symbol: this.symbol,
++ insert: false
++ }), insert);
+ },
+
+- _calculateBounds: function(getter, matrix) {
+- return this.symbol._definition[getter](matrix);
++ isEmpty: function() {
++ return this._symbol._definition.isEmpty();
+ },
+
+- draw: function(ctx, param) {
+- if (param.selection) {
+- Item.drawSelectedBounds(this.symbol._definition.getStrokeBounds(),
+- ctx, this._matrix);
+- } else {
+- ctx.save();
+- this._matrix.applyToContext(ctx);
+- Item.draw(this.symbol.getDefinition(), ctx, param);
+- ctx.restore();
+- }
++ _getBounds: function(getter, matrix) {
++ return this.symbol._definition._getCachedBounds(getter, matrix);
++ },
++
++ _hitTest: function(point, options, matrix) {
++ var result = this._symbol._definition._hitTest(point, options, matrix);
++ if (result)
++ result.item = this;
++ return result;
++ },
++
++ _draw: function(ctx, param) {
++ this.symbol._definition.draw(ctx, param);
+ }
+
+ });
+
+-HitResult = Base.extend({
+- initialize: function(type, item, values) {
++var HitResult = Base.extend({
++ _class: 'HitResult',
++
++ initialize: function HitResult(type, item, values) {
+ this.type = type;
+ this.item = item;
+ if (values) {
+- Base.each(values, function(value, key) {
+- this[key] = value;
+- }, this);
++ values.enumerable = true;
++ this.inject(values);
+ }
+ },
+
+ statics: {
+- getOptions: function(point, options) {
++ getOptions: function(options) {
+ return options && options._merged ? options : Base.merge({
+- point: Point.read(arguments, 0, 1),
+ type: null,
+- tolerance: 2,
++ tolerance: paper.project.options.hitTolerance || 2,
+ fill: !options,
+ stroke: !options,
+ segments: !options,
+@@ -3032,13 +4171,14 @@ HitResult = Base.extend({
+ }
+ });
+
+-var Segment = this.Segment = Base.extend({
+- initialize: function(arg0, arg1, arg2, arg3, arg4, arg5) {
++var Segment = Base.extend({
++ _class: 'Segment',
++
++ initialize: function Segment(arg0, arg1, arg2, arg3, arg4, arg5) {
+ var count = arguments.length,
+- createPoint = SegmentPoint.create,
+ point, handleIn, handleOut;
+- if (count == 0) {
+- } else if (count == 1) {
++ if (count === 0) {
++ } else if (count === 1) {
+ if (arg0.point) {
+ point = arg0.point;
+ handleIn = arg0.handleIn;
+@@ -3054,20 +4194,26 @@ var Segment = this.Segment = Base.extend({
+ handleIn = arg1;
+ handleOut = arg2;
+ }
+- } else if (count == 6) {
++ } else if (count === 6) {
+ point = [ arg0, arg1 ];
+ handleIn = [ arg2, arg3 ];
+ handleOut = [ arg4, arg5 ];
+ }
+- createPoint(this, '_point', point);
+- createPoint(this, '_handleIn', handleIn);
+- createPoint(this, '_handleOut', handleOut);
++ this._point = new SegmentPoint(point, this);
++ this._handleIn = new SegmentPoint(handleIn, this);
++ this._handleOut = new SegmentPoint(handleOut, this);
++ },
++
++ _serialize: function(options) {
++ return Base.serialize(this.isLinear() ? this._point
++ : [this._point, this._handleIn, this._handleOut], options, true);
+ },
+
+ _changed: function(point) {
+ if (!this._path)
+ return;
+- var curve = this._path._curves && this.getCurve(), other;
++ var curve = this._path._curves && this.getCurve(),
++ other;
+ if (curve) {
+ curve._changed();
+ if (other = (curve[point == this._point
+@@ -3076,7 +4222,7 @@ var Segment = this.Segment = Base.extend({
+ other._changed();
+ }
+ }
+- this._path._changed(Change.GEOMETRY);
++ this._path._changed(5);
+ },
+
+ getPoint: function() {
+@@ -3106,11 +4252,20 @@ var Segment = this.Segment = Base.extend({
+ this._handleOut.set(point.x, point.y);
+ },
+
++ isLinear: function() {
++ return this._handleIn.isZero() && this._handleOut.isZero();
++ },
++
++ setLinear: function() {
++ this._handleIn.set(0, 0);
++ this._handleOut.set(0, 0);
++ },
++
+ _isSelected: function(point) {
+ var state = this._selectionState;
+- return point == this._point ? !!(state & SelectionState.POINT)
+- : point == this._handleIn ? !!(state & SelectionState.HANDLE_IN)
+- : point == this._handleOut ? !!(state & SelectionState.HANDLE_OUT)
++ return point == this._point ? !!(state & 4)
++ : point == this._handleIn ? !!(state & 1)
++ : point == this._handleOut ? !!(state & 2)
+ : false;
+ },
+
+@@ -3119,9 +4274,9 @@ var Segment = this.Segment = Base.extend({
+ selected = !!selected,
+ state = this._selectionState || 0,
+ selection = [
+- !!(state & SelectionState.POINT),
+- !!(state & SelectionState.HANDLE_IN),
+- !!(state & SelectionState.HANDLE_OUT)
++ !!(state & 4),
++ !!(state & 1),
++ !!(state & 2)
+ ];
+ if (point == this._point) {
+ if (selected) {
+@@ -3141,14 +4296,15 @@ var Segment = this.Segment = Base.extend({
+ if (selected)
+ selection[0] = false;
+ selection[index] = selected;
+- path._changed(Change.ATTRIBUTE);
+ }
+ }
+- this._selectionState = (selection[0] ? SelectionState.POINT : 0)
+- | (selection[1] ? SelectionState.HANDLE_IN : 0)
+- | (selection[2] ? SelectionState.HANDLE_OUT : 0);
+- if (path && state != this._selectionState)
++ this._selectionState = (selection[0] ? 4 : 0)
++ | (selection[1] ? 1 : 0)
++ | (selection[2] ? 2 : 0);
++ if (path && state != this._selectionState) {
+ path._updateSelection(this, state, this._selectionState);
++ path._changed(33);
++ }
+ },
+
+ isSelected: function() {
+@@ -3168,15 +4324,21 @@ var Segment = this.Segment = Base.extend({
+ },
+
+ getCurve: function() {
+- if (this._path) {
+- var index = this._index;
+- if (!this._path._closed && index == this._path._segments.length - 1)
++ var path = this._path,
++ index = this._index;
++ if (path) {
++ if (!path._closed && index == path._segments.length - 1)
+ index--;
+- return this._path.getCurves()[index] || null;
++ return path.getCurves()[index] || null;
+ }
+ return null;
+ },
+
++ getLocation: function() {
++ var curve = this.getCurve();
++ return curve ? new CurveLocation(curve, curve.getNext() ? 0 : 1) : null;
++ },
++
+ getNext: function() {
+ var segments = this._path && this._path._segments;
+ return segments && (segments[this._index + 1]
+@@ -3197,6 +4359,18 @@ var Segment = this.Segment = Base.extend({
+ return this._path ? !!this._path.removeSegment(this._index) : false;
+ },
+
++ clone: function() {
++ return new Segment(this._point, this._handleIn, this._handleOut);
++ },
++
++ equals: function(segment) {
++ return segment === this || segment
++ && this._point.equals(segment._point)
++ && this._handleIn.equals(segment._handleIn)
++ && this._handleOut.equals(segment._handleOut)
++ || false;
++ },
++
+ toString: function() {
+ var parts = [ 'point: ' + this._point ];
+ if (!this._handleIn.isZero())
+@@ -3225,37 +4399,59 @@ var Segment = this.Segment = Base.extend({
+ coords[i++] = handleOut._x + x;
+ coords[i++] = handleOut._y + y;
+ }
+- if (!matrix)
+- return;
+- matrix._transformCoordinates(coords, 0, coords, 0, i / 2);
+- x = coords[0];
+- y = coords[1];
+- if (change) {
+- point._x = x;
+- point._y = y;
+- i = 2;
+- if (handleIn) {
+- handleIn._x = coords[i++] - x;
+- handleIn._y = coords[i++] - y;
+- }
+- if (handleOut) {
+- handleOut._x = coords[i++] - x;
+- handleOut._y = coords[i++] - y;
+- }
+- } else {
+- if (!handleIn) {
+- coords[i++] = x;
+- coords[i++] = y;
+- }
+- if (!handleOut) {
+- coords[i++] = x;
+- coords[i++] = y;
++ if (matrix) {
++ matrix._transformCoordinates(coords, 0, coords, 0, i / 2);
++ x = coords[0];
++ y = coords[1];
++ if (change) {
++ point._x = x;
++ point._y = y;
++ i = 2;
++ if (handleIn) {
++ handleIn._x = coords[i++] - x;
++ handleIn._y = coords[i++] - y;
++ }
++ if (handleOut) {
++ handleOut._x = coords[i++] - x;
++ handleOut._y = coords[i++] - y;
++ }
++ } else {
++ if (!handleIn) {
++ coords[i++] = x;
++ coords[i++] = y;
++ }
++ if (!handleOut) {
++ coords[i++] = x;
++ coords[i++] = y;
++ }
+ }
+ }
++ return coords;
+ }
+ });
+
+ var SegmentPoint = Point.extend({
++ initialize: function SegmentPoint(point, owner) {
++ var x, y, selected;
++ if (!point) {
++ x = y = 0;
++ } else if ((x = point[0]) !== undefined) {
++ y = point[1];
++ } else {
++ if ((x = point.x) === undefined) {
++ point = Point.read(arguments);
++ x = point.x;
++ }
++ y = point.y;
++ selected = point.selected;
++ }
++ this._x = x;
++ this._y = y;
++ this._owner = owner;
++ if (selected)
++ this.setSelected(true);
++ },
++
+ set: function(x, y) {
+ this._x = x;
+ this._y = y;
+@@ -3282,7 +4478,7 @@ var SegmentPoint = Point.extend({
+ },
+
+ isZero: function() {
+- return this._x == 0 && this._y == 0;
++ return Numerical.isZero(this._x) && Numerical.isZero(this._y);
+ },
+
+ setSelected: function(selected) {
+@@ -3291,68 +4487,47 @@ var SegmentPoint = Point.extend({
+
+ isSelected: function() {
+ return this._owner._isSelected(this);
+- },
+-
+- statics: {
+- create: function(segment, key, pt) {
+- var point = new SegmentPoint(SegmentPoint.dont),
+- x, y, selected;
+- if (!pt) {
+- x = y = 0;
+- } else if ((x = pt[0]) !== undefined) {
+- y = pt[1];
+- } else {
+- if ((x = pt.x) === undefined) {
+- pt = Point.read(arguments, 2, 1);
+- x = pt.x;
+- }
+- y = pt.y;
+- selected = pt.selected;
+- }
+- point._x = x;
+- point._y = y;
+- point._owner = segment;
+- segment[key] = point;
+- if (selected)
+- point.setSelected(true);
+- return point;
+- }
+ }
+ });
+
+-var SelectionState = {
+- HANDLE_IN: 1,
+- HANDLE_OUT: 2,
+- POINT: 4
+-};
+-
+-var Curve = this.Curve = Base.extend({
+- initialize: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
++var Curve = Base.extend({
++ _class: 'Curve',
++ initialize: function Curve(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
+ var count = arguments.length;
+- if (count == 0) {
++ if (count === 3) {
++ this._path = arg0;
++ this._segment1 = arg1;
++ this._segment2 = arg2;
++ } else if (count === 0) {
+ this._segment1 = new Segment();
+ this._segment2 = new Segment();
+- } else if (count == 1) {
++ } else if (count === 1) {
+ this._segment1 = new Segment(arg0.segment1);
+ this._segment2 = new Segment(arg0.segment2);
+- } else if (count == 2) {
++ } else if (count === 2) {
+ this._segment1 = new Segment(arg0);
+ this._segment2 = new Segment(arg1);
+- } else if (count == 4) {
+- this._segment1 = new Segment(arg0, null, arg1);
+- this._segment2 = new Segment(arg3, arg2, null);
+- } else if (count == 8) {
+- var p1 = Point.create(arg0, arg1),
+- p2 = Point.create(arg6, arg7);
+- this._segment1 = new Segment(p1, null,
+- Point.create(arg2, arg3).subtract(p1));
+- this._segment2 = new Segment(p2,
+- Point.create(arg4, arg5).subtract(p2), null);
++ } else {
++ var point1, handle1, handle2, point2;
++ if (count === 4) {
++ point1 = arg0;
++ handle1 = arg1;
++ handle2 = arg2;
++ point2 = arg3;
++ } else if (count === 8) {
++ point1 = [arg0, arg1];
++ point2 = [arg6, arg7];
++ handle1 = [arg2 - arg0, arg3 - arg1];
++ handle2 = [arg4 - arg6, arg5 - arg7];
++ }
++ this._segment1 = new Segment(point1, null, handle1);
++ this._segment2 = new Segment(point2, handle2, null);
+ }
+ },
+
+ _changed: function() {
+ delete this._length;
++ delete this._bounds;
+ },
+
+ getPoint1: function() {
+@@ -3428,22 +4603,22 @@ var Curve = this.Curve = Base.extend({
+ this.getHandle2().setSelected(selected);
+ },
+
+- getValues: function(matrix) {
+- return Curve.getValues(this._segment1, this._segment2, matrix);
++ getValues: function() {
++ return Curve.getValues(this._segment1, this._segment2);
+ },
+
+- getPoints: function(matrix) {
+- var coords = this.getValues(matrix),
++ getPoints: function() {
++ var coords = this.getValues(),
+ points = [];
+ for (var i = 0; i < 8; i += 2)
+- points.push(Point.create(coords[i], coords[i + 1]));
++ points.push(new Point(coords[i], coords[i + 1]));
+ return points;
+ },
+
+ getLength: function() {
+ var from = arguments[0],
+- to = arguments[1];
+- fullLength = arguments.length == 0 || from == 0 && to == 1;
++ to = arguments[1],
++ fullLength = arguments.length === 0 || from === 0 && to === 1;
+ if (fullLength && this._length != null)
+ return this._length;
+ var length = Curve.getLength(this.getValues(), from, to);
+@@ -3452,6 +4627,10 @@ var Curve = this.Curve = Base.extend({
+ return length;
+ },
+
++ getArea: function() {
++ return Curve.getArea(this.getValues());
++ },
++
+ getPart: function(from, to) {
+ return new Curve(Curve.getPart(this.getValues(), from, to));
+ },
+@@ -3461,47 +4640,67 @@ var Curve = this.Curve = Base.extend({
+ && this._segment2._handleIn.isZero();
+ },
+
+- getParameterAt: function(offset, start) {
+- return Curve.getParameterAt(this.getValues(), offset,
+- start !== undefined ? start : offset < 0 ? 1 : 0);
+- },
+-
+- getPoint: function(parameter) {
+- return Curve.evaluate(this.getValues(), parameter, 0);
+- },
+-
+- getTangent: function(parameter) {
+- return Curve.evaluate(this.getValues(), parameter, 1);
+- },
+-
+- getNormal: function(parameter) {
+- return Curve.evaluate(this.getValues(), parameter, 2);
++ getIntersections: function(curve) {
++ return Curve.getIntersections(this.getValues(), curve.getValues(),
++ this, curve, []);
+ },
+
+- getParameter: function(point) {
+- point = Point.read(point);
+- return Curve.getParameter(this.getValues(), point.x, point.y);
++ reverse: function() {
++ return new Curve(this._segment2.reverse(), this._segment1.reverse());
+ },
+
+- getCrossings: function(point, matrix, roots) {
+- var vals = this.getValues(matrix),
+- num = Curve.solveCubic(vals, 1, point.y, roots),
+- crossings = 0;
+- for (var i = 0; i < num; i++) {
+- var t = roots[i];
+- if (t >= 0 && t < 1 && Curve.evaluate(vals, t, 0).x > point.x) {
+- if (t < Numerical.TOLERANCE && Curve.evaluate(
+- this.getPrevious().getValues(matrix), 1, 1).y
+- * Curve.evaluate(vals, t, 1).y >= 0)
+- continue;
+- crossings++;
++ _getParameter: function(offset, isParameter) {
++ return isParameter
++ ? offset
++ : offset && offset.curve === this
++ ? offset.parameter
++ : offset === undefined && isParameter === undefined
++ ? 0.5
++ : this.getParameterAt(offset, 0);
++ },
++
++ divide: function(offset, isParameter) {
++ var parameter = this._getParameter(offset, isParameter),
++ res = null;
++ if (parameter > 0 && parameter < 1) {
++ var parts = Curve.subdivide(this.getValues(), parameter),
++ isLinear = this.isLinear(),
++ left = parts[0],
++ right = parts[1];
++
++ if (!isLinear) {
++ this._segment1._handleOut.set(left[2] - left[0],
++ left[3] - left[1]);
++ this._segment2._handleIn.set(right[4] - right[6],
++ right[5] - right[7]);
++ }
++
++ var x = left[6], y = left[7],
++ segment = new Segment(new Point(x, y),
++ !isLinear && new Point(left[4] - x, left[5] - y),
++ !isLinear && new Point(right[2] - x, right[3] - y));
++
++ if (this._path) {
++ if (this._segment1._index > 0 && this._segment2._index === 0) {
++ this._path.add(segment);
++ } else {
++ this._path.insert(this._segment2._index, segment);
++ }
++ res = this;
++ } else {
++ var end = this._segment2;
++ this._segment2 = segment;
++ res = new Curve(segment, end);
+ }
+ }
+- return crossings;
++ return res;
+ },
+
+- reverse: function() {
+- return new Curve(this._segment2.reverse(), this._segment1.reverse());
++ split: function(offset, isParameter) {
++ return this._path
++ ? this._path.split(this._segment1._index,
++ this._getParameter(offset, isParameter))
++ : null;
+ },
+
+ clone: function() {
+@@ -3518,181 +4717,356 @@ var Curve = this.Curve = Base.extend({
+ return '{ ' + parts.join(', ') + ' }';
+ },
+
+- statics: {
+- create: function(path, segment1, segment2) {
+- var curve = new Curve(Curve.dont);
+- curve._path = path;
+- curve._segment1 = segment1;
+- curve._segment2 = segment2;
+- return curve;
+- },
+-
+- getValues: function(segment1, segment2, matrix) {
+- var p1 = segment1._point,
+- h1 = segment1._handleOut,
+- h2 = segment2._handleIn,
+- p2 = segment2._point,
+- coords = [
+- p1._x, p1._y,
+- p1._x + h1._x, p1._y + h1._y,
+- p2._x + h2._x, p2._y + h2._y,
+- p2._x, p2._y
+- ];
+- return matrix
+- ? matrix._transformCoordinates(coords, 0, coords, 0, 4)
+- : coords;
+- },
++statics: {
++ getValues: function(segment1, segment2) {
++ var p1 = segment1._point,
++ h1 = segment1._handleOut,
++ h2 = segment2._handleIn,
++ p2 = segment2._point;
++ return [
++ p1._x, p1._y,
++ p1._x + h1._x, p1._y + h1._y,
++ p2._x + h2._x, p2._y + h2._y,
++ p2._x, p2._y
++ ];
++ },
+
+- evaluate: function(v, t, type) {
+- var p1x = v[0], p1y = v[1],
+- c1x = v[2], c1y = v[3],
+- c2x = v[4], c2y = v[5],
+- p2x = v[6], p2y = v[7],
+- x, y;
++ evaluate: function(v, t, type) {
++ var p1x = v[0], p1y = v[1],
++ c1x = v[2], c1y = v[3],
++ c2x = v[4], c2y = v[5],
++ p2x = v[6], p2y = v[7],
++ x, y;
+
+- if (type == 0 && (t == 0 || t == 1)) {
+- x = t == 0 ? p1x : p2x;
+- y = t == 0 ? p1y : p2y;
++ if (type === 0 && (t === 0 || t === 1)) {
++ x = t === 0 ? p1x : p2x;
++ y = t === 0 ? p1y : p2y;
++ } else {
++ var cx = 3 * (c1x - p1x),
++ bx = 3 * (c2x - c1x) - cx,
++ ax = p2x - p1x - cx - bx,
++
++ cy = 3 * (c1y - p1y),
++ by = 3 * (c2y - c1y) - cy,
++ ay = p2y - p1y - cy - by;
++ if (type === 0) {
++ x = ((ax * t + bx) * t + cx) * t + p1x;
++ y = ((ay * t + by) * t + cy) * t + p1y;
+ } else {
+- var tMin = Numerical.TOLERANCE;
+- if (t < tMin && c1x == p1x && c1y == p1y)
+- t = tMin;
+- else if (t > 1 - tMin && c2x == p2x && c2y == p2y)
+- t = 1 - tMin;
+- var cx = 3 * (c1x - p1x),
+- bx = 3 * (c2x - c1x) - cx,
+- ax = p2x - p1x - cx - bx,
+-
+- cy = 3 * (c1y - p1y),
+- by = 3 * (c2y - c1y) - cy,
+- ay = p2y - p1y - cy - by;
+-
+- switch (type) {
+- case 0:
+- x = ((ax * t + bx) * t + cx) * t + p1x;
+- y = ((ay * t + by) * t + cy) * t + p1y;
+- break;
+- case 1:
+- case 2:
++ var tMin = 0.00001;
++ if (t < tMin && c1x == p1x && c1y == p1y
++ || t > 1 - tMin && c2x == p2x && c2y == p2y) {
++ x = c2x - c1x;
++ y = c2y - c1y;
++ } else {
+ x = (3 * ax * t + 2 * bx) * t + cx;
+ y = (3 * ay * t + 2 * by) * t + cy;
+- break;
++ }
++ if (type === 3) {
++ var x2 = 6 * ax * t + 2 * bx,
++ y2 = 6 * ay * t + 2 * by;
++ return (x * y2 - y * x2) / Math.pow(x * x + y * y, 3 / 2);
+ }
+ }
+- return type == 2 ? new Point(y, -x) : new Point(x, y);
+- },
+-
+- subdivide: function(v, t) {
+- var p1x = v[0], p1y = v[1],
+- c1x = v[2], c1y = v[3],
+- c2x = v[4], c2y = v[5],
+- p2x = v[6], p2y = v[7];
+- if (t === undefined)
+- t = 0.5;
+- var u = 1 - t,
+- p3x = u * p1x + t * c1x, p3y = u * p1y + t * c1y,
+- p4x = u * c1x + t * c2x, p4y = u * c1y + t * c2y,
+- p5x = u * c2x + t * p2x, p5y = u * c2y + t * p2y,
+- p6x = u * p3x + t * p4x, p6y = u * p3y + t * p4y,
+- p7x = u * p4x + t * p5x, p7y = u * p4y + t * p5y,
+- p8x = u * p6x + t * p7x, p8y = u * p6y + t * p7y;
+- return [
+- [p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y],
+- [p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y]
+- ];
+- },
++ }
++ return type == 2 ? new Point(y, -x) : new Point(x, y);
++ },
+
+- solveCubic: function (v, coord, val, roots) {
+- var p1 = v[coord],
+- c1 = v[coord + 2],
+- c2 = v[coord + 4],
+- p2 = v[coord + 6],
+- c = 3 * (c1 - p1),
+- b = 3 * (c2 - c1) - c,
+- a = p2 - p1 - c - b;
+- return Numerical.solveCubic(a, b, c, p1 - val, roots,
+- Numerical.TOLERANCE);
+- },
+-
+- getParameter: function(v, x, y) {
+- var txs = [],
+- tys = [],
+- sx = Curve.solveCubic(v, 0, x, txs),
+- sy = Curve.solveCubic(v, 1, y, tys),
+- tx, ty;
+- for (var cx = 0; sx == -1 || cx < sx;) {
+- if (sx == -1 || (tx = txs[cx++]) >= 0 && tx <= 1) {
+- for (var cy = 0; sy == -1 || cy < sy;) {
+- if (sy == -1 || (ty = tys[cy++]) >= 0 && ty <= 1) {
+- if (sx == -1) tx = ty;
+- else if (sy == -1) ty = tx;
+- if (Math.abs(tx - ty) < Numerical.TOLERANCE)
+- return (tx + ty) * 0.5;
+- }
++ subdivide: function(v, t) {
++ var p1x = v[0], p1y = v[1],
++ c1x = v[2], c1y = v[3],
++ c2x = v[4], c2y = v[5],
++ p2x = v[6], p2y = v[7];
++ if (t === undefined)
++ t = 0.5;
++ var u = 1 - t,
++ p3x = u * p1x + t * c1x, p3y = u * p1y + t * c1y,
++ p4x = u * c1x + t * c2x, p4y = u * c1y + t * c2y,
++ p5x = u * c2x + t * p2x, p5y = u * c2y + t * p2y,
++ p6x = u * p3x + t * p4x, p6y = u * p3y + t * p4y,
++ p7x = u * p4x + t * p5x, p7y = u * p4y + t * p5y,
++ p8x = u * p6x + t * p7x, p8y = u * p6y + t * p7y;
++ return [
++ [p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y],
++ [p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y]
++ ];
++ },
++
++ solveCubic: function (v, coord, val, roots) {
++ var p1 = v[coord],
++ c1 = v[coord + 2],
++ c2 = v[coord + 4],
++ p2 = v[coord + 6],
++ c = 3 * (c1 - p1),
++ b = 3 * (c2 - c1) - c,
++ a = p2 - p1 - c - b;
++ return Numerical.solveCubic(a, b, c, p1 - val, roots);
++ },
++
++ getParameterOf: function(v, x, y) {
++ if (Math.abs(v[0] - x) < 0.00001
++ && Math.abs(v[1] - y) < 0.00001)
++ return 0;
++ if (Math.abs(v[6] - x) < 0.00001
++ && Math.abs(v[7] - y) < 0.00001)
++ return 1;
++ var txs = [],
++ tys = [],
++ sx = Curve.solveCubic(v, 0, x, txs),
++ sy = Curve.solveCubic(v, 1, y, tys),
++ tx, ty;
++ for (var cx = 0; sx == -1 || cx < sx;) {
++ if (sx == -1 || (tx = txs[cx++]) >= 0 && tx <= 1) {
++ for (var cy = 0; sy == -1 || cy < sy;) {
++ if (sy == -1 || (ty = tys[cy++]) >= 0 && ty <= 1) {
++ if (sx == -1) tx = ty;
++ else if (sy == -1) ty = tx;
++ if (Math.abs(tx - ty) < 0.00001)
++ return (tx + ty) * 0.5;
+ }
+- if (sx == -1)
+- break;
+ }
++ if (sx == -1)
++ break;
+ }
+- return null;
+- },
+-
+- getPart: function(v, from, to) {
+- if (from > 0)
+- v = Curve.subdivide(v, from)[1];
+- if (to < 1)
+- v = Curve.subdivide(v, (to - from) / (1 - from))[0];
+- return v;
+- },
++ }
++ return null;
++ },
+
+- isFlatEnough: function(v) {
+- var p1x = v[0], p1y = v[1],
+- c1x = v[2], c1y = v[3],
+- c2x = v[4], c2y = v[5],
+- p2x = v[6], p2y = v[7],
++ getPart: function(v, from, to) {
++ if (from > 0)
++ v = Curve.subdivide(v, from)[1];
++ if (to < 1)
++ v = Curve.subdivide(v, (to - from) / (1 - from))[0];
++ return v;
++ },
+
+- a = p1y - p2y,
+- b = p2x - p1x,
+- c = p1x * p2y - p2x * p1y,
+- v1 = a * c1x + b * c1y + c,
+- v2 = a * c2x + b * c2y + c;
+- return Math.abs((v1 * v1 + v2 * v2) / (a * (a * a + b * b))) < 0.005;
+- }
+- }
+-}, new function() {
++ isLinear: function(v) {
++ return v[0] === v[2] && v[1] === v[3] && v[4] === v[6] && v[5] === v[7];
++ },
+
+- function getLengthIntegrand(v) {
++ isFlatEnough: function(v, tolerance) {
+ var p1x = v[0], p1y = v[1],
+ c1x = v[2], c1y = v[3],
+ c2x = v[4], c2y = v[5],
+ p2x = v[6], p2y = v[7],
++ ux = 3 * c1x - 2 * p1x - p2x,
++ uy = 3 * c1y - 2 * p1y - p2y,
++ vx = 3 * c2x - 2 * p2x - p1x,
++ vy = 3 * c2y - 2 * p2y - p1y;
++ return Math.max(ux * ux, vx * vx) + Math.max(uy * uy, vy * vy)
++ < 10 * tolerance * tolerance;
++ },
+
+- ax = 9 * (c1x - c2x) + 3 * (p2x - p1x),
+- bx = 6 * (p1x + c2x) - 12 * c1x,
+- cx = 3 * (c1x - p1x),
++ getArea: function(v) {
++ var p1x = v[0], p1y = v[1],
++ c1x = v[2], c1y = v[3],
++ c2x = v[4], c2y = v[5],
++ p2x = v[6], p2y = v[7];
++ return ( 3.0 * c1y * p1x - 1.5 * c1y * c2x
++ - 1.5 * c1y * p2x - 3.0 * p1y * c1x
++ - 1.5 * p1y * c2x - 0.5 * p1y * p2x
++ + 1.5 * c2y * p1x + 1.5 * c2y * c1x
++ - 3.0 * c2y * p2x + 0.5 * p2y * p1x
++ + 1.5 * p2y * c1x + 3.0 * p2y * c2x) / 10;
++ },
++
++ getBounds: function(v) {
++ var min = v.slice(0, 2),
++ max = min.slice(),
++ roots = [0, 0];
++ for (var i = 0; i < 2; i++)
++ Curve._addBounds(v[i], v[i + 2], v[i + 4], v[i + 6],
++ i, 0, min, max, roots);
++ return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);
++ },
++
++ _getCrossings: function(v, prev, x, y, roots) {
++ var count = Curve.solveCubic(v, 1, y, roots),
++ crossings = 0,
++ tolerance = 0.00001,
++ abs = Math.abs;
+
+- ay = 9 * (c1y - c2y) + 3 * (p2y - p1y),
+- by = 6 * (p1y + c2y) - 12 * c1y,
+- cy = 3 * (c1y - p1y);
++ function changesOrientation(tangent) {
++ return Curve.evaluate(prev, 1, 1).y
++ * tangent.y > 0;
++ }
+
+- return function(t) {
+- var dx = (ax * t + bx) * t + cx,
+- dy = (ay * t + by) * t + cy;
+- return Math.sqrt(dx * dx + dy * dy);
+- };
+- }
++ if (count === -1) {
++ roots[0] = Curve.getParameterOf(v, x, y);
++ count = roots[0] !== null ? 1 : 0;
++ }
++ for (var i = 0; i < count; i++) {
++ var t = roots[i];
++ if (t > -tolerance && t < 1 - tolerance) {
++ var pt = Curve.evaluate(v, t, 0);
++ if (x < pt.x + tolerance) {
++ var tan = Curve.evaluate(v, t, 1);
++ if (abs(pt.x - x) < tolerance) {
++ var angle = tan.getAngle();
++ if (angle > -180 && angle < 0
++ && (t > tolerance || changesOrientation(tan)))
++ continue;
++ } else {
++ if (abs(tan.y) < tolerance
++ || t < tolerance && !changesOrientation(tan))
++ continue;
++ }
++ crossings++;
++ }
++ }
++ }
++ return crossings;
++ },
+
+- function getIterations(a, b) {
+- return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32)));
++ _addBounds: function(v0, v1, v2, v3, coord, padding, min, max, roots) {
++ function add(value, padding) {
++ var left = value - padding,
++ right = value + padding;
++ if (left < min[coord])
++ min[coord] = left;
++ if (right > max[coord])
++ max[coord] = right;
++ }
++ var a = 3 * (v1 - v2) - v0 + v3,
++ b = 2 * (v0 + v2) - 4 * v1,
++ c = v1 - v0,
++ count = Numerical.solveQuadratic(a, b, c, roots),
++ tMin = 0.00001,
++ tMax = 1 - tMin;
++ add(v3, 0);
++ for (var i = 0; i < count; i++) {
++ var t = roots[i],
++ u = 1 - t;
++ if (tMin < t && t < tMax)
++ add(u * u * u * v0
++ + 3 * u * u * t * v1
++ + 3 * u * t * t * v2
++ + t * t * t * v3,
++ padding);
++ }
+ }
++}}, Base.each(['getBounds', 'getStrokeBounds', 'getHandleBounds', 'getRoughBounds'],
++ function(name) {
++ this[name] = function() {
++ if (!this._bounds)
++ this._bounds = {};
++ var bounds = this._bounds[name];
++ if (!bounds) {
++ bounds = this._bounds[name] = Path[name]([this._segment1,
++ this._segment2], false, this._path.getStyle());
++ }
++ return bounds.clone();
++ };
++ },
++{
+
+- return {
+- statics: true,
+-
+- getLength: function(v, a, b) {
+- if (a === undefined)
+- a = 0;
+- if (b === undefined)
++}), Base.each(['getPoint', 'getTangent', 'getNormal', 'getCurvature'],
++ function(name, index) {
++ this[name + 'At'] = function(offset, isParameter) {
++ var values = this.getValues();
++ return Curve.evaluate(values, isParameter
++ ? offset : Curve.getParameterAt(values, offset, 0), index);
++ };
++ this[name] = function(parameter) {
++ return Curve.evaluate(this.getValues(), parameter, index);
++ };
++ },
++{
++ getParameterAt: function(offset, start) {
++ return Curve.getParameterAt(this.getValues(), offset,
++ start !== undefined ? start : offset < 0 ? 1 : 0);
++ },
++
++ getParameterOf: function(point) {
++ point = Point.read(arguments);
++ return Curve.getParameterOf(this.getValues(), point.x, point.y);
++ },
++
++ getLocationAt: function(offset, isParameter) {
++ if (!isParameter)
++ offset = this.getParameterAt(offset);
++ return new CurveLocation(this, offset);
++ },
++
++ getLocationOf: function(point) {
++ point = Point.read(arguments);
++ var t = this.getParameterOf(point);
++ return t != null ? new CurveLocation(this, t) : null;
++ },
++
++ getNearestLocation: function(point) {
++ point = Point.read(arguments);
++ var values = this.getValues(),
++ count = 100,
++ tolerance = Numerical.TOLERANCE,
++ minDist = Infinity,
++ minT = 0;
++
++ function refine(t) {
++ if (t >= 0 && t <= 1) {
++ var dist = point.getDistance(
++ Curve.evaluate(values, t, 0), true);
++ if (dist < minDist) {
++ minDist = dist;
++ minT = t;
++ return true;
++ }
++ }
++ }
++
++ for (var i = 0; i <= count; i++)
++ refine(i / count);
++
++ var step = 1 / (count * 2);
++ while (step > tolerance) {
++ if (!refine(minT - step) && !refine(minT + step))
++ step /= 2;
++ }
++ var pt = Curve.evaluate(values, minT, 0);
++ return new CurveLocation(this, minT, pt, null, null, null,
++ point.getDistance(pt));
++ },
++
++ getNearestPoint: function(point) {
++ point = Point.read(arguments);
++ return this.getNearestLocation(point).getPoint();
++ }
++
++}),
++new function() {
++
++ function getLengthIntegrand(v) {
++ var p1x = v[0], p1y = v[1],
++ c1x = v[2], c1y = v[3],
++ c2x = v[4], c2y = v[5],
++ p2x = v[6], p2y = v[7],
++
++ ax = 9 * (c1x - c2x) + 3 * (p2x - p1x),
++ bx = 6 * (p1x + c2x) - 12 * c1x,
++ cx = 3 * (c1x - p1x),
++
++ ay = 9 * (c1y - c2y) + 3 * (p2y - p1y),
++ by = 6 * (p1y + c2y) - 12 * c1y,
++ cy = 3 * (c1y - p1y);
++
++ return function(t) {
++ var dx = (ax * t + bx) * t + cx,
++ dy = (ay * t + by) * t + cy;
++ return Math.sqrt(dx * dx + dy * dy);
++ };
++ }
++
++ function getIterations(a, b) {
++ return Math.max(2, Math.min(16, Math.ceil(Math.abs(b - a) * 32)));
++ }
++
++ return {
++ statics: true,
++
++ getLength: function(v, a, b) {
++ if (a === undefined)
++ a = 0;
++ if (b === undefined)
+ b = 1;
+ if (v[0] == v[2] && v[1] == v[3] && v[6] == v[4] && v[7] == v[5]) {
+ var dx = v[6] - v[0],
+@@ -3704,7 +5078,7 @@ var Curve = this.Curve = Base.extend({
+ },
+
+ getParameterAt: function(v, offset, start) {
+- if (offset == 0)
++ if (offset === 0)
+ return start;
+ var forward = offset > 0,
+ a = forward ? start : 0,
+@@ -3727,164 +5101,260 @@ var Curve = this.Curve = Base.extend({
+ }
+ return Numerical.findRoot(f, ds,
+ forward ? a + guess : b - guess,
+- a, b, 16, Numerical.TOLERANCE);
++ a, b, 16, 0.00001);
+ }
+ };
+ }, new function() {
+-
+- var maxDepth = 32,
+- epsilon = Math.pow(2, -maxDepth - 1);
+-
+- var zCubic = [
+- [1.0, 0.6, 0.3, 0.1],
+- [0.4, 0.6, 0.6, 0.4],
+- [0.1, 0.3, 0.6, 1.0]
+- ];
+-
+- var xAxis = new Line(new Point(0, 0), new Point(1, 0));
+-
+- function toBezierForm(v, point) {
+- var n = 3,
+- degree = 5,
+- c = [],
+- d = [],
+- cd = [],
+- w = [];
+- for(var i = 0; i <= n; i++) {
+- c[i] = v[i].subtract(point);
+- if (i < n)
+- d[i] = v[i + 1].subtract(v[i]).multiply(n);
+- }
+-
+- for (var row = 0; row < n; row++) {
+- cd[row] = [];
+- for (var column = 0; column <= n; column++)
+- cd[row][column] = d[row].dot(c[column]);
+- }
+-
+- for (var i = 0; i <= degree; i++)
+- w[i] = new Point(i / degree, 0);
+-
+- for (k = 0; k <= degree; k++) {
+- var lb = Math.max(0, k - n + 1),
+- ub = Math.min(k, n);
+- for (var i = lb; i <= ub; i++) {
+- var j = k - i;
+- w[k].y += cd[j][i] * zCubic[j][i];
+- }
+- }
+-
+- return w;
++ function addLocation(locations, curve1, t1, point1, curve2, t2, point2) {
++ var first = locations[0],
++ last = locations[locations.length - 1];
++ if ((!first || !point1.equals(first._point))
++ && (!last || !point1.equals(last._point)))
++ locations.push(
++ new CurveLocation(curve1, t1, point1, curve2, t2, point2));
+ }
+
+- function findRoots(w, depth) {
+- switch (countCrossings(w)) {
+- case 0:
+- return [];
+- case 1:
+- if (depth >= maxDepth)
+- return [0.5 * (w[0].x + w[5].x)];
+- if (isFlatEnough(w)) {
+- var line = new Line(w[0], w[5], true);
+- return [ line.vector.getLength(true) <= Numerical.EPSILON
+- ? line.point.x
+- : xAxis.intersect(line).x ];
++ function addCurveIntersections(v1, v2, curve1, curve2, locations,
++ range1, range2, recursion) {
++ recursion = (recursion || 0) + 1;
++ if (recursion > 20)
++ return;
++ range1 = range1 || [ 0, 1 ];
++ range2 = range2 || [ 0, 1 ];
++ var part1 = Curve.getPart(v1, range1[0], range1[1]),
++ part2 = Curve.getPart(v2, range2[0], range2[1]),
++ iteration = 0;
++ while (iteration++ < 20) {
++ var range,
++ intersects1 = clipFatLine(part1, part2, range = range2.slice()),
++ intersects2 = 0;
++ if (intersects1 === 0)
++ break;
++ if (intersects1 > 0) {
++ range2 = range;
++ part2 = Curve.getPart(v2, range2[0], range2[1]);
++ intersects2 = clipFatLine(part2, part1, range = range1.slice());
++ if (intersects2 === 0)
++ break;
++ if (intersects1 > 0) {
++ range1 = range;
++ part1 = Curve.getPart(v1, range1[0], range1[1]);
++ }
++ }
++ if (intersects1 < 0 || intersects2 < 0) {
++ if (range1[1] - range1[0] > range2[1] - range2[0]) {
++ var t = (range1[0] + range1[1]) / 2;
++ addCurveIntersections(v1, v2, curve1, curve2, locations,
++ [ range1[0], t ], range2, recursion);
++ addCurveIntersections(v1, v2, curve1, curve2, locations,
++ [ t, range1[1] ], range2, recursion);
++ break;
++ } else {
++ var t = (range2[0] + range2[1]) / 2;
++ addCurveIntersections(v1, v2, curve1, curve2, locations,
++ range1, [ range2[0], t ], recursion);
++ addCurveIntersections(v1, v2, curve1, curve2, locations,
++ range1, [ t, range2[1] ], recursion);
++ break;
++ }
++ }
++ if (Math.abs(range1[1] - range1[0]) <= 0.00001 &&
++ Math.abs(range2[1] - range2[0]) <= 0.00001) {
++ var t1 = (range1[0] + range1[1]) / 2,
++ t2 = (range2[0] + range2[1]) / 2;
++ addLocation(locations,
++ curve1, t1, Curve.evaluate(v1, t1, 0),
++ curve2, t2, Curve.evaluate(v2, t2, 0));
++ break;
+ }
+ }
++ }
+
+- var p = [[]],
+- left = [],
+- right = [];
+- for (var j = 0; j <= 5; j++)
+- p[0][j] = new Point(w[j]);
+-
+- for (var i = 1; i <= 5; i++) {
+- p[i] = [];
+- for (var j = 0 ; j <= 5 - i; j++)
+- p[i][j] = p[i - 1][j].add(p[i - 1][j + 1]).multiply(0.5);
+- }
+- for (var j = 0; j <= 5; j++) {
+- left[j] = p[j][0];
+- right[j] = p[5 - j][j];
++ function clipFatLine(v1, v2, range2) {
++ var p0x = v1[0], p0y = v1[1], p1x = v1[2], p1y = v1[3],
++ p2x = v1[4], p2y = v1[5], p3x = v1[6], p3y = v1[7],
++ q0x = v2[0], q0y = v2[1], q1x = v2[2], q1y = v2[3],
++ q2x = v2[4], q2y = v2[5], q3x = v2[6], q3y = v2[7],
++ getSignedDistance = Line.getSignedDistance,
++ d1 = getSignedDistance(p0x, p0y, p3x, p3y, p1x, p1y) || 0,
++ d2 = getSignedDistance(p0x, p0y, p3x, p3y, p2x, p2y) || 0,
++ factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9,
++ dmin = factor * Math.min(0, d1, d2),
++ dmax = factor * Math.max(0, d1, d2),
++ dq0 = getSignedDistance(p0x, p0y, p3x, p3y, q0x, q0y),
++ dq1 = getSignedDistance(p0x, p0y, p3x, p3y, q1x, q1y),
++ dq2 = getSignedDistance(p0x, p0y, p3x, p3y, q2x, q2y),
++ dq3 = getSignedDistance(p0x, p0y, p3x, p3y, q3x, q3y);
++ if (dmin > Math.max(dq0, dq1, dq2, dq3)
++ || dmax < Math.min(dq0, dq1, dq2, dq3))
++ return 0;
++ var hull = getConvexHull(dq0, dq1, dq2, dq3),
++ swap;
++ if (dq3 < dq0) {
++ swap = dmin;
++ dmin = dmax;
++ dmax = swap;
++ }
++ var tmaxdmin = -Infinity,
++ tmin = Infinity,
++ tmax = -Infinity;
++ for (var i = 0, l = hull.length; i < l; i++) {
++ var p1 = hull[i],
++ p2 = hull[(i + 1) % l];
++ if (p2[1] < p1[1]) {
++ swap = p2;
++ p2 = p1;
++ p1 = swap;
++ }
++ var x1 = p1[0],
++ y1 = p1[1],
++ x2 = p2[0],
++ y2 = p2[1];
++ var inv = (y2 - y1) / (x2 - x1);
++ if (dmin >= y1 && dmin <= y2) {
++ var ixdx = x1 + (dmin - y1) / inv;
++ if (ixdx < tmin)
++ tmin = ixdx;
++ if (ixdx > tmaxdmin)
++ tmaxdmin = ixdx;
++ }
++ if (dmax >= y1 && dmax <= y2) {
++ var ixdx = x1 + (dmax - y1) / inv;
++ if (ixdx > tmax)
++ tmax = ixdx;
++ if (ixdx < tmin)
++ tmin = 0;
++ }
++ }
++ if (tmin !== Infinity && tmax !== -Infinity) {
++ var min = Math.min(dmin, dmax),
++ max = Math.max(dmin, dmax);
++ if (dq3 > min && dq3 < max)
++ tmax = 1;
++ if (dq0 > min && dq0 < max)
++ tmin = 0;
++ if (tmaxdmin > tmax)
++ tmax = 1;
++ var v2tmin = range2[0],
++ tdiff = range2[1] - v2tmin;
++ range2[0] = v2tmin + tmin * tdiff;
++ range2[1] = v2tmin + tmax * tdiff;
++ if ((tdiff - (range2[1] - range2[0])) / tdiff >= 0.2)
++ return 1;
+ }
+-
+- return findRoots(left, depth + 1).concat(findRoots(right, depth + 1));
++ if (Curve.getBounds(v1).touches(Curve.getBounds(v2)))
++ return -1;
++ return 0;
+ }
+
+- function countCrossings(v) {
+- var crossings = 0,
+- prevSign = null;
+- for (var i = 0, l = v.length; i < l; i++) {
+- var sign = v[i].y < 0 ? -1 : 1;
+- if (prevSign != null && sign != prevSign)
+- crossings++;
+- prevSign = sign;
++ function getConvexHull(dq0, dq1, dq2, dq3) {
++ var p0 = [ 0, dq0 ],
++ p1 = [ 1 / 3, dq1 ],
++ p2 = [ 2 / 3, dq2 ],
++ p3 = [ 1, dq3 ],
++ getSignedDistance = Line.getSignedDistance,
++ dist1 = getSignedDistance(0, dq0, 1, dq3, 1 / 3, dq1),
++ dist2 = getSignedDistance(0, dq0, 1, dq3, 2 / 3, dq2);
++ if (dist1 * dist2 < 0) {
++ return [ p0, p1, p3, p2 ];
++ }
++ var pmax, cross;
++ if (Math.abs(dist1) > Math.abs(dist2)) {
++ pmax = p1;
++ cross = (dq3 - dq2 - (dq3 - dq0) / 3)
++ * (2 * (dq3 - dq2) - dq3 + dq1) / 3;
++ } else {
++ pmax = p2;
++ cross = (dq1 - dq0 + (dq0 - dq3) / 3)
++ * (-2 * (dq0 - dq1) + dq0 - dq2) / 3;
+ }
+- return crossings;
++ return cross < 0
++ ? [ p0, pmax, p3 ]
++ : [ p0, p1, p2, p3 ];
+ }
+
+- function isFlatEnough(v) {
+-
+- var n = v.length - 1,
+- a = v[0].y - v[n].y,
+- b = v[n].x - v[0].x,
+- c = v[0].x * v[n].y - v[n].x * v[0].y,
+- maxAbove = 0,
+- maxBelow = 0;
+- for (var i = 1; i < n; i++) {
+- var val = a * v[i].x + b * v[i].y + c,
+- dist = val * val;
+- if (val < 0 && dist > maxBelow) {
+- maxBelow = dist;
+- } else if (dist > maxAbove) {
+- maxAbove = dist;
++ function addCurveLineIntersections(v1, v2, curve1, curve2, locations) {
++ var flip = Curve.isLinear(v1),
++ vc = flip ? v2 : v1,
++ vl = flip ? v1 : v2,
++ l1x = vl[0], l1y = vl[1],
++ l2x = vl[6], l2y = vl[7],
++ lvx = l2x - l1x,
++ lvy = l2y - l1y,
++ angle = Math.atan2(-lvy, lvx),
++ sin = Math.sin(angle),
++ cos = Math.cos(angle),
++ rl2x = lvx * cos - lvy * sin,
++ vcr = [];
++
++ for(var i = 0; i < 8; i += 2) {
++ var x = vc[i] - l1x,
++ y = vc[i + 1] - l1y;
++ vcr.push(
++ x * cos - y * sin,
++ y * cos + x * sin);
++ }
++ var roots = [],
++ count = Curve.solveCubic(vcr, 1, 0, roots);
++ for (var i = 0; i < count; i++) {
++ var t = roots[i];
++ if (t >= 0 && t <= 1) {
++ var point = Curve.evaluate(vcr, t, 0);
++ if (point.x >= 0 && point.x <= rl2x)
++ addLocation(locations,
++ flip ? curve2 : curve1,
++ t, Curve.evaluate(vc, t, 0),
++ flip ? curve1 : curve2);
+ }
+ }
+- return Math.abs((maxAbove + maxBelow) / (2 * a * (a * a + b * b)))
+- < epsilon;
+ }
+
+- return {
+- getNearestLocation: function(point, matrix) {
+- var w = toBezierForm(this.getPoints(matrix), point);
+- var roots = findRoots(w, 0).concat([0, 1]);
+- var minDist = Infinity,
+- minT,
+- minPoint;
+- for (var i = 0; i < roots.length; i++) {
+- var pt = this.getPoint(roots[i]),
+- dist = point.getDistance(pt, true);
+- if (dist < minDist) {
+- minDist = dist;
+- minT = roots[i];
+- minPoint = pt;
+- }
+- }
+- return new CurveLocation(this, minT, minPoint, Math.sqrt(minDist));
+- },
++ function addLineIntersection(v1, v2, curve1, curve2, locations) {
++ var point = Line.intersect(
++ v1[0], v1[1], v1[6], v1[7],
++ v2[0], v2[1], v2[6], v2[7]);
++ if (point)
++ addLocation(locations, curve1, null, point, curve2);
++ }
+
+- getNearestPoint: function(point, matrix) {
+- return this.getNearestLocation(point, matrix).getPoint();
+- }
+- };
++ return { statics: {
++ getIntersections: function(v1, v2, curve1, curve2, locations) {
++ var linear1 = Curve.isLinear(v1),
++ linear2 = Curve.isLinear(v2);
++ (linear1 && linear2
++ ? addLineIntersection
++ : linear1 || linear2
++ ? addCurveLineIntersections
++ : addCurveIntersections)(v1, v2, curve1, curve2, locations);
++ return locations;
++ }
++ }};
+ });
+
+-CurveLocation = Base.extend({
+- initialize: function(curve, parameter, point, distance) {
++var CurveLocation = Base.extend({
++ _class: 'CurveLocation',
++ initialize: function CurveLocation(curve, parameter, point, _curve2,
++ _parameter2, _point2, _distance) {
++ this._id = CurveLocation._id = (CurveLocation._id || 0) + 1;
+ this._curve = curve;
++ this._segment1 = curve._segment1;
++ this._segment2 = curve._segment2;
+ this._parameter = parameter;
+ this._point = point;
+- this._distance = distance;
++ this._curve2 = _curve2;
++ this._parameter2 = _parameter2;
++ this._point2 = _point2;
++ this._distance = _distance;
+ },
+
+ getSegment: function() {
+ if (!this._segment) {
+- var curve = this._curve,
++ var curve = this.getCurve(),
+ parameter = this.getParameter();
+- if (parameter == 0) {
+- this._segment = curve._segment1;
+- } else if (parameter == 1) {
++ if (parameter === 1) {
+ this._segment = curve._segment2;
++ } else if (parameter === 0 || arguments[0]) {
++ this._segment = curve._segment1;
+ } else if (parameter == null) {
+ return null;
+ } else {
+@@ -3898,59 +5368,92 @@ CurveLocation = Base.extend({
+ },
+
+ getCurve: function() {
++ if (!this._curve || arguments[0]) {
++ this._curve = this._segment1.getCurve();
++ if (this._curve.getParameterOf(this._point) == null)
++ this._curve = this._segment2.getPrevious().getCurve();
++ }
+ return this._curve;
+ },
+
++ getIntersection: function() {
++ var intersection = this._intersection;
++ if (!intersection && this._curve2) {
++ var param = this._parameter2;
++ this._intersection = intersection = new CurveLocation(
++ this._curve2, param, this._point2 || this._point, this);
++ intersection._intersection = this;
++ }
++ return intersection;
++ },
++
+ getPath: function() {
+- return this._curve && this._curve._path;
++ var curve = this.getCurve();
++ return curve && curve._path;
+ },
+
+ getIndex: function() {
+- return this._curve && this._curve.getIndex();
++ var curve = this.getCurve();
++ return curve && curve.getIndex();
+ },
+
+ getOffset: function() {
+- var path = this._curve && this._curve._path;
++ var path = this.getPath();
+ return path && path._getOffset(this);
+ },
+
+ getCurveOffset: function() {
+- var parameter = this.getParameter();
+- return parameter != null && this._curve
+- && this._curve.getLength(0, parameter);
++ var curve = this.getCurve(),
++ parameter = this.getParameter();
++ return parameter != null && curve && curve.getLength(0, parameter);
+ },
+
+ getParameter: function() {
+- if (this._parameter == null && this._curve && this._point)
+- this._parameter = this._curve.getParameterAt(this._point);
++ if ((this._parameter == null || arguments[0]) && this._point) {
++ var curve = this.getCurve(arguments[0] && this._point);
++ this._parameter = curve && curve.getParameterOf(this._point);
++ }
+ return this._parameter;
+ },
+
+ getPoint: function() {
+- if (!this._point && this._curve && this._parameter != null)
+- this._point = this._curve.getPoint(this._parameter);
++ if ((!this._point || arguments[0]) && this._parameter != null) {
++ var curve = this.getCurve();
++ this._point = curve && curve.getPointAt(this._parameter, true);
++ }
+ return this._point;
+ },
+
+ getTangent: function() {
+- var parameter = this.getParameter();
+- return parameter != null && this._curve
+- && this._curve.getTangent(parameter);
++ var parameter = this.getParameter(),
++ curve = this.getCurve();
++ return parameter != null && curve && curve.getTangentAt(parameter, true);
+ },
+
+ getNormal: function() {
+- var parameter = this.getParameter();
+- return parameter != null && this._curve
+- && this._curve.getNormal(parameter);
++ var parameter = this.getParameter(),
++ curve = this.getCurve();
++ return parameter != null && curve && curve.getNormalAt(parameter, true);
+ },
+
+ getDistance: function() {
+ return this._distance;
+ },
+
++ divide: function() {
++ var curve = this.getCurve(true);
++ return curve && curve.divide(this.getParameter(true), true);
++ },
++
++ split: function() {
++ var curve = this.getCurve(true);
++ return curve && curve.split(this.getParameter(true), true);
++ },
++
+ toString: function() {
+ var parts = [],
+- point = this.getPoint();
++ point = this.getPoint(),
++ f = Formatter.instance;
+ if (point)
+ parts.push('point: ' + point);
+ var index = this.getIndex();
+@@ -3958,44 +5461,180 @@ CurveLocation = Base.extend({
+ parts.push('index: ' + index);
+ var parameter = this.getParameter();
+ if (parameter != null)
+- parts.push('parameter: ' + Base.formatNumber(parameter));
++ parts.push('parameter: ' + f.number(parameter));
+ if (this._distance != null)
+- parts.push('distance: ' + Base.formatNumber(this._distance));
++ parts.push('distance: ' + f.number(this._distance));
+ return '{ ' + parts.join(', ') + ' }';
+ }
+ });
+
+-var PathItem = this.PathItem = Item.extend({
++var PathItem = Item.extend({
++ _class: 'PathItem',
++
++ initialize: function PathItem() {
++ },
++
++ getIntersections: function(path) {
++ if (!this.getBounds().touches(path.getBounds()))
++ return [];
++ var locations = [],
++ curves1 = this.getCurves(),
++ curves2 = path.getCurves(),
++ length2 = curves2.length,
++ values2 = [];
++ for (var i = 0; i < length2; i++)
++ values2[i] = curves2[i].getValues();
++ for (var i = 0, l = curves1.length; i < l; i++) {
++ var curve1 = curves1[i],
++ values1 = curve1.getValues();
++ for (var j = 0; j < length2; j++)
++ Curve.getIntersections(values1, values2[j], curve1, curves2[j],
++ locations);
++ }
++ return locations;
++ },
++
++ setPathData: function(data) {
++
++ var parts = data.match(/[a-z][^a-z]*/ig),
++ coords,
++ relative = false,
++ control,
++ current = new Point();
++
++ function getCoord(index, coord, update) {
++ var val = parseFloat(coords[index]);
++ if (relative)
++ val += current[coord];
++ if (update)
++ current[coord] = val;
++ return val;
++ }
++
++ function getPoint(index, update) {
++ return new Point(
++ getCoord(index, 'x', update),
++ getCoord(index + 1, 'y', update)
++ );
++ }
++
++ if (this._type === 'path')
++ this.removeSegments();
++ else
++ this.removeChildren();
++
++ for (var i = 0, l = parts.length; i < l; i++) {
++ var part = parts[i],
++ cmd = part[0],
++ lower = cmd.toLowerCase();
++ coords = part.slice(1).trim().split(/[\s,]+|(?=[+-])/);
++ relative = cmd === lower;
++ var length = coords.length;
++ switch (lower) {
++ case 'm':
++ case 'l':
++ for (var j = 0; j < length; j += 2)
++ this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo'](
++ getPoint(j, true));
++ break;
++ case 'h':
++ case 'v':
++ var coord = lower == 'h' ? 'x' : 'y';
++ for (var j = 0; j < length; j++) {
++ getCoord(j, coord, true);
++ this.lineTo(current);
++ }
++ break;
++ case 'c':
++ for (var j = 0; j < length; j += 6) {
++ this.cubicCurveTo(
++ getPoint(j),
++ control = getPoint(j + 2),
++ getPoint(j + 4, true));
++ }
++ break;
++ case 's':
++ for (var j = 0; j < length; j += 4) {
++ this.cubicCurveTo(
++ current.multiply(2).subtract(control),
++ control = getPoint(j),
++ getPoint(j + 2, true));
++ }
++ break;
++ case 'q':
++ for (var j = 0; j < length; j += 4) {
++ this.quadraticCurveTo(
++ control = getPoint(j),
++ getPoint(j + 2, true));
++ }
++ break;
++ case 't':
++ for (var j = 0; j < length; j += 2) {
++ this.quadraticCurveTo(
++ control = current.multiply(2).subtract(control),
++ getPoint(j, true));
++ }
++ break;
++ case 'a':
++ break;
++ case 'z':
++ this.closePath();
++ break;
++ }
++ }
++ },
++
++ _canComposite: function() {
++ return !(this.hasFill() && this.hasStroke());
++ }
+
+ });
+
+-var Path = this.Path = PathItem.extend({
+- initialize: function(segments) {
+- this.base();
++var Path = PathItem.extend({
++ _class: 'Path',
++ _serializeFields: {
++ segments: [],
++ closed: false
++ },
++
++ initialize: function Path(arg) {
+ this._closed = false;
+- this._selectedSegmentState = 0;
+- this.setSegments(!segments || !Array.isArray(segments)
+- || typeof segments[0] !== 'object' ? arguments : segments);
++ this._segments = [];
++ var segments = Array.isArray(arg)
++ ? typeof arg[0] === 'object'
++ ? arg
++ : arguments
++ : arg && (arg.point !== undefined && arg.size === undefined
++ || arg.x !== undefined)
++ ? arguments
++ : null;
++ this.setSegments(segments || []);
++ this._initialize(!segments && arg);
+ },
+
+- clone: function() {
+- var copy = this._clone(new Path(this._segments));
++ clone: function(insert) {
++ var copy = this._clone(new Path({
++ segments: this._segments,
++ insert: false
++ }), insert);
+ copy._closed = this._closed;
+ if (this._clockwise !== undefined)
+ copy._clockwise = this._clockwise;
+ return copy;
+ },
+
+- _changed: function(flags) {
+- Item.prototype._changed.call(this, flags);
+- if (flags & ChangeFlag.GEOMETRY) {
+- delete this._strokeBounds;
+- delete this._handleBounds;
+- delete this._roughBounds;
++ _changed: function _changed(flags) {
++ _changed.base.call(this, flags);
++ if (flags & 4) {
+ delete this._length;
+ delete this._clockwise;
+- } else if (flags & ChangeFlag.STROKE) {
+- delete this._strokeBounds;
++ if (this._curves) {
++ for (var i = 0, l = this._curves.length; i < l; i++) {
++ this._curves[i]._changed(5);
++ }
++ }
++ } else if (flags & 8) {
++ delete this._bounds;
+ }
+ },
+
+@@ -4004,14 +5643,9 @@ var Path = this.Path = PathItem.extend({
+ },
+
+ setSegments: function(segments) {
+- if (!this._segments) {
+- this._segments = [];
+- } else {
+- this._selectedSegmentState = 0;
+- this._segments.length = 0;
+- if (this._curves)
+- delete this._curves;
+- }
++ this._selectedSegmentState = 0;
++ this._segments.length = 0;
++ delete this._curves;
+ this._add(Segment.readAll(segments));
+ },
+
+@@ -4024,17 +5658,16 @@ var Path = this.Path = PathItem.extend({
+ },
+
+ getCurves: function() {
+- if (!this._curves) {
+- var segments = this._segments,
+- length = segments.length;
+- if (!this._closed && length > 0)
+- length--;
+- this._curves = new Array(length);
++ var curves = this._curves,
++ segments = this._segments;
++ if (!curves) {
++ var length = this._countCurves();
++ curves = this._curves = new Array(length);
+ for (var i = 0; i < length; i++)
+- this._curves[i] = Curve.create(this, segments[i],
++ curves[i] = new Curve(this, segments[i],
+ segments[i + 1] || segments[0]);
+ }
+- return this._curves;
++ return curves;
+ },
+
+ getFirstCurve: function() {
+@@ -4046,7 +5679,42 @@ var Path = this.Path = PathItem.extend({
+ return curves[curves.length - 1];
+ },
+
+- getClosed: function() {
++ getPathData: function() {
++ var segments = this._segments,
++ precision = arguments[0],
++ f = Formatter.instance,
++ parts = [];
++
++ function addCurve(seg1, seg2, skipLine) {
++ var point1 = seg1._point,
++ point2 = seg2._point,
++ handle1 = seg1._handleOut,
++ handle2 = seg2._handleIn;
++ if (handle1.isZero() && handle2.isZero()) {
++ if (!skipLine) {
++ parts.push('L' + f.point(point2, precision));
++ }
++ } else {
++ var end = point2.subtract(point1);
++ parts.push('c' + f.point(handle1, precision)
++ + ' ' + f.point(end.add(handle2), precision)
++ + ' ' + f.point(end, precision));
++ }
++ }
++
++ if (segments.length === 0)
++ return '';
++ parts.push('M' + f.point(segments[0]._point));
++ for (var i = 0, l = segments.length - 1; i < l; i++)
++ addCurve(segments[i], segments[i + 1], false);
++ if (this._closed) {
++ addCurve(segments[segments.length - 1], segments[0], true);
++ parts.push('z');
++ }
++ return parts.join('');
++ },
++
++ isClosed: function() {
+ return this._closed;
+ },
+
+@@ -4054,32 +5722,32 @@ var Path = this.Path = PathItem.extend({
+ if (this._closed != (closed = !!closed)) {
+ this._closed = closed;
+ if (this._curves) {
+- var length = this._segments.length,
+- i;
+- if (!closed && length > 0)
+- length--;
+- this._curves.length = length;
++ var length = this._curves.length = this._countCurves();
+ if (closed)
+- this._curves[i = length - 1] = Curve.create(this,
+- this._segments[i], this._segments[0]);
++ this._curves[length - 1] = new Curve(this,
++ this._segments[length - 1], this._segments[0]);
+ }
+- this._changed(Change.GEOMETRY);
++ this._changed(5);
+ }
+ },
+
+- _transform: function(matrix, flags) {
+- if (!matrix.isIdentity()) {
+- var coords = new Array(6);
+- for (var i = 0, l = this._segments.length; i < l; i++) {
+- this._segments[i]._transformCoordinates(matrix, coords, true);
+- }
+- var fillColor = this.getFillColor(),
+- strokeColor = this.getStrokeColor();
+- if (fillColor && fillColor.transform)
+- fillColor.transform(matrix);
+- if (strokeColor && strokeColor.transform)
+- strokeColor.transform(matrix);
++ isEmpty: function() {
++ return this._segments.length === 0;
++ },
++
++ isPolygon: function() {
++ for (var i = 0, l = this._segments.length; i < l; i++) {
++ if (!this._segments[i].isLinear())
++ return false;
+ }
++ return true;
++ },
++
++ _applyMatrix: function(matrix) {
++ var coords = new Array(6);
++ for (var i = 0, l = this._segments.length; i < l; i++)
++ this._segments[i]._transformCoordinates(matrix, coords, true);
++ return true;
+ },
+
+ _add: function(segs, index) {
+@@ -4091,13 +5759,12 @@ var Path = this.Path = PathItem.extend({
+ fullySelected = this.isFullySelected();
+ for (var i = 0; i < amount; i++) {
+ var segment = segs[i];
+- if (segment._path) {
+- segment = segs[i] = new Segment(segment);
+- }
++ if (segment._path)
++ segment = segs[i] = segment.clone();
+ segment._path = this;
+ segment._index = index + i;
+ if (fullySelected)
+- segment._selectionState = SelectionState.POINT;
++ segment._selectionState = 4;
+ if (segment._selectionState)
+ this._updateSelection(segment, 0, segment._selectionState);
+ }
+@@ -4105,22 +5772,49 @@ var Path = this.Path = PathItem.extend({
+ segments.push.apply(segments, segs);
+ } else {
+ segments.splice.apply(segments, [index, 0].concat(segs));
+- for (var i = index + amount, l = segments.length; i < l; i++) {
++ for (var i = index + amount, l = segments.length; i < l; i++)
+ segments[i]._index = i;
+- }
+- }
+- if (curves && --index >= 0) {
+- curves.splice(index, 0, Curve.create(this, segments[index],
+- segments[index + 1]));
+- var curve = curves[index + amount];
+- if (curve) {
+- curve._segment1 = segments[index + amount];
+- }
+ }
+- this._changed(Change.GEOMETRY);
++ if (curves || segs._curves) {
++ if (!curves)
++ curves = this._curves = [];
++ var from = index > 0 ? index - 1 : index,
++ start = from,
++ to = Math.min(from + amount, this._countCurves());
++ if (segs._curves) {
++ curves.splice.apply(curves, [from, 0].concat(segs._curves));
++ start += segs._curves.length;
++ }
++ for (var i = start; i < to; i++)
++ curves.splice(i, 0, new Curve(this, null, null));
++ this._adjustCurves(from, to);
++ }
++ this._changed(5);
+ return segs;
+ },
+
++ _adjustCurves: function(from, to) {
++ var segments = this._segments,
++ curves = this._curves,
++ curve;
++ for (var i = from; i < to; i++) {
++ curve = curves[i];
++ curve._path = this;
++ curve._segment1 = segments[i];
++ curve._segment2 = segments[i + 1] || segments[0];
++ }
++ if (curve = curves[this._closed && from === 0 ? segments.length - 1
++ : from - 1])
++ curve._segment2 = segments[from] || segments[0];
++ if (curve = curves[to])
++ curve._segment1 = segments[to];
++ },
++
++ _countCurves: function() {
++ var length = this._segments.length;
++ return !this._closed && length > 0 ? length - 1 : length;
++ },
++
+ add: function(segment1 ) {
+ return arguments.length > 1 && typeof segment1 !== 'number'
+ ? this._add(Segment.readAll(arguments))
+@@ -4133,11 +5827,11 @@ var Path = this.Path = PathItem.extend({
+ : this._add([ Segment.read(arguments, 1) ], index)[0];
+ },
+
+- addSegment: function(segment) {
++ addSegment: function() {
+ return this._add([ Segment.read(arguments) ])[0];
+ },
+
+- insertSegment: function(index, segment) {
++ insertSegment: function(index ) {
+ return this._add([ Segment.read(arguments, 1) ], index)[0];
+ },
+
+@@ -4150,16 +5844,15 @@ var Path = this.Path = PathItem.extend({
+ },
+
+ removeSegment: function(index) {
+- var segments = this.removeSegments(index, index + 1);
+- return segments[0] || null;
++ return this.removeSegments(index, index + 1)[0] || null;
+ },
+
+ removeSegments: function(from, to) {
+ from = from || 0;
+- to = Base.pick(to, this._segments.length);
++ to = Base.pick(to, this._segments.length);
+ var segments = this._segments,
+ curves = this._curves,
+- last = to >= segments.length,
++ count = segments.length,
+ removed = segments.splice(from, to - from),
+ amount = removed.length;
+ if (!amount)
+@@ -4168,37 +5861,48 @@ var Path = this.Path = PathItem.extend({
+ var segment = removed[i];
+ if (segment._selectionState)
+ this._updateSelection(segment, segment._selectionState, 0);
+- removed._index = removed._path = undefined;
++ delete segment._index;
++ delete segment._path;
+ }
+ for (var i = from, l = segments.length; i < l; i++)
+ segments[i]._index = i;
+ if (curves) {
+- curves.splice(from, amount);
+- var curve;
+- if (curve = curves[from - 1])
+- curve._segment2 = segments[from];
+- if (curve = curves[from])
+- curve._segment1 = segments[from];
+- if (last && this._closed && (curve = curves[curves.length - 1]))
+- curve._segment2 = segments[0];
+- }
+- this._changed(Change.GEOMETRY);
++ var index = from > 0 && to === count + (this._closed ? 1 : 0)
++ ? from - 1
++ : from,
++ curves = curves.splice(index, amount);
++ if (arguments[2])
++ removed._curves = curves.slice(1);
++ this._adjustCurves(index, index);
++ }
++ this._changed(5);
+ return removed;
+ },
+
+ isFullySelected: function() {
+ return this._selected && this._selectedSegmentState
+- == this._segments.length * SelectionState.POINT;
++ == this._segments.length * 4;
+ },
+
+ setFullySelected: function(selected) {
++ if (selected)
++ this._selectSegments(true);
++ this.setSelected(selected);
++ },
++
++ setSelected: function setSelected(selected) {
++ if (!selected)
++ this._selectSegments(false);
++ setSelected.base.call(this, selected);
++ },
++
++ _selectSegments: function(selected) {
+ var length = this._segments.length;
+ this._selectedSegmentState = selected
+- ? length * SelectionState.POINT : 0;
++ ? length * 4 : 0;
+ for (var i = 0; i < length; i++)
+ this._segments[i]._selectionState = selected
+- ? SelectionState.POINT : 0;
+- this.setSelected(selected);
++ ? 4 : 0;
+ },
+
+ _updateSelection: function(segment, oldState, newState) {
+@@ -4228,37 +5932,50 @@ var Path = this.Path = PathItem.extend({
+ }
+ },
+
++ split: function(index, parameter) {
++ if (parameter === null)
++ return;
++ if (arguments.length === 1) {
++ var arg = index;
++ if (typeof arg === 'number')
++ arg = this.getLocationAt(arg);
++ index = arg.index;
++ parameter = arg.parameter;
++ }
++ if (parameter >= 1) {
++ index++;
++ parameter--;
++ }
++ var curves = this.getCurves();
++ if (index >= 0 && index < curves.length) {
++ if (parameter > 0) {
++ curves[index++].divide(parameter, true);
++ }
++ var segs = this.removeSegments(index, this._segments.length, true),
++ path;
++ if (this._closed) {
++ this.setClosed(false);
++ path = this;
++ } else if (index > 0) {
++ path = this._clone(new Path().insertAbove(this, true));
++ }
++ path._add(segs, 0);
++ this.addSegment(segs[0]);
++ return path;
++ }
++ return null;
++ },
++
+ isClockwise: function() {
+ if (this._clockwise !== undefined)
+ return this._clockwise;
+- var sum = 0,
+- xPre, yPre;
+- function edge(x, y) {
+- if (xPre !== undefined)
+- sum += (xPre - x) * (y + yPre);
+- xPre = x;
+- yPre = y;
+- }
+- for (var i = 0, l = this._segments.length; i < l; i++) {
+- var seg1 = this._segments[i],
+- seg2 = this._segments[i + 1 < l ? i + 1 : 0],
+- point1 = seg1._point,
+- handle1 = seg1._handleOut,
+- handle2 = seg2._handleIn,
+- point2 = seg2._point;
+- edge(point1._x, point1._y);
+- edge(point1._x + handle1._x, point1._y + handle1._y);
+- edge(point2._x + handle2._x, point2._y + handle2._y);
+- edge(point2._x, point2._y);
+- }
+- return sum > 0;
++ return Path.isClockwise(this._segments);
+ },
+
+ setClockwise: function(clockwise) {
+- if (this.isClockwise() != (clockwise = !!clockwise)) {
++ if (this.isClockwise() != (clockwise = !!clockwise))
+ this.reverse();
+- this._clockwise = clockwise;
+- }
++ this._clockwise = clockwise;
+ },
+
+ reverse: function() {
+@@ -4268,7 +5985,9 @@ var Path = this.Path = PathItem.extend({
+ var handleIn = segment._handleIn;
+ segment._handleIn = segment._handleOut;
+ segment._handleOut = handleIn;
++ segment._index = i;
+ }
++ delete this._curves;
+ if (this._clockwise !== undefined)
+ this._clockwise = !this._clockwise;
+ },
+@@ -4280,12 +5999,13 @@ var Path = this.Path = PathItem.extend({
+ last2 = path.getLastSegment();
+ if (last1._point.equals(last2._point))
+ path.reverse();
+- var first2 = path.getFirstSegment();
++ var first1,
++ first2 = path.getFirstSegment();
+ if (last1._point.equals(first2._point)) {
+ last1.setHandleOut(first2._handleOut);
+ this._add(segments.slice(1));
+ } else {
+- var first1 = this.getFirstSegment();
++ first1 = this.getFirstSegment();
+ if (first1._point.equals(first2._point))
+ path.reverse();
+ last2 = path.getLastSegment();
+@@ -4293,23 +6013,29 @@ var Path = this.Path = PathItem.extend({
+ first1.setHandleIn(last2._handleIn);
+ this._add(segments.slice(0, segments.length - 1), 0);
+ } else {
+- this._add(segments.slice(0));
++ this._add(segments.slice());
+ }
+ }
++ if (path.closed)
++ this._add([segments[0]]);
+ path.remove();
+- var first1 = this.getFirstSegment();
++ first1 = this.getFirstSegment();
+ last1 = this.getLastSegment();
+ if (last1._point.equals(first1._point)) {
+ first1.setHandleIn(last1._handleIn);
+ last1.remove();
+ this.setClosed(true);
+ }
+- this._changed(Change.GEOMETRY);
++ this._changed(5);
+ return true;
+ }
+ return false;
+ },
+
++ reduce: function() {
++ return this;
++ },
++
+ getLength: function() {
+ if (this._length == null) {
+ var curves = this.getCurves();
+@@ -4320,6 +6046,14 @@ var Path = this.Path = PathItem.extend({
+ return this._length;
+ },
+
++ getArea: function() {
++ var curves = this.getCurves();
++ var area = 0;
++ for (var i = 0, l = curves.length; i < l; i++)
++ area += curves[i].getArea();
++ return area;
++ },
++
+ _getOffset: function(location) {
+ var index = location && location.getIndex();
+ if (index != null) {
+@@ -4333,13 +6067,13 @@ var Path = this.Path = PathItem.extend({
+ return null;
+ },
+
+- getLocation: function(point) {
++ getLocationOf: function(point) {
++ point = Point.read(arguments);
+ var curves = this.getCurves();
+ for (var i = 0, l = curves.length; i < l; i++) {
+- var curve = curves[i];
+- var t = curve.getParameter(point);
+- if (t != null)
+- return new CurveLocation(curve, t);
++ var loc = curves[i].getLocationOf(point);
++ if (loc)
++ return loc;
+ }
+ return null;
+ },
+@@ -4349,15 +6083,14 @@ var Path = this.Path = PathItem.extend({
+ length = 0;
+ if (isParameter) {
+ var index = ~~offset;
+- return new CurveLocation(curves[index], offset - index);
++ return curves[index].getLocationAt(offset - index, true);
+ }
+ for (var i = 0, l = curves.length; i < l; i++) {
+ var start = length,
+ curve = curves[i];
+ length += curve.getLength();
+ if (length >= offset) {
+- return new CurveLocation(curve,
+- curve.getParameterAt(offset - start));
++ return curve.getLocationAt(offset - start);
+ }
+ }
+ if (offset <= this.getLength())
+@@ -4380,12 +6113,13 @@ var Path = this.Path = PathItem.extend({
+ return loc && loc.getNormal();
+ },
+
+- getNearestLocation: function(point, matrix) {
++ getNearestLocation: function(point) {
++ point = Point.read(arguments);
+ var curves = this.getCurves(),
+ minDist = Infinity,
+ minLoc = null;
+ for (var i = 0, l = curves.length; i < l; i++) {
+- var loc = curves[i].getNearestLocation(point, matrix);
++ var loc = curves[i].getNearestLocation(point);
+ if (loc._distance < minDist) {
+ minDist = loc._distance;
+ minLoc = loc;
+@@ -4394,81 +6128,197 @@ var Path = this.Path = PathItem.extend({
+ return minLoc;
+ },
+
+- getNearestPoint: function(point, matrix) {
+- return this.getNearestLocation(point, matrix).getPoint();
++ getNearestPoint: function(point) {
++ point = Point.read(arguments);
++ return this.getNearestLocation(point).getPoint();
+ },
+
+- contains: function(point, matrix) {
+- point = Point.read(arguments);
+- if (!this._closed || !this.getRoughBounds(matrix)._containsPoint(point))
++ getStyle: function() {
++ var parent = this._parent;
++ return (parent && parent._type === 'compound-path'
++ ? parent : this)._style;
++ },
++
++ _contains: function(point) {
++ var closed = this._closed;
++ if (!closed && !this.hasFill()
++ || !this._getBounds('getRoughBounds')._containsPoint(point))
+ return false;
+ var curves = this.getCurves(),
++ segments = this._segments,
+ crossings = 0,
+- roots = [];
+- for (var i = 0, l = curves.length; i < l; i++)
+- crossings += curves[i].getCrossings(point, matrix, roots);
+- return (crossings & 1) == 1;
+- },
++ roots = new Array(3),
++ last = (closed
++ ? curves[curves.length - 1]
++ : new Curve(segments[segments.length - 1]._point,
++ segments[0]._point)).getValues(),
++ previous = last;
++ for (var i = 0, l = curves.length; i < l; i++) {
++ var vals = curves[i].getValues(),
++ x = vals[0],
++ y = vals[1];
++ if (!(x === vals[2] && y === vals[3] && x === vals[4]
++ && y === vals[5] && x === vals[6] && y === vals[7])) {
++ crossings += Curve._getCrossings(vals, previous,
++ point.x, point.y, roots);
++ previous = vals;
++ }
++ }
++ if (!closed) {
++ crossings += Curve._getCrossings(last, previous, point.x, point.y,
++ roots);
++ }
++ return (crossings & 1) === 1;
++ },
++
++ _hitTest: function(point, options) {
++ var style = this.getStyle(),
++ segments = this._segments,
++ closed = this._closed,
++ tolerance = options.tolerance || 0,
++ radius = 0, join, cap, miterLimit,
++ that = this,
++ area, loc, res;
++
++ if (options.stroke && style.getStrokeColor()) {
++ join = style.getStrokeJoin();
++ cap = style.getStrokeCap();
++ radius = style.getStrokeWidth() / 2 + tolerance;
++ miterLimit = radius * style.getMiterLimit();
++ }
++
++ function checkPoint(seg, pt, name) {
++ if (point.getDistance(pt) < tolerance)
++ return new HitResult(name, that, { segment: seg, point: pt });
++ }
++
++ function checkSegmentPoints(seg, ends) {
++ var pt = seg._point;
++ return (ends || options.segments) && checkPoint(seg, pt, 'segment')
++ || (!ends && options.handles) && (
++ checkPoint(seg, pt.add(seg._handleIn), 'handle-in') ||
++ checkPoint(seg, pt.add(seg._handleOut), 'handle-out'));
++ }
++
++ function addAreaPoint(point) {
++ area.push(point);
++ }
++
++ function getAreaCurve(index) {
++ var p1 = area[index],
++ p2 = area[(index + 1) % area.length];
++ return [p1.x, p1.y, p1.x, p1.y, p2.x, p2.y, p2.x ,p2.y];
++ }
++
++ function isInArea(point) {
++ var length = area.length,
++ previous = getAreaCurve(length - 1),
++ roots = new Array(3),
++ crossings = 0;
++ for (var i = 0; i < length; i++) {
++ var curve = getAreaCurve(i);
++ crossings += Curve._getCrossings(curve, previous,
++ point.x, point.y, roots);
++ previous = curve;
++ }
++ return (crossings & 1) === 1;
++ }
++
++ function checkSegmentStroke(segment) {
++ if (join !== 'round' || cap !== 'round') {
++ area = [];
++ if (closed || segment._index > 0
++ && segment._index < segments.length - 1) {
++ if (join !== 'round' && (segment._handleIn.isZero()
++ || segment._handleOut.isZero()))
++ Path._addSquareJoin(segment, join, radius, miterLimit,
++ addAreaPoint, true);
++ } else if (cap !== 'round') {
++ Path._addSquareCap(segment, cap, radius, addAreaPoint, true);
++ }
++ if (area.length > 0)
++ return isInArea(point);
++ }
++ return point.getDistance(segment._point) <= radius;
++ }
+
+- _hitTest: function(point, options, matrix) {
+- var tolerance = options.tolerance || 0,
+- radius = (options.stroke ? this.getStrokeWidth() / 2 : 0) + tolerance,
+- loc,
+- res;
+- var coords = [],
+- that = this;
+- function checkSegment(segment, ends) {
+- segment._transformCoordinates(matrix, coords);
+- for (var j = ends || options.segments ? 0 : 2,
+- m = !ends && options.handles ? 6 : 2; j < m; j += 2) {
+- if (point.getDistance(coords[j], coords[j + 1]) < tolerance)
+- return new HitResult(j == 0 ? 'segment'
+- : 'handle-' + (j == 2 ? 'in' : 'out'),
+- that, { segment: segment });
+- }
+- }
+- if (options.ends && !options.segments && !this._closed) {
+- if (res = checkSegment(this.getFirstSegment(), true)
+- || checkSegment(this.getLastSegment(), true))
++ if (options.ends && !options.segments && !closed) {
++ if (res = checkSegmentPoints(segments[0], true)
++ || checkSegmentPoints(segments[segments.length - 1], true))
+ return res;
+ } else if (options.segments || options.handles) {
+- for (var i = 0, l = this._segments.length; i < l; i++) {
+- if (res = checkSegment(this._segments[i]))
++ for (var i = 0, l = segments.length; i < l; i++) {
++ if (res = checkSegmentPoints(segments[i]))
+ return res;
+ }
+ }
+- if (options.stroke && radius > 0)
+- loc = this.getNearestLocation(point, matrix);
+- if (!(loc && loc._distance <= radius) && options.fill
+- && this.getFillColor() && this.contains(point, matrix))
+- return new HitResult('fill', this);
+- if (!loc && options.stroke && radius > 0)
+- loc = this.getNearestLocation(point, matrix);
+- if (loc && loc._distance <= radius)
+- return options.stroke
++ if (radius > 0) {
++ loc = this.getNearestLocation(point);
++ if (loc) {
++ var parameter = loc.getParameter();
++ if (parameter === 0 || parameter === 1) {
++ if (!checkSegmentStroke(loc.getSegment()))
++ loc = null;
++ } else if (loc._distance > radius) {
++ loc = null;
++ }
++ }
++ if (!loc && join === 'miter') {
++ for (var i = 0, l = segments.length; i < l; i++) {
++ var segment = segments[i];
++ if (point.getDistance(segment._point) <= miterLimit
++ && checkSegmentStroke(segment)) {
++ loc = segment.getLocation();
++ break;
++ }
++ }
++ }
++ }
++ return !loc && options.fill && this.hasFill() && this.contains(point)
++ ? new HitResult('fill', this)
++ : loc
+ ? new HitResult('stroke', this, { location: loc })
+- : new HitResult('fill', this);
++ : null;
+ }
+
+ }, new function() {
+
+- function drawHandles(ctx, segments) {
++ function drawHandles(ctx, segments, matrix, size) {
++ var half = size / 2;
++
++ function drawHandle(index) {
++ var hX = coords[index],
++ hY = coords[index + 1];
++ if (pX != hX || pY != hY) {
++ ctx.beginPath();
++ ctx.moveTo(pX, pY);
++ ctx.lineTo(hX, hY);
++ ctx.stroke();
++ ctx.beginPath();
++ ctx.arc(hX, hY, half, 0, Math.PI * 2, true);
++ ctx.fill();
++ }
++ }
++
++ var coords = new Array(6);
+ for (var i = 0, l = segments.length; i < l; i++) {
+- var segment = segments[i],
+- point = segment._point,
+- state = segment._selectionState,
+- selected = state & SelectionState.POINT;
+- if (selected || (state & SelectionState.HANDLE_IN))
+- drawHandle(ctx, point, segment._handleIn);
+- if (selected || (state & SelectionState.HANDLE_OUT))
+- drawHandle(ctx, point, segment._handleOut);
++ var segment = segments[i];
++ segment._transformCoordinates(matrix, coords, false);
++ var state = segment._selectionState,
++ selected = state & 4,
++ pX = coords[0],
++ pY = coords[1];
++ if (selected || (state & 1))
++ drawHandle(2);
++ if (selected || (state & 2))
++ drawHandle(4);
+ ctx.save();
+ ctx.beginPath();
+- ctx.rect(point._x - 2, point._y - 2, 4, 4);
++ ctx.rect(pX - half, pY - half, size, size);
+ ctx.fill();
+ if (!selected) {
+ ctx.beginPath();
+- ctx.rect(point._x - 1, point._y - 1, 2, 2);
++ ctx.rect(pX - half + 1, pY - half + 1, size - 2, size - 2);
+ ctx.fillStyle = '#ffffff';
+ ctx.fill();
+ }
+@@ -4476,44 +6326,55 @@ var Path = this.Path = PathItem.extend({
+ }
+ }
+
+- function drawHandle(ctx, point, handle) {
+- if (!handle.isZero()) {
+- var handleX = point._x + handle._x,
+- handleY = point._y + handle._y;
+- ctx.beginPath();
+- ctx.moveTo(point._x, point._y);
+- ctx.lineTo(handleX, handleY);
+- ctx.stroke();
+- ctx.beginPath();
+- ctx.arc(handleX, handleY, 1.75, 0, Math.PI * 2, true);
+- ctx.fill();
+- }
+- }
+-
+- function drawSegments(ctx, path) {
++ function drawSegments(ctx, path, matrix) {
+ var segments = path._segments,
+ length = segments.length,
+- handleOut, outX, outY;
++ coords = new Array(6),
++ first = true,
++ curX, curY,
++ prevX, prevY,
++ inX, inY,
++ outX, outY;
+
+ function drawSegment(i) {
+- var segment = segments[i],
+- point = segment._point,
+- x = point._x,
+- y = point._y,
+- handleIn = segment._handleIn;
+- if (!handleOut) {
+- ctx.moveTo(x, y);
++ var segment = segments[i];
++ if (matrix) {
++ segment._transformCoordinates(matrix, coords, false);
++ curX = coords[0];
++ curY = coords[1];
++ } else {
++ var point = segment._point;
++ curX = point._x;
++ curY = point._y;
++ }
++ if (first) {
++ ctx.moveTo(curX, curY);
++ first = false;
+ } else {
+- if (handleIn.isZero() && handleOut.isZero()) {
+- ctx.lineTo(x, y);
++ if (matrix) {
++ inX = coords[2];
++ inY = coords[3];
++ } else {
++ var handle = segment._handleIn;
++ inX = curX + handle._x;
++ inY = curY + handle._y;
++ }
++ if (inX == curX && inY == curY && outX == prevX && outY == prevY) {
++ ctx.lineTo(curX, curY);
+ } else {
+- ctx.bezierCurveTo(outX, outY,
+- handleIn._x + x, handleIn._y + y, x, y);
++ ctx.bezierCurveTo(outX, outY, inX, inY, curX, curY);
+ }
+ }
+- handleOut = segment._handleOut;
+- outX = handleOut._x + x;
+- outY = handleOut._y + y;
++ prevX = curX;
++ prevY = curY;
++ if (matrix) {
++ outX = coords[4];
++ outY = coords[5];
++ } else {
++ var handle = segment._handleOut;
++ outX = prevX + handle._x;
++ outY = prevY + handle._y;
++ }
+ }
+
+ for (var i = 0; i < length; i++)
+@@ -4522,56 +6383,53 @@ var Path = this.Path = PathItem.extend({
+ drawSegment(0);
+ }
+
+- function drawDashes(ctx, path, dashArray, dashOffset) {
+- var flattener = new PathFlattener(path),
+- from = dashOffset, to,
+- i = 0;
+- while (from < flattener.length) {
+- to = from + dashArray[(i++) % dashArray.length];
+- flattener.drawPart(ctx, from, to);
+- from = to + dashArray[(i++) % dashArray.length];
+- }
+- }
+-
+ return {
+- draw: function(ctx, param) {
+- if (!param.compound)
++ _draw: function(ctx, param) {
++ var clip = param.clip,
++ compound = param.compound;
++ if (!compound)
+ ctx.beginPath();
+
+- var fillColor = this.getFillColor(),
+- strokeColor = this.getStrokeColor(),
+- dashArray = this.getDashArray() || [],
+- hasDash = !!dashArray.length;
++ var style = this.getStyle(),
++ fillColor = style.getFillColor(),
++ strokeColor = style.getStrokeColor(),
++ dashArray = style.getDashArray(),
++ drawDash = !paper.support.nativeDash && strokeColor
++ && dashArray && dashArray.length;
+
+- if (param.compound || param.selection || this._clipMask || fillColor
+- || strokeColor && !hasDash) {
++ if (fillColor || strokeColor && !drawDash || compound || clip)
+ drawSegments(ctx, this);
+- }
+
+- if (param.selection) {
+- ctx.stroke();
+- drawHandles(ctx, this._segments);
+- } else if (this._clipMask) {
+- ctx.clip();
+- } else if (!param.compound && (fillColor || strokeColor)) {
+- ctx.save();
++ if (this._closed)
++ ctx.closePath();
++
++ if (!clip && !compound && (fillColor || strokeColor)) {
+ this._setStyles(ctx);
+- if (!fillColor || !strokeColor)
+- ctx.globalAlpha = this._opacity;
+- if (fillColor) {
+- ctx.fillStyle = fillColor.getCanvasStyle(ctx);
++ if (fillColor)
+ ctx.fill();
+- }
+ if (strokeColor) {
+- ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
+- if (hasDash) {
++ if (drawDash) {
+ ctx.beginPath();
+- drawDashes(ctx, this, dashArray, this.getDashOffset());
++ var flattener = new PathFlattener(this),
++ from = style.getDashOffset(), to,
++ i = 0;
++ while (from < flattener.length) {
++ to = from + dashArray[(i++) % dashArray.length];
++ flattener.drawPart(ctx, from, to);
++ from = to + dashArray[(i++) % dashArray.length];
++ }
+ }
+ ctx.stroke();
+ }
+- ctx.restore();
+ }
++ },
++
++ _drawSelected: function(ctx, matrix) {
++ ctx.beginPath();
++ drawSegments(ctx, this, matrix);
++ ctx.stroke();
++ drawHandles(ctx, this._segments, matrix,
++ this._project.options.handleSize || 4);
+ }
+ };
+ }, new function() {
+@@ -4591,24 +6449,9 @@ var Path = this.Path = PathItem.extend({
+ x[n - i - 1] -= tmp[n - i] * x[n - i];
+ }
+ return x;
+- };
+-
+- var styles = {
+- getStrokeWidth: 'lineWidth',
+- getStrokeJoin: 'lineJoin',
+- getStrokeCap: 'lineCap',
+- getMiterLimit: 'miterLimit'
+- };
++ }
+
+ return {
+- _setStyles: function(ctx) {
+- for (var i in styles) {
+- var style = this._style[i]();
+- if (style)
+- ctx[styles[i]] = style;
+- }
+- },
+-
+ smooth: function() {
+ var segments = this._segments,
+ size = segments.length,
+@@ -4651,11 +6494,12 @@ var Path = this.Path = PathItem.extend({
+
+ if (this._closed) {
+ for (var i = 0, j = size; i < overlap; i++, j++) {
+- var f1 = (i / overlap);
+- var f2 = 1 - f1;
++ var f1 = i / overlap,
++ f2 = 1 - f1,
++ ie = i + overlap,
++ je = j + overlap;
+ x[j] = x[i] * f1 + x[j] * f2;
+ y[j] = y[i] * f1 + y[j] * f2;
+- var ie = i + overlap, je = j + overlap;
+ x[je] = x[ie] * f2 + x[je] * f1;
+ y[je] = y[ie] * f2 + y[je] * f1;
+ }
+@@ -4694,43 +6538,45 @@ var Path = this.Path = PathItem.extend({
+ }
+
+ return {
+- moveTo: function(point) {
++ moveTo: function() {
++ if (this._segments.length === 1)
++ this.removeSegment(0);
+ if (!this._segments.length)
+ this._add([ new Segment(Point.read(arguments)) ]);
+ },
+
+- moveBy: function(point) {
++ moveBy: function() {
+ throw new Error('moveBy() is unsupported on Path items.');
+ },
+
+- lineTo: function(point) {
++ lineTo: function() {
+ this._add([ new Segment(Point.read(arguments)) ]);
+ },
+
+- cubicCurveTo: function(handle1, handle2, to) {
+- handle1 = Point.read(arguments, 0, 1);
+- handle2 = Point.read(arguments, 1, 1);
+- to = Point.read(arguments, 2, 1);
++ cubicCurveTo: function() {
++ var handle1 = Point.read(arguments),
++ handle2 = Point.read(arguments),
++ to = Point.read(arguments);
+ var current = getCurrentSegment(this);
+ current.setHandleOut(handle1.subtract(current._point));
+ this._add([ new Segment(to, handle2.subtract(to)) ]);
+ },
+
+- quadraticCurveTo: function(handle, to) {
+- handle = Point.read(arguments, 0, 1);
+- to = Point.read(arguments, 1, 1);
++ quadraticCurveTo: function() {
++ var handle = Point.read(arguments),
++ to = Point.read(arguments);
+ var current = getCurrentSegment(this)._point;
+ this.cubicCurveTo(
+- handle.add(current.subtract(handle).multiply(1/3)),
+- handle.add(to.subtract(handle).multiply(1/3)),
++ handle.add(current.subtract(handle).multiply(1 / 3)),
++ handle.add(to.subtract(handle).multiply(1 / 3)),
+ to
+ );
+ },
+
+- curveTo: function(through, to, parameter) {
+- through = Point.read(arguments, 0, 1);
+- to = Point.read(arguments, 1, 1);
+- var t = Base.pick(parameter, 0.5),
++ curveTo: function() {
++ var through = Point.read(arguments),
++ to = Point.read(arguments),
++ t = Base.pick(Base.read(arguments), 0.5),
+ t1 = 1 - t,
+ current = getCurrentSegment(this)._point,
+ handle = through.subtract(current.multiply(t1 * t1))
+@@ -4744,24 +6590,25 @@ var Path = this.Path = PathItem.extend({
+ arcTo: function(to, clockwise ) {
+ var current = getCurrentSegment(this),
+ from = current._point,
+- through;
+- if (clockwise === undefined)
+- clockwise = true;
+- if (typeof clockwise === 'boolean') {
+- to = Point.read(arguments, 0, 1);
++ through,
++ point = Point.read(arguments),
++ next = Base.pick(Base.peek(arguments), true);
++ if (typeof next === 'boolean') {
++ to = point;
++ clockwise = next;
+ var middle = from.add(to).divide(2),
+ through = middle.add(middle.subtract(from).rotate(
+ clockwise ? -90 : 90));
+ } else {
+- through = Point.read(arguments, 0, 1);
+- to = Point.read(arguments, 1, 1);
++ through = point;
++ to = Point.read(arguments);
+ }
+ var l1 = new Line(from.add(through).divide(2),
+- through.subtract(from).rotate(90)),
+- l2 = new Line(through.add(to).divide(2),
+- to.subtract(through).rotate(90)),
+- center = l1.intersect(l2),
+- line = new Line(from, to, true),
++ through.subtract(from).rotate(90), true),
++ l2 = new Line(through.add(to).divide(2),
++ to.subtract(through).rotate(90), true),
++ center = l1.intersect(l2, true),
++ line = new Line(from, to),
+ throughSide = line.getSide(through);
+ if (!center) {
+ if (!throughSide)
+@@ -4770,7 +6617,6 @@ var Path = this.Path = PathItem.extend({
+ + [from, through, to]);
+ }
+ var vector = from.subtract(center),
+- radius = vector.getLength(),
+ extent = vector.getDirectedAngle(to.subtract(center)),
+ centerSide = line.getSide(center);
+ if (centerSide == 0) {
+@@ -4816,378 +6662,358 @@ var Path = this.Path = PathItem.extend({
+ throughVector = Point.read(throughVector);
+ toVector = Point.read(toVector);
+ var current = getCurrentSegment(this)._point;
+- this.arcBy(current.add(throughVector), current.add(toVector));
++ this.arcTo(current.add(throughVector), current.add(toVector));
+ },
+
+ closePath: function() {
++ var first = this.getFirstSegment(),
++ last = this.getLastSegment();
++ if (first._point.equals(last._point)) {
++ first.setHandleIn(last._handleIn);
++ last.remove();
++ }
+ this.setClosed(true);
+ }
+ };
+-}, new function() {
++}, {
++
++ _getBounds: function(getter, matrix) {
++ return Path[getter](this._segments, this._closed, this.getStyle(),
++ matrix);
++ },
+
+- function getBounds(that, matrix, strokePadding) {
+- var segments = that._segments,
+- first = segments[0];
++statics: {
++ isClockwise: function(segments) {
++ var sum = 0,
++ xPre, yPre,
++ add = false;
++ function edge(x, y) {
++ if (add)
++ sum += (xPre - x) * (y + yPre);
++ xPre = x;
++ yPre = y;
++ add = true;
++ }
++ for (var i = 0, l = segments.length; i < l; i++) {
++ var seg1 = segments[i],
++ seg2 = segments[i + 1 < l ? i + 1 : 0],
++ point1 = seg1._point,
++ handle1 = seg1._handleOut,
++ handle2 = seg2._handleIn,
++ point2 = seg2._point;
++ edge(point1._x, point1._y);
++ edge(point1._x + handle1._x, point1._y + handle1._y);
++ edge(point2._x + handle2._x, point2._y + handle2._y);
++ edge(point2._x, point2._y);
++ }
++ return sum > 0;
++ },
++
++ getBounds: function(segments, closed, style, matrix, strokePadding) {
++ var first = segments[0];
+ if (!first)
+- return null;
++ return new Rectangle();
+ var coords = new Array(6),
+- prevCoords = new Array(6);
+- if (matrix && matrix.isIdentity())
+- matrix = null;
+- first._transformCoordinates(matrix, prevCoords, false);
+- var min = prevCoords.slice(0, 2),
+- max = min.slice(0),
+- tMin = Numerical.TOLERANCE,
+- tMax = 1 - tMin;
++ prevCoords = first._transformCoordinates(matrix, new Array(6), false),
++ min = prevCoords.slice(0, 2),
++ max = min.slice(),
++ roots = new Array(2);
++
+ function processSegment(segment) {
+ segment._transformCoordinates(matrix, coords, false);
+-
+ for (var i = 0; i < 2; i++) {
+- var v0 = prevCoords[i],
+- v1 = prevCoords[i + 4],
+- v2 = coords[i + 2],
+- v3 = coords[i];
+-
+- function add(value, t) {
+- var padding = 0;
+- if (value == null) {
+- var u = 1 - t;
+- value = u * u * u * v0
+- + 3 * u * u * t * v1
+- + 3 * u * t * t * v2
+- + t * t * t * v3;
+- padding = strokePadding ? strokePadding[i] : 0;
+- }
+- var left = value - padding,
+- right = value + padding;
+- if (left < min[i])
+- min[i] = left;
+- if (right > max[i])
+- max[i] = right;
+-
+- }
+- add(v3, null);
+-
+- var a = 3 * (v1 - v2) - v0 + v3,
+- b = 2 * (v0 + v2) - 4 * v1,
+- c = v1 - v0;
+-
+- if (a == 0) {
+- if (b == 0)
+- continue;
+- var t = -c / b;
+- if (tMin < t && t < tMax)
+- add(null, t);
+- continue;
+- }
+-
+- var q = b * b - 4 * a * c;
+- if (q < 0)
+- continue;
+- var sqrt = Math.sqrt(q),
+- f = -0.5 / a,
+- t1 = (b - sqrt) * f,
+- t2 = (b + sqrt) * f;
+- if (tMin < t1 && t1 < tMax)
+- add(null, t1);
+- if (tMin < t2 && t2 < tMax)
+- add(null, t2);
++ Curve._addBounds(
++ prevCoords[i],
++ prevCoords[i + 4],
++ coords[i + 2],
++ coords[i],
++ i, strokePadding ? strokePadding[i] : 0, min, max, roots);
+ }
+ var tmp = prevCoords;
+ prevCoords = coords;
+ coords = tmp;
+ }
++
+ for (var i = 1, l = segments.length; i < l; i++)
+ processSegment(segments[i]);
+- if (that._closed)
++ if (closed)
+ processSegment(first);
+- return Rectangle.create(min[0], min[1],
+- max[0] - min[0], max[1] - min[1]);
+- }
+-
+- function getPenPadding(radius, matrix) {
+- if (!matrix)
+- return [radius, radius];
+- var mx = matrix.createShiftless(),
+- hor = mx.transform(new Point(radius, 0)),
+- ver = mx.transform(new Point(0, radius)),
+- phi = hor.getAngleInRadians(),
+- a = hor.getLength(),
+- b = ver.getLength();
+- var tx = - Math.atan(b * Math.tan(phi)),
+- ty = + Math.atan(b / Math.tan(phi)),
+- x = a * Math.cos(tx) * Math.cos(phi)
+- - b * Math.sin(tx) * Math.sin(phi),
+- y = b * Math.sin(ty) * Math.cos(phi)
+- + a * Math.cos(ty) * Math.sin(phi);
+- return [Math.abs(x), Math.abs(y)];
+- }
+-
+- return {
+- getBounds: function() {
+- var useCache = arguments[0] === undefined;
+- if (useCache && this._bounds)
+- return this._bounds;
+- var bounds = this._createBounds(getBounds(this, arguments[0]));
+- if (useCache)
+- this._bounds = bounds;
+- return bounds;
+- },
+-
+- getStrokeBounds: function() {
+- if (!this._style._strokeColor || !this._style._strokeWidth)
+- return this.getBounds.apply(this, arguments);
+- var useCache = arguments[0] === undefined;
+- if (useCache && this._strokeBounds)
+- return this._strokeBounds;
+- var matrix = arguments[0],
+- width = this.getStrokeWidth(),
+- radius = width / 2,
+- padding = getPenPadding(radius, matrix),
+- join = this.getStrokeJoin(),
+- cap = this.getStrokeCap(),
+- miter = this.getMiterLimit() * width / 2,
+- segments = this._segments,
+- length = segments.length,
+- bounds = getBounds(this, matrix, getPenPadding(radius));
+- var joinBounds = new Rectangle(new Size(padding).multiply(2));
+-
+- function add(point) {
+- bounds = bounds.include(matrix
+- ? matrix.transform(point) : point);
+- }
+-
+- function addBevelJoin(curve, t) {
+- var point = curve.getPoint(t),
+- normal = curve.getNormal(t).normalize(radius);
+- add(point.add(normal));
+- add(point.subtract(normal));
+- }
+-
+- function addJoin(segment, join) {
+- if (join === 'round' || !segment._handleIn.isZero()
+- && !segment._handleOut.isZero()) {
+- bounds = bounds.unite(joinBounds.setCenter(matrix
+- ? matrix.transform(segment._point) : segment._point));
+- } else if (join == 'bevel') {
+- var curve = segment.getCurve();
+- addBevelJoin(curve, 0);
+- addBevelJoin(curve.getPrevious(), 1);
+- } else if (join == 'miter') {
+- var curve2 = segment.getCurve(),
+- curve1 = curve2.getPrevious(),
+- point = curve2.getPoint(0),
+- normal1 = curve1.getNormal(1).normalize(radius),
+- normal2 = curve2.getNormal(0).normalize(radius),
+- line1 = new Line(point.subtract(normal1),
+- new Point(-normal1.y, normal1.x)),
+- line2 = new Line(point.subtract(normal2),
+- new Point(-normal2.y, normal2.x)),
+- corner = line1.intersect(line2);
+- if (!corner || point.getDistance(corner) > miter) {
+- addJoin(segment, 'bevel');
+- } else {
+- add(corner);
+- }
+- }
++ return new Rectangle(min[0], min[1], max[0] - min[0], max[1] - min[1]);
++ },
++
++ getStrokeBounds: function(segments, closed, style, matrix) {
++ function getPenPadding(radius, matrix) {
++ if (!matrix)
++ return [radius, radius];
++ var mx = matrix.shiftless(),
++ hor = mx.transform(new Point(radius, 0)),
++ ver = mx.transform(new Point(0, radius)),
++ phi = hor.getAngleInRadians(),
++ a = hor.getLength(),
++ b = ver.getLength();
++ var sin = Math.sin(phi),
++ cos = Math.cos(phi),
++ tan = Math.tan(phi),
++ tx = -Math.atan(b * tan / a),
++ ty = Math.atan(b / (tan * a));
++ return [Math.abs(a * Math.cos(tx) * cos - b * Math.sin(tx) * sin),
++ Math.abs(b * Math.sin(ty) * cos + a * Math.cos(ty) * sin)];
++ }
++
++ if (!style.getStrokeColor() || !style.getStrokeWidth())
++ return Path.getBounds(segments, closed, style, matrix);
++ var length = segments.length - (closed ? 0 : 1),
++ radius = style.getStrokeWidth() / 2,
++ padding = getPenPadding(radius, matrix),
++ bounds = Path.getBounds(segments, closed, style, matrix, padding),
++ join = style.getStrokeJoin(),
++ cap = style.getStrokeCap(),
++ miterLimit = radius * style.getMiterLimit();
++ var joinBounds = new Rectangle(new Size(padding).multiply(2));
++
++ function add(point) {
++ bounds = bounds.include(matrix
++ ? matrix._transformPoint(point, point) : point);
++ }
++
++ function addJoin(segment, join) {
++ if (join === 'round' || !segment._handleIn.isZero()
++ && !segment._handleOut.isZero()) {
++ bounds = bounds.unite(joinBounds.setCenter(matrix
++ ? matrix._transformPoint(segment._point) : segment._point));
++ } else {
++ Path._addSquareJoin(segment, join, radius, miterLimit, add);
+ }
++ }
+
+- function addCap(segment, cap, t) {
+- switch (cap) {
+- case 'round':
+- return addJoin(segment, cap);
+- case 'butt':
+- case 'square':
+- var curve = segment.getCurve(),
+- point = curve.getPoint(t),
+- normal = curve.getNormal(t).normalize(radius);
+- if (cap === 'square')
+- point = point.add(normal.y, -normal.x);
+- add(point.add(normal));
+- add(point.subtract(normal));
+- break;
+- }
++ function addCap(segment, cap) {
++ switch (cap) {
++ case 'round':
++ addJoin(segment, cap);
++ break;
++ case 'butt':
++ case 'square':
++ Path._addSquareCap(segment, cap, radius, add);
++ break;
+ }
++ }
+
+- for (var i = 1, l = length - (this._closed ? 0 : 1); i < l; i++) {
+- addJoin(segments[i], join);
+- }
+- if (this._closed) {
+- addJoin(segments[0], join);
+- } else {
+- addCap(segments[0], cap, 0);
+- addCap(segments[length - 1], cap, 1);
+- }
+- if (useCache)
+- this._strokeBounds = bounds;
+- return bounds;
+- },
+-
+- getHandleBounds: function() {
+- var matrix = arguments[0],
+- useCache = matrix === undefined;
+- if (useCache && this._handleBounds)
+- return this._handleBounds;
+- var coords = new Array(6),
+- stroke = arguments[1] / 2 || 0,
+- join = arguments[2] / 2 || 0,
+- open = !this._closed,
+- x1 = Infinity,
+- x2 = -x1,
+- y1 = x1,
+- y2 = x2;
+- for (var i = 0, l = this._segments.length; i < l; i++) {
+- var segment = this._segments[i];
+- segment._transformCoordinates(matrix, coords, false);
+- for (var j = 0; j < 6; j += 2) {
+- var padding = j == 0 ? join : stroke,
+- x = coords[j],
+- y = coords[j + 1],
+- xn = x - padding,
+- xx = x + padding,
+- yn = y - padding,
+- yx = y + padding;
+- if (xn < x1) x1 = xn;
+- if (xx > x2) x2 = xx;
+- if (yn < y1) y1 = yn;
+- if (yx > y2) y2 = yx;
+- }
++ for (var i = 1; i < length; i++)
++ addJoin(segments[i], join);
++ if (closed) {
++ addJoin(segments[0], join);
++ } else {
++ addCap(segments[0], cap);
++ addCap(segments[segments.length - 1], cap);
++ }
++ return bounds;
++ },
++
++ _addSquareJoin: function(segment, join, radius, miterLimit, addPoint, area) {
++ var curve2 = segment.getCurve(),
++ curve1 = curve2.getPrevious(),
++ point = curve2.getPointAt(0, true),
++ normal1 = curve1.getNormalAt(1, true),
++ normal2 = curve2.getNormalAt(0, true),
++ step = normal1.getDirectedAngle(normal2) < 0 ? -radius : radius;
++ normal1.setLength(step);
++ normal2.setLength(step);
++ if (area) {
++ addPoint(point);
++ addPoint(point.add(normal1));
++ }
++ if (join === 'miter') {
++ var corner = new Line(
++ point.add(normal1),
++ new Point(-normal1.y, normal1.x), true
++ ).intersect(new Line(
++ point.add(normal2),
++ new Point(-normal2.y, normal2.x), true
++ ), true);
++ if (corner && point.getDistance(corner) <= miterLimit) {
++ addPoint(corner);
++ if (!area)
++ return;
+ }
+- var bounds = Rectangle.create(x1, y1, x2 - x1, y2 - y1);
+- if (useCache)
+- this._handleBounds = bounds;
+- return bounds;
+- },
++ }
++ if (!area)
++ addPoint(point.add(normal1));
++ addPoint(point.add(normal2));
++ },
+
+- getRoughBounds: function() {
+- var useCache = arguments[0] === undefined;
+- if (useCache && this._roughBounds)
+- return this._roughBounds;
+- var bounds = this.getHandleBounds(arguments[0], this.strokeWidth,
+- this.getStrokeJoin() == 'miter'
+- ? this.strokeWidth * this.getMiterLimit()
+- : this.strokeWidth);
+- if (useCache)
+- this._roughBounds = bounds;
+- return bounds;
++ _addSquareCap: function(segment, cap, radius, addPoint, area) {
++ var point = segment._point,
++ loc = segment.getLocation(),
++ normal = loc.getNormal().normalize(radius);
++ if (area) {
++ addPoint(point.subtract(normal));
++ addPoint(point.add(normal));
+ }
+- };
+-});
++ if (cap === 'square')
++ point = point.add(normal.rotate(loc.getParameter() == 0 ? -90 : 90));
++ addPoint(point.add(normal));
++ addPoint(point.subtract(normal));
++ },
++
++ getHandleBounds: function(segments, closed, style, matrix, strokePadding,
++ joinPadding) {
++ var coords = new Array(6),
++ x1 = Infinity,
++ x2 = -x1,
++ y1 = x1,
++ y2 = x2;
++ strokePadding = strokePadding / 2 || 0;
++ joinPadding = joinPadding / 2 || 0;
++ for (var i = 0, l = segments.length; i < l; i++) {
++ var segment = segments[i];
++ segment._transformCoordinates(matrix, coords, false);
++ for (var j = 0; j < 6; j += 2) {
++ var padding = j == 0 ? joinPadding : strokePadding,
++ x = coords[j],
++ y = coords[j + 1],
++ xn = x - padding,
++ xx = x + padding,
++ yn = y - padding,
++ yx = y + padding;
++ if (xn < x1) x1 = xn;
++ if (xx > x2) x2 = xx;
++ if (yn < y1) y1 = yn;
++ if (yx > y2) y2 = yx;
++ }
++ }
++ return new Rectangle(x1, y1, x2 - x1, y2 - y1);
++ },
++
++ getRoughBounds: function(segments, closed, style, matrix) {
++ var strokeWidth = style.getStrokeColor() ? style.getStrokeWidth() : 0,
++ joinWidth = strokeWidth;
++ if (strokeWidth > 0) {
++ if (style.getStrokeJoin() === 'miter')
++ joinWidth = strokeWidth * style.getMiterLimit();
++ if (style.getStrokeCap() === 'square')
++ joinWidth = Math.max(joinWidth, strokeWidth * Math.sqrt(2));
++ }
++ return Path.getHandleBounds(segments, closed, style, matrix,
++ strokeWidth, joinWidth);
++ }
++}});
+
+ Path.inject({ statics: new function() {
+- var kappa = 2 / 3 * (Math.sqrt(2) - 1);
+
+- var ovalSegments = [
++ function createPath(args) {
++ return new Path(Base.getNamed(args));
++ }
++
++ function createRectangle() {
++ var rect = Rectangle.readNamed(arguments, 'rectangle'),
++ radius = Size.readNamed(arguments, 'radius', 0, 0,
++ { readNull: true }),
++ bl = rect.getBottomLeft(true),
++ tl = rect.getTopLeft(true),
++ tr = rect.getTopRight(true),
++ br = rect.getBottomRight(true),
++ path = createPath(arguments);
++ if (!radius || radius.isZero()) {
++ path._add([
++ new Segment(bl),
++ new Segment(tl),
++ new Segment(tr),
++ new Segment(br)
++ ]);
++ } else {
++ radius = Size.min(radius, rect.getSize(true).divide(2));
++ var h = radius.multiply(kappa * 2);
++ path._add([
++ new Segment(bl.add(radius.width, 0), null, [-h.width, 0]),
++ new Segment(bl.subtract(0, radius.height), [0, h.height], null),
++
++ new Segment(tl.add(0, radius.height), null, [0, -h.height]),
++ new Segment(tl.add(radius.width, 0), [-h.width, 0], null),
++
++ new Segment(tr.subtract(radius.width, 0), null, [h.width, 0]),
++ new Segment(tr.add(0, radius.height), [0, -h.height], null),
++
++ new Segment(br.subtract(0, radius.height), null, [0, h.height]),
++ new Segment(br.subtract(radius.width, 0), [h.width, 0], null)
++ ]);
++ }
++ path._closed = true;
++ return path;
++ }
++
++ var kappa = Numerical.KAPPA / 2;
++
++ var ellipseSegments = [
+ new Segment([0, 0.5], [0, kappa ], [0, -kappa]),
+ new Segment([0.5, 0], [-kappa, 0], [kappa, 0 ]),
+ new Segment([1, 0.5], [0, -kappa], [0, kappa ]),
+ new Segment([0.5, 1], [kappa, 0 ], [-kappa, 0])
+ ];
+
++ function createEllipse() {
++ var rect = Rectangle.readNamed(arguments, 'rectangle'),
++ path = createPath(arguments),
++ point = rect.getPoint(true),
++ size = rect.getSize(true),
++ segments = new Array(4);
++ for (var i = 0; i < 4; i++) {
++ var segment = ellipseSegments[i];
++ segments[i] = new Segment(
++ segment._point.multiply(size).add(point),
++ segment._handleIn.multiply(size),
++ segment._handleOut.multiply(size)
++ );
++ }
++ path._add(segments);
++ path._closed = true;
++ return path;
++ }
++
+ return {
+ Line: function() {
+- var step = Math.floor(arguments.length / 2);
+ return new Path(
+- Segment.read(arguments, 0, step),
+- Segment.read(arguments, step, step)
+- );
++ Point.readNamed(arguments, 'from'),
++ Point.readNamed(arguments, 'to')
++ ).set(Base.getNamed(arguments));
+ },
+
+- Rectangle: function(rect) {
+- rect = Rectangle.read(arguments);
+- var left = rect.x,
+- top = rect.y
+- right = left + rect.width,
+- bottom = top + rect.height,
+- path = new Path();
+- path._add([
+- new Segment(Point.create(left, bottom)),
+- new Segment(Point.create(left, top)),
+- new Segment(Point.create(right, top)),
+- new Segment(Point.create(right, bottom))
+- ]);
+- path._closed = true;
+- return path;
++ Circle: function() {
++ var center = Point.readNamed(arguments, 'center'),
++ radius = Base.readNamed(arguments, 'radius');
++ return createEllipse(new Rectangle(center.subtract(radius),
++ new Size(radius * 2, radius * 2)))
++ .set(Base.getNamed(arguments));
+ },
+
+- RoundRectangle: function(rect, size) {
+- if (arguments.length == 2) {
+- rect = Rectangle.read(arguments, 0, 1);
+- size = Size.read(arguments, 1, 1);
+- } else if (arguments.length == 6) {
+- rect = Rectangle.read(arguments, 0, 4);
+- size = Size.read(arguments, 4, 2);
+- }
+- size = Size.min(size, rect.getSize(true).divide(2));
+- var path = new Path(),
+- uSize = size.multiply(kappa * 2),
+- bl = rect.getBottomLeft(true),
+- tl = rect.getTopLeft(true),
+- tr = rect.getTopRight(true),
+- br = rect.getBottomRight(true);
+- path._add([
+- new Segment(bl.add(size.width, 0), null, [-uSize.width, 0]),
+- new Segment(bl.subtract(0, size.height), [0, uSize.height], null),
+-
+- new Segment(tl.add(0, size.height), null, [0, -uSize.height]),
+- new Segment(tl.add(size.width, 0), [-uSize.width, 0], null),
+-
+- new Segment(tr.subtract(size.width, 0), null, [uSize.width, 0]),
+- new Segment(tr.add(0, size.height), [0, -uSize.height], null),
++ Rectangle: createRectangle,
+
+- new Segment(br.subtract(0, size.height), null, [0, uSize.height]),
+- new Segment(br.subtract(size.width, 0), [uSize.width, 0], null)
+- ]);
+- path._closed = true;
+- return path;
+- },
++ RoundRectangle: createRectangle,
+
+- Oval: function(rect) {
+- rect = Rectangle.read(arguments);
+- var path = new Path(),
+- point = rect.getPoint(true),
+- size = rect.getSize(true),
+- segments = new Array(4);
+- for (var i = 0; i < 4; i++) {
+- var segment = ovalSegments[i];
+- segments[i] = new Segment(
+- segment._point.multiply(size).add(point),
+- segment._handleIn.multiply(size),
+- segment._handleOut.multiply(size)
+- );
+- }
+- path._add(segments);
+- path._closed = true;
+- return path;
+- },
++ Ellipse: createEllipse,
+
+- Circle: function(center, radius) {
+- if (arguments.length == 3) {
+- center = Point.read(arguments, 0, 2);
+- radius = arguments[2];
+- } else {
+- center = Point.read(arguments, 0, 1);
+- }
+- return Path.Oval(new Rectangle(center.subtract(radius),
+- Size.create(radius * 2, radius * 2)));
+- },
++ Oval: createEllipse,
+
+- Arc: function(from, through, to) {
+- var path = new Path();
++ Arc: function() {
++ var from = Point.readNamed(arguments, 'from'),
++ through = Point.readNamed(arguments, 'through'),
++ to = Point.readNamed(arguments, 'to'),
++ path = createPath(arguments);
+ path.moveTo(from);
+ path.arcTo(through, to);
+ return path;
+ },
+
+- RegularPolygon: function(center, numSides, radius) {
+- center = Point.read(arguments, 0, 1);
+- var path = new Path(),
+- step = 360 / numSides,
+- three = !(numSides % 3),
++ RegularPolygon: function() {
++ var center = Point.readNamed(arguments, 'center'),
++ sides = Base.readNamed(arguments, 'sides'),
++ radius = Base.readNamed(arguments, 'radius'),
++ path = createPath(arguments),
++ step = 360 / sides,
++ three = !(sides % 3),
+ vector = new Point(0, three ? -radius : radius),
+ offset = three ? -1 : 0.5,
+- segments = new Array(numSides);
+- for (var i = 0; i < numSides; i++) {
++ segments = new Array(sides);
++ for (var i = 0; i < sides; i++) {
+ segments[i] = new Segment(center.add(
+ vector.rotate((i + offset) * step)));
+ }
+@@ -5196,14 +7022,16 @@ Path.inject({ statics: new function() {
+ return path;
+ },
+
+- Star: function(center, numPoints, radius1, radius2) {
+- center = Point.read(arguments, 0, 1);
+- numPoints *= 2;
+- var path = new Path(),
+- step = 360 / numPoints,
++ Star: function() {
++ var center = Point.readNamed(arguments, 'center'),
++ points = Base.readNamed(arguments, 'points') * 2,
++ radius1 = Base.readNamed(arguments, 'radius1'),
++ radius2 = Base.readNamed(arguments, 'radius2'),
++ path = createPath(arguments),
++ step = 360 / points,
+ vector = new Point(0, -1),
+- segments = new Array(numPoints);
+- for (var i = 0; i < numPoints; i++) {
++ segments = new Array(points);
++ for (var i = 0; i < points; i++) {
+ segments[i] = new Segment(center.add(
+ vector.rotate(step * i).multiply(i % 2 ? radius2 : radius1)));
+ }
+@@ -5214,23 +7042,30 @@ Path.inject({ statics: new function() {
+ };
+ }});
+
+-var CompoundPath = this.CompoundPath = PathItem.extend({
+- initialize: function(paths) {
+- this.base();
++var CompoundPath = PathItem.extend({
++ _class: 'CompoundPath',
++ _serializeFields: {
++ children: []
++ },
++
++ initialize: function CompoundPath(arg) {
+ this._children = [];
+ this._namedChildren = {};
+- var items = !paths || !Array.isArray(paths)
+- || typeof paths[0] !== 'object' ? arguments : paths;
+- this.addChildren(items);
++ if (!this._initialize(arg))
++ this.addChildren(Array.isArray(arg) ? arg : arguments);
+ },
+
+- insertChild: function(index, item) {
+- this.base(index, item);
+- if (item._clockwise === undefined)
+- item.setClockwise(item._index == 0);
++ insertChildren: function insertChildren(index, items, _preserve) {
++ items = insertChildren.base.call(this, index, items, _preserve, 'path');
++ for (var i = 0, l = !_preserve && items && items.length; i < l; i++) {
++ var item = items[i];
++ if (item._clockwise === undefined)
++ item.setClockwise(item._index === 0);
++ }
++ return items;
+ },
+
+- simplify: function() {
++ reduce: function() {
+ if (this._children.length == 1) {
+ var child = this._children[0];
+ child.insertAbove(this);
+@@ -5240,33 +7075,107 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
+ return this;
+ },
+
++ reverse: function() {
++ var children = this._children;
++ for (var i = 0, l = children.length; i < l; i++)
++ children[i].reverse();
++ },
++
+ smooth: function() {
+ for (var i = 0, l = this._children.length; i < l; i++)
+ this._children[i].smooth();
+ },
+
+- draw: function(ctx, param) {
+- var l = this._children.length;
+- if (l == 0) {
+- return;
++ isClockwise: function() {
++ var child = this.getFirstChild();
++ return child && child.isClockwise();
++ },
++
++ setClockwise: function(clockwise) {
++ if (this.isClockwise() != !!clockwise)
++ this.reverse();
++ },
++
++ getFirstSegment: function() {
++ var first = this.getFirstChild();
++ return first && first.getFirstSegment();
++ },
++
++ getLastSegment: function() {
++ var last = this.getLastChild();
++ return last && last.getLastSegment();
++ },
++
++ getCurves: function() {
++ var children = this._children,
++ curves = [];
++ for (var i = 0, l = children.length; i < l; i++)
++ curves = curves.concat(children[i].getCurves());
++ return curves;
++ },
++
++ getFirstCurve: function() {
++ var first = this.getFirstChild();
++ return first && first.getFirstCurve();
++ },
++
++ getLastCurve: function() {
++ var last = this.getLastChild();
++ return last && last.getFirstCurve();
++ },
++
++ getArea: function() {
++ var children = this._children,
++ area = 0;
++ for (var i = 0, l = children.length; i < l; i++)
++ area += children[i].getArea();
++ return area;
++ },
++
++ getPathData: function() {
++ var children = this._children,
++ paths = [];
++ for (var i = 0, l = children.length; i < l; i++)
++ paths.push(children[i].getPathData(arguments[0]));
++ return paths.join(' ');
++ },
++
++ _contains: function(point) {
++ var children = [];
++ for (var i = 0, l = this._children.length; i < l; i++) {
++ var child = this._children[i];
++ if (child.contains(point))
++ children.push(child);
+ }
+- var firstChild = this._children[0];
+- ctx.beginPath();
+- param.compound = true;
+- for (var i = 0; i < l; i++)
+- Item.draw(this._children[i], ctx, param);
+- firstChild._setStyles(ctx);
+- var fillColor = firstChild.getFillColor(),
+- strokeColor = firstChild.getStrokeColor();
+- if (fillColor) {
+- ctx.fillStyle = fillColor.getCanvasStyle(ctx);
+- ctx.fill();
++ return (children.length & 1) == 1 && children;
++ },
++
++ _hitTest: function _hitTest(point, options) {
++ var res = _hitTest.base.call(this, point,
++ Base.merge(options, { fill: false }));
++ if (!res && options.fill && this.hasFill()) {
++ res = this._contains(point);
++ res = res ? new HitResult('fill', res[0]) : null;
+ }
+- if (strokeColor) {
+- ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
+- ctx.stroke();
++ return res;
++ },
++
++ _draw: function(ctx, param) {
++ var children = this._children,
++ style = this._style;
++ if (children.length === 0)
++ return;
++ ctx.beginPath();
++ param = param.extend({ compound: true });
++ for (var i = 0, l = children.length; i < l; i++)
++ children[i].draw(ctx, param);
++ if (!param.clip) {
++ this._setStyles(ctx);
++ if (style.getFillColor())
++ ctx.fill();
++ if (style.getStrokeColor())
++ ctx.stroke();
+ }
+- param.compound = false;
+ }
+ }, new function() {
+ function getCurrentPath(that) {
+@@ -5276,19 +7185,19 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
+ }
+
+ var fields = {
+- moveTo: function(point) {
++ moveTo: function() {
+ var path = new Path();
+ this.addChild(path);
+ path.moveTo.apply(path, arguments);
+ },
+
+- moveBy: function(point) {
++ moveBy: function() {
+ this.moveTo(getCurrentPath(this).getLastSegment()._point.add(
+ Point.read(arguments)));
+ },
+
+ closePath: function() {
+- getCurrentPath(this).setClosed(true);
++ getCurrentPath(this).closePath();
+ }
+ };
+
+@@ -5331,7 +7240,7 @@ var PathFlattener = Base.extend({
+ },
+
+ _computeParts: function(curve, index, minT, maxT) {
+- if ((maxT - minT) > 1 / 32 && !Curve.isFlatEnough(curve)) {
++ if ((maxT - minT) > 1 / 32 && !Curve.isFlatEnough(curve, 0.25)) {
+ var curves = Curve.subdivide(curve);
+ var halfT = (minT + maxT) / 2;
+ this._computeParts(curves[0], index, minT, halfT);
+@@ -5340,7 +7249,7 @@ var PathFlattener = Base.extend({
+ var x = curve[6] - curve[0],
+ y = curve[7] - curve[1],
+ dist = Math.sqrt(x * x + y * y);
+- if (dist > Numerical.TOLERANCE) {
++ if (dist > 0.00001) {
+ this.length += dist;
+ this.parts.push({
+ offset: this.length,
+@@ -5414,11 +7323,13 @@ var PathFitter = Base.extend({
+ },
+
+ fit: function() {
+- this.segments = [new Segment(this.points[0])];
+- this.fitCubic(0, this.points.length - 1,
+- this.points[1].subtract(this.points[0]).normalize(),
+- this.points[this.points.length - 2].subtract(
+- this.points[this.points.length - 1]).normalize());
++ var points = this.points,
++ length = points.length;
++ this.segments = length > 0 ? [new Segment(points[0])] : [];
++ if (length > 1)
++ this.fitCubic(0, length - 1,
++ points[1].subtract(points[0]).normalize(),
++ points[length - 2].subtract(points[length - 1]).normalize());
+ return this.segments;
+ },
+
+@@ -5433,7 +7344,6 @@ var PathFitter = Base.extend({
+ }
+ var uPrime = this.chordLengthParameterize(first, last),
+ maxError = Math.max(this.error, this.error * this.error),
+- error,
+ split;
+ for (var i = 0; i <= 4; i++) {
+ var curve = this.generateBezier(first, last, uPrime, tan1, tan2);
+@@ -5463,10 +7373,10 @@ var PathFitter = Base.extend({
+ },
+
+ generateBezier: function(first, last, uPrime, tan1, tan2) {
+- var epsilon = Numerical.EPSILON,
++ var epsilon = 1e-11,
+ pt1 = this.points[first],
+ pt2 = this.points[last],
+- C = [[0, 0], [0, 0]],
++ C = [[0, 0], [0, 0]],
+ X = [0, 0];
+
+ for (var i = 0, l = last - first + 1; i < l; i++) {
+@@ -5502,10 +7412,10 @@ var PathFitter = Base.extend({
+ c1 = C[1][0] + C[1][1];
+ if (Math.abs(c0) > epsilon) {
+ alpha1 = alpha2 = X[0] / c0;
+- } else if (Math.abs(c0) > epsilon) {
++ } else if (Math.abs(c1) > epsilon) {
+ alpha1 = alpha2 = X[1] / c1;
+ } else {
+- alpha1 = alpha2 = 0.;
++ alpha1 = alpha2 = 0;
+ }
+ }
+
+@@ -5535,11 +7445,11 @@ var PathFitter = Base.extend({
+ curve2[i] = curve1[i + 1].subtract(curve1[i]).multiply(2);
+ }
+ var pt = this.evaluate(3, curve, u),
+- pt1 = this.evaluate(2, curve1, u),
+- pt2 = this.evaluate(1, curve2, u),
+- diff = pt.subtract(point),
++ pt1 = this.evaluate(2, curve1, u),
++ pt2 = this.evaluate(1, curve2, u),
++ diff = pt.subtract(point),
+ df = pt1.dot(pt1) + diff.dot(pt2);
+- if (Math.abs(df) < Numerical.TOLERANCE)
++ if (Math.abs(df) < 0.00001)
+ return u;
+ return u - diff.dot(pt1) / df;
+ },
+@@ -5585,266 +7495,326 @@ var PathFitter = Base.extend({
+ }
+ });
+
+-var TextItem = this.TextItem = Item.extend({
+- initialize: function() {
+- this.base();
+- this._content = '';
+- this._characterStyle = CharacterStyle.create(this);
+- this.setCharacterStyle(this._project.getCurrentStyle());
+- this._paragraphStyle = ParagraphStyle.create(this);
+- this.setParagraphStyle();
+- },
+-
+- _clone: function(copy) {
+- copy._content = this._content;
+- copy.setCharacterStyle(this._characterStyle);
+- copy.setParagraphStyle(this._paragraphStyle);
+- return this.base(copy);
+- },
+-
+- getContent: function() {
+- return this._content;
+- },
+-
+- setContent: function(content) {
+- this._changed(Change.CONTENT);
+- this._content = '' + content;
+- },
+-
+- getCharacterStyle: function() {
+- return this._characterStyle;
+- },
+-
+- setCharacterStyle: function(style) {
+- this._characterStyle.initialize(style);
+- },
+-
+- getParagraphStyle: function() {
+- return this._paragraphStyle;
+- },
++PathItem.inject(new function() {
+
+- setParagraphStyle: function(style) {
+- this._paragraphStyle.initialize(style);
++ function splitPath(intersections, collectOthers) {
++ intersections.sort(function(loc1, loc2) {
++ var path1 = loc1.getPath(),
++ path2 = loc2.getPath();
++ return path1 === path2
++ ? (loc1.getIndex() + loc1.getParameter())
++ - (loc2.getIndex() + loc2.getParameter())
++ : path1._id - path2._id;
++ });
++ var others = collectOthers && [];
++ for (var i = intersections.length - 1; i >= 0; i--) {
++ var loc = intersections[i],
++ other = loc.getIntersection(),
++ curve = loc.divide(),
++ segment = curve && curve.getSegment1() || loc.getSegment();
++ if (others)
++ others.push(other);
++ segment._intersection = other;
++ }
++ return others;
+ }
+-});
+-
+-var PointText = this.PointText = TextItem.extend({
+- initialize: function(point) {
+- this.base();
+- this._point = Point.read(arguments).clone();
+- this._matrix = new Matrix().translate(this._point);
+- },
+
+- clone: function() {
+- var copy = this._clone(new PointText(this._point));
+- copy._matrix.initialize(this._matrix);
+- return copy;
++ function reorientPath(path) {
++ if (path instanceof CompoundPath) {
++ var children = path._children,
++ length = children.length,
++ bounds = new Array(length),
++ counters = new Array(length),
++ clockwise = children[0].isClockwise();
++ for (var i = 0; i < length; i++) {
++ bounds[i] = children[i].getBounds();
++ counters[i] = 0;
++ }
++ for (var i = 0; i < length; i++) {
++ for (var j = 1; j < length; j++) {
++ if (i !== j && bounds[i].contains(bounds[j]))
++ counters[j]++;
++ }
++ if (i > 0 && counters[i] % 2 === 0)
++ children[i].setClockwise(clockwise);
++ }
++ }
++ return path;
++ }
++
++ function computeBoolean(path1, path2, operator, subtract) {
++ path1 = reorientPath(path1.clone(false));
++ path2 = reorientPath(path2.clone(false));
++ var path1Clockwise = path1.isClockwise(),
++ path2Clockwise = path2.isClockwise(),
++ intersections = path1.getIntersections(path2);
++ splitPath(splitPath(intersections, true));
++ if (subtract) {
++ path2.reverse();
++ path2Clockwise = !path2Clockwise;
++ }
++ var paths = []
++ .concat(path1._children || [path1])
++ .concat(path2._children || [path2]),
++ segments = [],
++ result = new CompoundPath();
++ for (var i = 0, l = paths.length; i < l; i++) {
++ var path = paths[i],
++ parent = path._parent,
++ clockwise = path.isClockwise(),
++ segs = path._segments;
++ path = parent instanceof CompoundPath ? parent : path;
++ for (var j = segs.length - 1; j >= 0; j--) {
++ var segment = segs[j],
++ midPoint = segment.getCurve().getPoint(0.5),
++ insidePath1 = path !== path1 && path1.contains(midPoint)
++ && (clockwise === path1Clockwise || subtract
++ || !testOnCurve(path1, midPoint)),
++ insidePath2 = path !== path2 && path2.contains(midPoint)
++ && (clockwise === path2Clockwise
++ || !testOnCurve(path2, midPoint));
++ if (operator(path === path1, insidePath1, insidePath2)) {
++ segment._invalid = true;
++ } else {
++ segments.push(segment);
++ }
++ }
++ }
++ for (var i = 0, l = segments.length; i < l; i++) {
++ var segment = segments[i];
++ if (segment._visited)
++ continue;
++ var path = new Path(),
++ loc = segment._intersection,
++ intersection = loc && loc.getSegment(true);
++ if (segment.getPrevious()._invalid)
++ segment.setHandleIn(intersection
++ ? intersection._handleIn
++ : new Point(0, 0));
++ do {
++ segment._visited = true;
++ if (segment._invalid && segment._intersection) {
++ var inter = segment._intersection.getSegment(true);
++ path.add(new Segment(segment._point, segment._handleIn,
++ inter._handleOut));
++ inter._visited = true;
++ segment = inter;
++ } else {
++ path.add(segment.clone());
++ }
++ segment = segment.getNext();
++ } while (segment && !segment._visited && segment !== intersection);
++ var amount = path._segments.length;
++ if (amount > 1 && (amount > 2 || !path.isPolygon())) {
++ path.setClosed(true);
++ result.addChild(path, true);
++ } else {
++ path.remove();
++ }
++ }
++ path1.remove();
++ path2.remove();
++ return result.reduce();
++ }
++
++ function testOnCurve(path, point) {
++ var curves = path.getCurves(),
++ bounds = path.getBounds();
++ if (bounds.contains(point)) {
++ for (var i = 0, l = curves.length; i < l; i++) {
++ var curve = curves[i];
++ if (curve.getBounds().contains(point)
++ && curve.getParameterOf(point))
++ return true;
++ }
++ }
++ return false;
++ }
++
++ return {
++ unite: function(path) {
++ return computeBoolean(this, path,
++ function(isPath1, isInPath1, isInPath2) {
++ return isInPath1 || isInPath2;
++ });
++ },
++
++ intersect: function(path) {
++ return computeBoolean(this, path,
++ function(isPath1, isInPath1, isInPath2) {
++ return !(isInPath1 || isInPath2);
++ });
++ },
++
++ subtract: function(path) {
++ return computeBoolean(this, path,
++ function(isPath1, isInPath1, isInPath2) {
++ return isPath1 && isInPath2 || !isPath1 && !isInPath1;
++ }, true);
++ },
++
++ exclude: function(path) {
++ return new Group([this.subtract(path), path.subtract(this)]);
++ },
++
++ divide: function(path) {
++ return new Group([this.subtract(path), this.intersect(path)]);
++ }
++ };
++});
++
++var TextItem = Item.extend({
++ _class: 'TextItem',
++ _boundsSelected: true,
++ _serializeFields: {
++ content: null
+ },
++ _boundsGetter: 'getBounds',
+
+- getPoint: function() {
+- return LinkedPoint.create(this, 'setPoint',
+- this._point.x, this._point.y);
++ initialize: function TextItem(arg) {
++ this._content = '';
++ this._lines = [];
++ var hasProps = arg && Base.isPlainObject(arg)
++ && arg.x === undefined && arg.y === undefined;
++ this._initialize(hasProps && arg, !hasProps && Point.read(arguments));
+ },
+
+- setPoint: function(point) {
+- this.translate(Point.read(arguments).subtract(this._point));
++ _clone: function _clone(copy) {
++ copy.setContent(this._content);
++ return _clone.base.call(this, copy);
+ },
+
+- getPosition: function() {
+- return this.getPoint();
++ getContent: function() {
++ return this._content;
+ },
+
+- setPosition: function(point) {
+- this.setPoint.apply(this, arguments);
++ setContent: function(content) {
++ this._content = '' + content;
++ this._lines = this._content.split(/\r\n|\n|\r/mg);
++ this._changed(69);
+ },
+
+- _transform: function(matrix, flags) {
+- this._matrix.preConcatenate(matrix);
+- matrix._transformPoint(this._point, this._point);
++ isEmpty: function() {
++ return !this._content;
+ },
+
+- draw: function(ctx) {
+- if (!this._content)
+- return;
+- ctx.save();
+- ctx.font = this.getFontSize() + 'pt ' + this.getFont();
+- ctx.textAlign = this.getJustification();
+- this._matrix.applyToContext(ctx);
++ getCharacterStyle: '#getStyle',
++ setCharacterStyle: '#setStyle',
+
+- var fillColor = this.getFillColor();
+- var strokeColor = this.getStrokeColor();
+- if (!fillColor || !strokeColor)
+- ctx.globalAlpha = this._opacity;
+- if (fillColor) {
+- ctx.fillStyle = fillColor.getCanvasStyle(ctx);
+- ctx.fillText(this._content, 0, 0);
+- }
+- if (strokeColor) {
+- ctx.strokeStyle = strokeColor.getCanvasStyle(ctx);
+- ctx.strokeText(this._content, 0, 0);
+- }
+- ctx.restore();
+- }
++ getParagraphStyle: '#getStyle',
++ setParagraphStyle: '#setStyle'
+ });
+
+-var Style = Item.extend({
+- initialize: function(style) {
+- var clone = style instanceof Style;
+- return Base.each(this._defaults, function(value, key) {
+- value = style && style[key] || value;
+- this[key] = value && clone && value.clone
+- ? value.clone() : value;
+- }, this);
+- },
+-
+- statics: {
+- create: function(item) {
+- var style = new this(this.dont);
+- style._item = item;
+- return style;
+- },
+-
+- extend: function(src) {
+- var styleKey = src._style,
+- flags = src._flags || {};
+- src._owner.inject(Base.each(src._defaults, function(value, key) {
+- var isColor = !!key.match(/Color$/),
+- part = Base.capitalize(key),
+- set = 'set' + part,
+- get = 'get' + part;
+- src[set] = function(value) {
+- var children = this._item && this._item._children;
+- value = isColor ? Color.read(arguments) : value;
+- if (children) {
+- for (var i = 0, l = children.length; i < l; i++)
+- children[i][styleKey][set](value);
+- } else {
+- var old = this['_' + key];
+- if (old != value && !(old && old.equals
+- && old.equals(value))) {
+- this['_' + key] = value;
+- if (isColor) {
+- if (old)
+- old._removeOwner(this._item);
+- if (value)
+- value._addOwner(this._item);
+- }
+- if (this._item)
+- this._item._changed(flags[key] || Change.STYLE);
+- }
+- }
+- return this;
+- };
+- src[get] = function() {
+- var children = this._item && this._item._children,
+- style;
+- if (!children)
+- return this['_' + key];
+- for (var i = 0, l = children.length; i < l; i++) {
+- var childStyle = children[i][styleKey][get]();
+- if (!style) {
+- style = childStyle;
+- } else if (style != childStyle && !(style
+- && style.equals && style.equals(childStyle))) {
+- return undefined;
+- }
+- }
+- return style;
+- };
+- this[set] = function(value) {
+- this[styleKey][set](value);
+- return this;
+- };
+- this[get] = function() {
+- return this[styleKey][get]();
+- };
+- }, {}));
+- return this.base(src);
+- }
+- }
+-});
++var PointText = TextItem.extend({
++ _class: 'PointText',
+
+-var PathStyle = this.PathStyle = Style.extend({
+- _defaults: {
+- fillColor: undefined,
+- strokeColor: undefined,
+- strokeWidth: 1,
+- strokeCap: 'butt',
+- strokeJoin: 'miter',
+- miterLimit: 10,
+- dashOffset: 0,
+- dashArray: []
++ initialize: function PointText() {
++ TextItem.apply(this, arguments);
+ },
+- _flags: {
+- strokeWidth: Change.STROKE,
+- strokeCap: Change.STROKE,
+- strokeJoin: Change.STROKE,
+- miterLimit: Change.STROKE
+- },
+- _owner: Item,
+- _style: '_style'
+
+-});
++ clone: function(insert) {
++ return this._clone(new PointText({ insert: false }), insert);
++ },
+
+-var ParagraphStyle = this.ParagraphStyle = Style.extend({
+- _defaults: {
+- justification: 'left'
++ getPoint: function() {
++ var point = this._matrix.getTranslation();
++ return new LinkedPoint(point.x, point.y, this, 'setPoint');
+ },
+- _owner: TextItem,
+- _style: '_paragraphStyle'
+
+-});
++ setPoint: function(point) {
++ point = Point.read(arguments);
++ this.translate(point.subtract(this._matrix.getTranslation()));
++ },
+
+-var CharacterStyle = this.CharacterStyle = PathStyle.extend({
+- _defaults: Base.merge(PathStyle.prototype._defaults, {
+- fillColor: 'black',
+- fontSize: 10,
+- font: 'sans-serif'
+- }),
+- _owner: TextItem,
+- _style: '_characterStyle'
++ _draw: function(ctx) {
++ if (!this._content)
++ return;
++ this._setStyles(ctx);
++ var style = this._style,
++ lines = this._lines,
++ leading = style.getLeading();
++ ctx.font = style.getFontStyle();
++ ctx.textAlign = style.getJustification();
++ for (var i = 0, l = lines.length; i < l; i++) {
++ var line = lines[i];
++ if (style.getFillColor())
++ ctx.fillText(line, 0, 0);
++ if (style.getStrokeColor())
++ ctx.strokeText(line, 0, 0);
++ ctx.translate(0, leading);
++ }
++ }
++}, new function() {
++ var measureCtx = null;
+
++ return {
++ _getBounds: function(getter, matrix) {
++ if (!measureCtx)
++ measureCtx = CanvasProvider.getContext(1, 1);
++ var style = this._style,
++ lines = this._lines,
++ count = lines.length,
++ justification = style.getJustification(),
++ leading = style.getLeading(),
++ x = 0;
++ measureCtx.font = style.getFontStyle();
++ var width = 0;
++ for (var i = 0; i < count; i++)
++ width = Math.max(width, measureCtx.measureText(lines[i]).width);
++ if (justification !== 'left')
++ x -= width / (justification === 'center' ? 2: 1);
++ var bounds = new Rectangle(x,
++ count ? - 0.75 * leading : 0,
++ width, count * leading);
++ return matrix ? matrix._transformBounds(bounds, bounds) : bounds;
++ }
++ };
+ });
+
+-var Color = this.Color = Base.extend(new function() {
++var Color = Base.extend(new function() {
+
+- var components = {
++ var types = {
+ gray: ['gray'],
+ rgb: ['red', 'green', 'blue'],
+ hsb: ['hue', 'saturation', 'brightness'],
+- hsl: ['hue', 'saturation', 'lightness']
++ hsl: ['hue', 'saturation', 'lightness'],
++ gradient: ['gradient', 'origin', 'destination', 'highlight']
+ };
+
+- var colorCache = {},
+- colorContext;
+-
+- function nameToRgbColor(name) {
+- var color = colorCache[name];
+- if (color)
+- return color.clone();
+- if (!colorContext) {
+- var canvas = CanvasProvider.getCanvas(Size.create(1, 1));
+- colorContext = canvas.getContext('2d');
+- colorContext.globalCompositeOperation = 'copy';
++ var componentParsers = {},
++ colorCache = {},
++ colorCtx;
++
++ function nameToRGB(name) {
++ var cached = colorCache[name];
++ if (!cached) {
++ if (!colorCtx) {
++ colorCtx = CanvasProvider.getContext(1, 1);
++ colorCtx.globalCompositeOperation = 'copy';
++ }
++ colorCtx.fillStyle = 'rgba(0,0,0,0)';
++ colorCtx.fillStyle = name;
++ colorCtx.fillRect(0, 0, 1, 1);
++ var data = colorCtx.getImageData(0, 0, 1, 1).data;
++ cached = colorCache[name] = [
++ data[0] / 255,
++ data[1] / 255,
++ data[2] / 255
++ ];
+ }
+- colorContext.fillStyle = 'rgba(0,0,0,0)';
+- colorContext.fillStyle = name;
+- colorContext.fillRect(0, 0, 1, 1);
+- var data = colorContext.getImageData(0, 0, 1, 1).data,
+- rgb = [data[0] / 255, data[1] / 255, data[2] / 255];
+- return (colorCache[name] = RgbColor.read(rgb)).clone();
++ return cached.slice();
+ }
+
+- function hexToRgbColor(string) {
++ function hexToRGB(string) {
+ var hex = string.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ if (hex.length >= 4) {
+- var rgb = new Array(3);
++ var components = [0, 0, 0];
+ for (var i = 0; i < 3; i++) {
+- var channel = hex[i + 1];
+- rgb[i] = parseInt(channel.length == 1
+- ? channel + channel : channel, 16) / 255;
++ var value = hex[i + 1];
++ components[i] = parseInt(value.length == 1
++ ? value + value : value, 16) / 255;
+ }
+- return RgbColor.read(rgb);
++ return components;
+ }
+ }
+
+@@ -5858,26 +7828,19 @@ var Color = this.Color = Base.extend(new function() {
+ ];
+
+ var converters = {
+- 'rgb-hsb': function(color) {
+- var r = color._red,
+- g = color._green,
+- b = color._blue,
+- max = Math.max(r, g, b),
++ 'rgb-hsb': function(r, g, b) {
++ var max = Math.max(r, g, b),
+ min = Math.min(r, g, b),
+ delta = max - min,
+- h = delta == 0 ? 0
++ h = delta === 0 ? 0
+ : ( max == r ? (g - b) / delta + (g < b ? 6 : 0)
+ : max == g ? (b - r) / delta + 2
+- : (r - g) / delta + 4) * 60,
+- s = max == 0 ? 0 : delta / max,
+- v = max;
+- return new HsbColor(h, s, v, color._alpha);
++ : (r - g) / delta + 4) * 60;
++ return [h, max === 0 ? 0 : delta / max, max];
+ },
+
+- 'hsb-rgb': function(color) {
+- var h = (color._hue / 60) % 6,
+- s = color._saturation,
+- b = color._brightness,
++ 'hsb-rgb': function(h, s, b) {
++ var h = (h / 60) % 6,
+ i = Math.floor(h),
+ f = h - i,
+ i = hsbIndices[i],
+@@ -5887,17 +7850,14 @@ var Color = this.Color = Base.extend(new function() {
+ b * (1 - s * f),
+ b * (1 - s * (1 - f))
+ ];
+- return new RgbColor(v[i[0]], v[i[1]], v[i[2]], color._alpha);
++ return [v[i[0]], v[i[1]], v[i[2]]];
+ },
+
+- 'rgb-hsl': function(color) {
+- var r = color._red,
+- g = color._green,
+- b = color._blue,
+- max = Math.max(r, g, b),
++ 'rgb-hsl': function(r, g, b) {
++ var max = Math.max(r, g, b),
+ min = Math.min(r, g, b),
+ delta = max - min,
+- achromatic = delta == 0,
++ achromatic = delta === 0,
+ h = achromatic ? 0
+ : ( max == r ? (g - b) / delta + (g < b ? 6 : 0)
+ : max == g ? (b - r) / delta + 2
+@@ -5906,16 +7866,13 @@ var Color = this.Color = Base.extend(new function() {
+ s = achromatic ? 0 : l < 0.5
+ ? delta / (max + min)
+ : delta / (2 - max - min);
+- return new HslColor(h, s, l, color._alpha);
++ return [h, s, l];
+ },
+
+- 'hsl-rgb': function(color) {
+- var s = color._saturation,
+- h = color._hue / 360,
+- l = color._lightness,
+- t1, t2, c;
+- if (s == 0)
+- return new RgbColor(l, l, l, color._alpha);
++ 'hsl-rgb': function(h, s, l) {
++ h /= 360;
++ if (s === 0)
++ return [l, l, l];
+ var t3s = [ h + 1 / 3, h, h - 1 / 3 ],
+ t2 = l < 0.5 ? l * (1 + s) : l + s - l * s,
+ t1 = 2 * l - t2,
+@@ -5932,488 +7889,914 @@ var Color = this.Color = Base.extend(new function() {
+ ? t1 + (t2 - t1) * ((2 / 3) - t3) * 6
+ : t1;
+ }
+- return new RgbColor(c[0], c[1], c[2], color._alpha);
++ return c;
++ },
++
++ 'rgb-gray': function(r, g, b) {
++ return [r * 0.2989 + g * 0.587 + b * 0.114];
+ },
+
+- 'rgb-gray': function(color) {
+- return new GrayColor(1 - (color._red * 0.2989 + color._green * 0.587
+- + color._blue * 0.114), color._alpha);
++ 'gray-rgb': function(g) {
++ return [g, g, g];
+ },
+
+- 'gray-rgb': function(color) {
+- var comp = 1 - color._gray;
+- return new RgbColor(comp, comp, comp, color._alpha);
++ 'gray-hsb': function(g) {
++ return [0, 0, g];
+ },
+
+- 'gray-hsb': function(color) {
+- return new HsbColor(0, 0, 1 - color._gray, color._alpha);
++ 'gray-hsl': function(g) {
++ return [0, 0, g];
+ },
+
+- 'gray-hsl': function(color) {
+- return new HslColor(0, 0, 1 - color._gray, color._alpha);
++ 'gradient-rgb': function() {
++ return [];
++ },
++
++ 'rgb-gradient': function() {
++ return [];
+ }
++
+ };
+
+- var fields = {
+- _readNull: true,
+-
+- initialize: function(arg) {
+- var isArray = Array.isArray(arg),
+- type = this._colorType;
+- if (typeof arg === 'object' && !isArray) {
+- if (!type) {
+- return arg.red !== undefined
+- ? new RgbColor(arg.red, arg.green, arg.blue, arg.alpha)
+- : arg.gray !== undefined
+- ? new GrayColor(arg.gray, arg.alpha)
+- : arg.lightness !== undefined
+- ? new HslColor(arg.hue, arg.saturation, arg.lightness,
+- arg.alpha)
+- : arg.hue !== undefined
+- ? new HsbColor(arg.hue, arg.saturation, arg.brightness,
+- arg.alpha)
+- : new RgbColor();
+- } else {
+- return Color.read(arguments).convert(type);
++ return Base.each(types, function(properties, type) {
++ componentParsers[type] = [];
++ Base.each(properties, function(name, index) {
++ var part = Base.capitalize(name),
++ hasOverlap = /^(hue|saturation)$/.test(name),
++ parser = componentParsers[type][index] = name === 'gradient'
++ ? function(value) {
++ var current = this._components[0];
++ value = Gradient.read(
++ Array.isArray(value) ? value : arguments,
++ 0, 0, { readNull: true });
++ if (current !== value) {
++ if (current)
++ current._removeOwner(this);
++ if (value)
++ value._addOwner(this);
++ }
++ return value;
++ }
++ : name === 'hue'
++ ? function(value) {
++ return isNaN(value) ? 0
++ : ((value % 360) + 360) % 360;
++ }
++ : type === 'gradient'
++ ? function() {
++ return Point.read(arguments, 0, 0, {
++ readNull: name === 'highlight',
++ clone: true
++ });
++ }
++ : function(value) {
++ return isNaN(value) ? 0
++ : Math.min(Math.max(value, 0), 1);
++ };
++
++ this['get' + part] = function() {
++ return this._type === type
++ || hasOverlap && /^hs[bl]$/.test(this._type)
++ ? this._components[index]
++ : this._convert(type)[index];
++ };
++
++ this['set' + part] = function(value) {
++ if (this._type !== type
++ && !(hasOverlap && /^hs[bl]$/.test(this._type))) {
++ this._components = this._convert(type);
++ this._properties = types[type];
++ this._type = type;
+ }
+- } else if (typeof arg === 'string') {
+- var rgbColor = arg.match(/^#[0-9a-f]{3,6}$/i)
+- ? hexToRgbColor(arg)
+- : nameToRgbColor(arg);
+- return type
+- ? rgbColor.convert(type)
+- : rgbColor;
+- } else {
+- var components = isArray ? arg
+- : Array.prototype.slice.call(arguments);
+- if (!type) {
+- if (components.length >= 3)
+- return new RgbColor(components);
+- return new GrayColor(components);
++ value = parser.call(this, value);
++ if (value != null) {
++ this._components[index] = value;
++ this._changed();
++ }
++ };
++ }, this);
++ }, {
++ _class: 'Color',
++ _readIndex: true,
++
++ initialize: function Color(arg) {
++ var slice = Array.prototype.slice,
++ args = arguments,
++ read = 0,
++ parse = true,
++ type,
++ components,
++ alpha,
++ values;
++ if (Array.isArray(arg)) {
++ args = arg;
++ arg = args[0];
++ }
++ var argType = arg != null && typeof arg;
++ if (argType === 'string' && arg in types) {
++ type = arg;
++ arg = args[1];
++ if (Array.isArray(arg)) {
++ components = arg;
++ alpha = args[2];
+ } else {
+- Base.each(this._components,
+- function(name, i) {
+- var value = components[i];
+- this['_' + name] = value !== undefined
+- ? value : null;
+- },
+- this);
++ if (this.__read)
++ read = 1;
++ args = slice.call(args, 1);
++ argType = typeof arg;
++ }
++ }
++ if (!components) {
++ parse = !(this.__options && this.__options.dontParse);
++ values = argType === 'number'
++ ? args
++ : argType === 'object' && arg.length != null
++ ? arg
++ : null;
++ if (values) {
++ if (!type)
++ type = values.length >= 3
++ ? 'rgb'
++ : 'gray';
++ var length = types[type].length;
++ alpha = values[length];
++ if (this.__read)
++ read += values === arguments
++ ? length + (alpha != null ? 1 : 0)
++ : 1;
++ if (values.length > length)
++ values = slice.call(values, 0, length);
++ } else if (argType === 'string') {
++ components = arg.match(/^#[0-9a-f]{3,6}$/i)
++ ? hexToRGB(arg)
++ : nameToRGB(arg);
++ type = 'rgb';
++ } else if (argType === 'object') {
++ if (arg.constructor === Color) {
++ type = arg._type;
++ components = arg._components.slice();
++ alpha = arg._alpha;
++ if (type === 'gradient') {
++ for (var i = 1, l = components.length; i < l; i++) {
++ var point = components[i];
++ if (point)
++ components[i] = point.clone();
++ }
++ }
++ } else if (arg.constructor === Gradient) {
++ type = 'gradient';
++ values = args;
++ } else {
++ type = 'hue' in arg
++ ? 'lightness' in arg
++ ? 'hsl'
++ : 'hsb'
++ : 'gradient' in arg || 'stops' in arg
++ || 'radial' in arg
++ ? 'gradient'
++ : 'gray' in arg
++ ? 'gray'
++ : 'rgb';
++ var properties = types[type];
++ parsers = parse && componentParsers[type];
++ this._components = components = [];
++ for (var i = 0, l = properties.length; i < l; i++) {
++ var value = arg[properties[i]];
++ if (value == null && i === 0 && type === 'gradient'
++ && 'stops' in arg) {
++ value = {
++ stops: arg.stops,
++ radial: arg.radial
++ };
++ }
++ if (parse)
++ value = parsers[i].call(this, value);
++ if (value != null)
++ components[i] = value;
++ }
++ alpha = arg.alpha;
++ }
++ }
++ if (this.__read && type)
++ read = 1;
++ }
++ this._type = type || 'rgb';
++ if (type === 'gradient')
++ this._id = Color._id = (Color._id || 0) + 1;
++ if (!components) {
++ this._components = components = [];
++ var parsers = componentParsers[this._type];
++ for (var i = 0, l = parsers.length; i < l; i++) {
++ var value = values && values[i];
++ if (parse)
++ value = parsers[i].call(this, value);
++ if (value != null)
++ components[i] = value;
+ }
+ }
++ this._components = components;
++ this._properties = types[this._type];
++ this._alpha = alpha;
++ if (this.__read)
++ this.__read = read;
++ },
++
++ _serialize: function(options, dictionary) {
++ var components = this.getComponents();
++ return Base.serialize(
++ /^(gray|rgb)$/.test(this._type)
++ ? components
++ : [this._type].concat(components),
++ options, true, dictionary);
++ },
++
++ _changed: function() {
++ this._canvasStyle = null;
++ if (this._owner)
++ this._owner._changed(17);
+ },
+
+ clone: function() {
+- var ctor = this.constructor,
+- copy = new ctor(ctor.dont),
+- components = this._components;
+- for (var i = 0, l = components.length; i < l; i++) {
+- var key = '_' + components[i];
+- copy[key] = this[key];
+- }
+- return copy;
++ return new Color(this._type, this._components.slice(), this._alpha);
+ },
+
+- convert: function(type) {
++ _convert: function(type) {
+ var converter;
+- return this._colorType == type
+- ? this.clone()
+- : (converter = converters[this._colorType + '-' + type])
+- ? converter(this)
+- : converters['rgb-' + type](
+- converters[this._colorType + '-rgb'](this));
++ return this._type === type
++ ? this._components.slice()
++ : (converter = converters[this._type + '-' + type])
++ ? converter.apply(this, this._components)
++ : converters['rgb-' + type].apply(this,
++ converters[this._type + '-rgb'].apply(this,
++ this._components));
+ },
+
+- statics: {
+- extend: function(src) {
+- src.beans = true;
+- if (src._colorType) {
+- var comps = components[src._colorType];
+- src._components = comps.concat(['alpha']);
+- Base.each(comps, function(name) {
+- var isHue = name === 'hue',
+- part = Base.capitalize(name),
+- name = '_' + name;
+- this['get' + part] = function() {
+- return this[name];
+- };
+- this['set' + part] = function(value) {
+- this[name] = isHue
+- ? ((value % 360) + 360) % 360
+- : Math.min(Math.max(value, 0), 1);
+- this._changed();
+- return this;
+- };
+- }, src);
++ convert: function(type) {
++ return new Color(type, this._convert(type), this._alpha);
++ },
++
++ getType: function() {
++ return this._type;
++ },
++
++ setType: function(type) {
++ this._components = this._convert(type);
++ this._properties = types[type];
++ this._type = type;
++ },
++
++ getComponents: function() {
++ var components = this._components.slice();
++ if (this._alpha != null)
++ components.push(this._alpha);
++ return components;
++ },
++
++ getAlpha: function() {
++ return this._alpha != null ? this._alpha : 1;
++ },
++
++ setAlpha: function(alpha) {
++ this._alpha = alpha == null ? null : Math.min(Math.max(alpha, 0), 1);
++ this._changed();
++ },
++
++ hasAlpha: function() {
++ return this._alpha != null;
++ },
++
++ equals: function(color) {
++ if (Base.isPlainValue(color))
++ color = Color.read(arguments);
++ return color === this || color && this._type === color._type
++ && this._alpha === color._alpha
++ && Base.equals(this._components, color._components)
++ || false;
++ },
++
++ toString: function() {
++ var properties = this._properties,
++ parts = [],
++ isGradient = this._type === 'gradient',
++ f = Formatter.instance;
++ for (var i = 0, l = properties.length; i < l; i++) {
++ var value = this._components[i];
++ if (value != null)
++ parts.push(properties[i] + ': '
++ + (isGradient ? value : f.number(value)));
++ }
++ if (this._alpha != null)
++ parts.push('alpha: ' + f.number(this._alpha));
++ return '{ ' + parts.join(', ') + ' }';
++ },
++
++ toCSS: function(noAlpha) {
++ var components = this._convert('rgb'),
++ alpha = noAlpha || this._alpha == null ? 1 : this._alpha;
++ components = [
++ Math.round(components[0] * 255),
++ Math.round(components[1] * 255),
++ Math.round(components[2] * 255)
++ ];
++ if (alpha < 1)
++ components.push(alpha);
++ return (components.length == 4 ? 'rgba(' : 'rgb(')
++ + components.join(',') + ')';
++ },
++
++ toCanvasStyle: function(ctx) {
++ if (this._canvasStyle)
++ return this._canvasStyle;
++ if (this._type !== 'gradient')
++ return this._canvasStyle = this.toCSS();
++ var components = this._components,
++ gradient = components[0],
++ stops = gradient._stops,
++ origin = components[1],
++ destination = components[2],
++ canvasGradient;
++ if (gradient._radial) {
++ var radius = destination.getDistance(origin),
++ highlight = components[3];
++ if (highlight) {
++ var vector = highlight.subtract(origin);
++ if (vector.getLength() > radius)
++ highlight = origin.add(vector.normalize(radius - 0.1));
++ }
++ var start = highlight || origin;
++ canvasGradient = ctx.createRadialGradient(start.x, start.y,
++ 0, origin.x, origin.y, radius);
++ } else {
++ canvasGradient = ctx.createLinearGradient(origin.x, origin.y,
++ destination.x, destination.y);
++ }
++ for (var i = 0, l = stops.length; i < l; i++) {
++ var stop = stops[i];
++ canvasGradient.addColorStop(stop._rampPoint,
++ stop._color.toCanvasStyle());
++ }
++ return this._canvasStyle = canvasGradient;
++ },
++
++ transform: function(matrix) {
++ if (this._type === 'gradient') {
++ var components = this._components;
++ for (var i = 1, l = components.length; i < l; i++) {
++ var point = components[i];
++ matrix._transformPoint(point, point, true);
+ }
+- return this.base(src);
++ this._changed();
+ }
++ },
++
++ statics: {
++ _types: types,
++
++ random: function() {
++ var random = Math.random;
++ return new Color(random(), random(), random());
++ }
++ }
++ });
++}, new function() {
++ function clamp(value, hue) {
++ return value < 0
++ ? 0
++ : hue && value > 360
++ ? 360
++ : !hue && value > 1
++ ? 1
++ : value;
++ }
++
++ var operators = {
++ add: function(a, b, hue) {
++ return clamp(a + b, hue);
++ },
++
++ subtract: function(a, b, hue) {
++ return clamp(a - b, hue);
++ },
++
++ multiply: function(a, b, hue) {
++ return clamp(a * b, hue);
++ },
++
++ divide: function(a, b, hue) {
++ return clamp(a / b, hue);
+ }
+ };
+
+- Base.each(components, function(comps, type) {
+- Base.each(comps, function(component) {
+- var part = Base.capitalize(component);
+- fields['get' + part] = function() {
+- return this.convert(type)[component];
+- };
+- fields['set' + part] = function(value) {
+- var color = this.convert(type);
+- color[component] = value;
+- color = color.convert(this._colorType);
+- for (var i = 0, l = this._components.length; i < l; i++) {
+- var key = this._components[i];
+- this[key] = color[key];
+- }
+- };
+- });
++ return Base.each(operators, function(operator, name) {
++ var options = { dontParse: /^(multiply|divide)$/.test(name) };
++
++ this[name] = function(color) {
++ color = Color.read(arguments, 0, 0, options);
++ var type = this._type,
++ properties = this._properties,
++ components1 = this._components,
++ components2 = color._convert(type);
++ for (var i = 0, l = components1.length; i < l; i++)
++ components2[i] = operator(components1[i], components2[i],
++ properties[i] === 'hue');
++ return new Color(type, components2,
++ this._alpha != null
++ ? operator(this._alpha, color.getAlpha())
++ : null);
++ };
++ }, {
+ });
++});
+
+- return fields;
+-}, {
++Base.each(Color._types, function(properties, type) {
++ var ctor = this[Base.capitalize(type) + 'Color'] = function(arg) {
++ var argType = arg != null && typeof arg,
++ components = argType === 'object' && arg.length != null
++ ? arg
++ : argType === 'string'
++ ? null
++ : arguments;
++ return components
++ ? new Color(type, components)
++ : new Color(arg);
++ };
++ if (type.length == 3) {
++ var acronym = type.toUpperCase();
++ Color[acronym] = this[acronym + 'Color'] = ctor;
++ }
++}, Base.exports);
++
++var Gradient = Base.extend({
++ _class: 'Gradient',
++
++ initialize: function Gradient(stops, radial) {
++ this._id = Gradient._id = (Gradient._id || 0) + 1;
++ if (stops && this._set(stops))
++ stops = radial = null;
++ if (!this._stops)
++ this.setStops(stops || ['white', 'black']);
++ if (this._radial == null)
++ this.setRadial(typeof radial === 'string' && radial === 'radial'
++ || radial || false);
++ },
++
++ _serialize: function(options, dictionary) {
++ return dictionary.add(this, function() {
++ return Base.serialize([this._stops, this._radial],
++ options, true, dictionary);
++ });
++ },
+
+ _changed: function() {
+- this._cssString = null;
+ for (var i = 0, l = this._owners && this._owners.length; i < l; i++)
+- this._owners[i]._changed(Change.STYLE);
++ this._owners[i]._changed();
+ },
+
+- _addOwner: function(item) {
++ _addOwner: function(color) {
+ if (!this._owners)
+ this._owners = [];
+- this._owners.push(item);
++ this._owners.push(color);
+ },
+
+- _removeOwner: function(item) {
+- var index = this._owners ? this._owners.indexOf(item) : -1;
++ _removeOwner: function(color) {
++ var index = this._owners ? this._owners.indexOf(color) : -1;
+ if (index != -1) {
+ this._owners.splice(index, 1);
+- if (this._owners.length == 0)
++ if (this._owners.length === 0)
+ delete this._owners;
+ }
+ },
+
+- getType: function() {
+- return this._colorType;
++ clone: function() {
++ var stops = [];
++ for (var i = 0, l = this._stops.length; i < l; i++)
++ stops[i] = this._stops[i].clone();
++ return new this.constructor(stops);
+ },
+
+- getComponents: function() {
+- var length = this._components.length;
+- var comps = new Array(length);
+- for (var i = 0; i < length; i++)
+- comps[i] = this['_' + this._components[i]];
+- return comps;
++ getStops: function() {
++ return this._stops;
+ },
+
+- getAlpha: function() {
+- return this._alpha != null ? this._alpha : 1;
++ setStops: function(stops) {
++ if (this.stops) {
++ for (var i = 0, l = this._stops.length; i < l; i++)
++ delete this._stops[i]._owner;
++ }
++ if (stops.length < 2)
++ throw new Error(
++ 'Gradient stop list needs to contain at least two stops.');
++ this._stops = GradientStop.readAll(stops, 0, false, true);
++ for (var i = 0, l = this._stops.length; i < l; i++) {
++ var stop = this._stops[i];
++ stop._owner = this;
++ if (stop._defaultRamp)
++ stop.setRampPoint(i / (l - 1));
++ }
++ this._changed();
+ },
+
+- setAlpha: function(alpha) {
+- this._alpha = alpha == null ? null : Math.min(Math.max(alpha, 0), 1);
+- this._changed();
+- return this;
++ getRadial: function() {
++ return this._radial;
+ },
+
+- hasAlpha: function() {
+- return this._alpha != null;
++ setRadial: function(radial) {
++ this._radial = radial;
++ this._changed();
+ },
+
+- equals: function(color) {
+- if (color && color._colorType === this._colorType) {
+- for (var i = 0, l = this._components.length; i < l; i++) {
+- var component = '_' + this._components[i];
+- if (this[component] !== color[component])
++ equals: function(gradient) {
++ if (gradient && gradient.constructor == this.constructor
++ && this._stops.length == gradient._stops.length) {
++ for (var i = 0, l = this._stops.length; i < l; i++) {
++ if (!this._stops[i].equals(gradient._stops[i]))
+ return false;
+ }
+ return true;
+ }
+ return false;
+- },
++ }
++});
+
+- toString: function() {
+- var parts = [],
+- format = Base.formatNumber;
+- for (var i = 0, l = this._components.length; i < l; i++) {
+- var component = this._components[i],
+- value = this['_' + component];
+- if (component === 'alpha' && value == null)
+- value = 1;
+- parts.push(component + ': ' + format(value));
++var GradientStop = Base.extend({
++ _class: 'GradientStop',
++
++ initialize: function GradientStop(arg0, arg1) {
++ if (arg0) {
++ var color, rampPoint;
++ if (arg1 === undefined && Array.isArray(arg0)) {
++ color = arg0[0];
++ rampPoint = arg0[1];
++ } else if (arg0.color) {
++ color = arg0.color;
++ rampPoint = arg0.rampPoint;
++ } else {
++ color = arg0;
++ rampPoint = arg1;
++ }
++ this.setColor(color);
++ this.setRampPoint(rampPoint);
+ }
+- return '{ ' + parts.join(', ') + ' }';
+ },
+
+- toCssString: function() {
+- if (!this._cssString) {
+- var color = this.convert('rgb'),
+- alpha = color.getAlpha(),
+- components = [
+- Math.round(color._red * 255),
+- Math.round(color._green * 255),
+- Math.round(color._blue * 255),
+- alpha != null ? alpha : 1
+- ];
+- this._cssString = 'rgba(' + components.join(', ') + ')';
+- }
+- return this._cssString;
++ clone: function() {
++ return new GradientStop(this._color.clone(), this._rampPoint);
+ },
+
+- getCanvasStyle: function() {
+- return this.toCssString();
+- }
++ _serialize: function(options, dictionary) {
++ return Base.serialize([this._color, this._rampPoint], options, true,
++ dictionary);
++ },
+
+-});
++ _changed: function() {
++ if (this._owner)
++ this._owner._changed(17);
++ },
+
+-var GrayColor = this.GrayColor = Color.extend({
+-
+- _colorType: 'gray'
+-});
+-
+-var RgbColor = this.RgbColor = this.RGBColor = Color.extend({
+-
+- _colorType: 'rgb'
+-});
++ getRampPoint: function() {
++ return this._rampPoint;
++ },
+
+-var HsbColor = this.HsbColor = this.HSBColor = Color.extend({
++ setRampPoint: function(rampPoint) {
++ this._defaultRamp = rampPoint == null;
++ this._rampPoint = rampPoint || 0;
++ this._changed();
++ },
+
+- _colorType: 'hsb'
+-});
++ getColor: function() {
++ return this._color;
++ },
+
+-var HslColor = this.HslColor = this.HSLColor = Color.extend({
++ setColor: function(color) {
++ this._color = Color.read(arguments);
++ if (this._color === color)
++ this._color = color.clone();
++ this._color._owner = this;
++ this._changed();
++ },
+
+- _colorType: 'hsl'
++ equals: function(stop) {
++ return stop === this || stop instanceof GradientStop
++ && this._color.equals(stop._color)
++ && this._rampPoint == stop._rampPoint
++ || false;
++ }
+ });
+
+-var GradientColor = this.GradientColor = Color.extend({
++var Style = Base.extend(new function() {
++ var defaults = {
++ fillColor: undefined,
++ strokeColor: undefined,
++ strokeWidth: 1,
++ strokeCap: 'butt',
++ strokeJoin: 'miter',
++ miterLimit: 10,
++ dashOffset: 0,
++ dashArray: [],
++ shadowColor: undefined,
++ shadowBlur: 0,
++ shadowOffset: new Point(),
++ selectedColor: undefined,
++ font: 'sans-serif',
++ fontSize: 12,
++ leading: null,
++ justification: 'left'
++ };
+
+- initialize: function(gradient, origin, destination, hilite) {
+- this.gradient = gradient || new Gradient();
+- this.setOrigin(origin);
+- this.setDestination(destination);
+- if (hilite)
+- this.setHilite(hilite);
+- },
++ var flags = {
++ strokeWidth: 25,
++ strokeCap: 25,
++ strokeJoin: 25,
++ miterLimit: 25,
++ font: 5,
++ fontSize: 5,
++ leading: 5,
++ justification: 5
++ };
+
+- clone: function() {
+- return new GradientColor(this.gradient, this._origin, this._destination,
+- this._hilite);
+- },
++ var item = {},
++ fields = {
++ _defaults: defaults,
++ _textDefaults: Base.merge(defaults, {
++ fillColor: new Color()
++ })
++ };
+
+- getOrigin: function() {
+- return this._origin;
+- },
++ Base.each(defaults, function(value, key) {
++ var isColor = /Color$/.test(key),
++ part = Base.capitalize(key),
++ flag = flags[key],
++ set = 'set' + part,
++ get = 'get' + part;
++
++ fields[set] = function(value) {
++ var children = this._item && this._item._children;
++ if (children && children.length > 0
++ && this._item._type !== 'compound-path') {
++ for (var i = 0, l = children.length; i < l; i++)
++ children[i]._style[set](value);
++ } else {
++ var old = this._values[key];
++ if (old != value) {
++ if (isColor) {
++ if (old)
++ delete old._owner;
++ if (value && value.constructor === Color)
++ value._owner = this._item;
++ }
++ this._values[key] = value;
++ if (this._item)
++ this._item._changed(flag || 17);
++ }
++ }
++ };
+
+- setOrigin: function(origin) {
+- origin = Point.read(arguments).clone();
+- this._origin = origin;
+- if (this._destination)
+- this._radius = this._destination.getDistance(this._origin);
+- this._changed();
+- return this;
+- },
++ fields[get] = function() {
++ var value,
++ children = this._item && this._item._children;
++ if (!children || children.length === 0 || arguments[0]
++ || this._item._type === 'compound-path') {
++ var value = this._values[key];
++ if (value === undefined) {
++ value = this._defaults[key];
++ if (value && value.clone)
++ value = value.clone();
++ this._values[key] = value;
++ } else if (isColor && !(value && value.constructor === Color)) {
++ this._values[key] = value = Color.read(
++ [value], 0, 0, { readNull: true, clone: true });
++ if (value)
++ value._owner = this._item;
++ }
++ return value;
++ }
++ for (var i = 0, l = children.length; i < l; i++) {
++ var childValue = children[i]._style[get]();
++ if (i === 0) {
++ value = childValue;
++ } else if (!Base.equals(value, childValue)) {
++ return undefined;
++ }
++ }
++ return value;
++ };
+
+- getDestination: function() {
+- return this._destination;
+- },
++ item[get] = function() {
++ return this._style[get]();
++ };
+
+- setDestination: function(destination) {
+- destination = Point.read(arguments).clone();
+- this._destination = destination;
+- this._radius = this._destination.getDistance(this._origin);
+- this._changed();
+- return this;
+- },
++ item[set] = function(value) {
++ this._style[set](value);
++ };
++ });
+
+- getHilite: function() {
+- return this._hilite;
+- },
++ Item.inject(item);
++ return fields;
++}, {
++ _class: 'Style',
+
+- setHilite: function(hilite) {
+- hilite = Point.read(arguments).clone();
+- var vector = hilite.subtract(this._origin);
+- if (vector.getLength() > this._radius) {
+- this._hilite = this._origin.add(
+- vector.normalize(this._radius - 0.1));
+- } else {
+- this._hilite = hilite;
+- }
+- this._changed();
+- return this;
++ initialize: function Style(style, _item) {
++ this._values = {};
++ this._item = _item;
++ if (_item instanceof TextItem)
++ this._defaults = this._textDefaults;
++ if (style)
++ this.set(style);
+ },
+
+- getCanvasStyle: function(ctx) {
+- var gradient;
+- if (this.gradient.type === 'linear') {
+- gradient = ctx.createLinearGradient(this._origin.x, this._origin.y,
+- this._destination.x, this._destination.y);
+- } else {
+- var origin = this._hilite || this._origin;
+- gradient = ctx.createRadialGradient(origin.x, origin.y,
+- 0, this._origin.x, this._origin.y, this._radius);
+- }
+- for (var i = 0, l = this.gradient._stops.length; i < l; i++) {
+- var stop = this.gradient._stops[i];
+- gradient.addColorStop(stop._rampPoint, stop._color.toCssString());
++ set: function(style) {
++ var isStyle = style instanceof Style,
++ values = isStyle ? style._values : style;
++ if (values) {
++ for (var key in values) {
++ if (key in this._defaults) {
++ var value = values[key];
++ this[key] = value && isStyle && value.clone
++ ? value.clone() : value;
++ }
++ }
+ }
+- return gradient;
+ },
+
+- equals: function(color) {
+- return color == this || color && color._colorType === this._colorType
+- && this.gradient.equals(color.gradient)
+- && this._origin.equals(color._origin)
+- && this._destination.equals(color._destination);
++ getLeading: function getLeading() {
++ var leading = getLeading.base.call(this);
++ return leading != null ? leading : this.getFontSize() * 1.2;
+ },
+
+- transform: function(matrix) {
+- matrix._transformPoint(this._origin, this._origin, true);
+- matrix._transformPoint(this._destination, this._destination, true);
+- if (this._hilite)
+- matrix._transformPoint(this._hilite, this._hilite, true);
+- this._radius = this._destination.getDistance(this._origin);
++ getFontStyle: function() {
++ var size = this.getFontSize();
++ return (/[a-z]/i.test(size) ? size + ' ' : size + 'px ')
++ + this.getFont();
+ }
++
+ });
+
+-var Gradient = this.Gradient = Base.extend({
+- initialize: function(stops, type) {
+- this.setStops(stops || ['white', 'black']);
+- this.type = type || 'linear';
+- },
++var DomElement = new function() {
++
++ var special = /^(checked|value|selected|disabled)$/i,
++ translated = { text: 'textContent', html: 'innerHTML' },
++ unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1 };
++
++ function create(nodes, parent) {
++ var res = [];
++ for (var i = 0, l = nodes && nodes.length; i < l;) {
++ var el = nodes[i++];
++ if (typeof el === 'string') {
++ el = document.createElement(el);
++ } else if (!el || !el.nodeType) {
++ continue;
++ }
++ if (Base.isPlainObject(nodes[i]))
++ DomElement.set(el, nodes[i++]);
++ if (Array.isArray(nodes[i]))
++ create(nodes[i++], el);
++ if (parent)
++ parent.appendChild(el);
++ res.push(el);
++ }
++ return res;
++ }
+
+- clone: function() {
+- var stops = [];
+- for (var i = 0, l = this._stops.length; i < l; i++)
+- stops[i] = this._stops[i].clone();
+- return new Gradient(stops, this.type);
+- },
++ return {
++ create: function(nodes, parent) {
++ var isArray = Array.isArray(nodes),
++ res = create(isArray ? nodes : arguments, isArray ? parent : null);
++ return res.length == 1 ? res[0] : res;
++ },
+
+- getStops: function() {
+- return this._stops;
+- },
++ find: function(selector, root) {
++ return (root || document).querySelector(selector);
++ },
+
+- setStops: function(stops) {
+- if (stops.length < 2)
+- throw new Error(
+- 'Gradient stop list needs to contain at least two stops.');
+- this._stops = GradientStop.readAll(stops);
+- for (var i = 0, l = this._stops.length; i < l; i++) {
+- var stop = this._stops[i];
+- if (stop._defaultRamp)
+- stop.setRampPoint(i / (l - 1));
+- }
+- },
++ findAll: function(selector, root) {
++ return (root || document).querySelectorAll(selector);
++ },
+
+- equals: function(gradient) {
+- if (gradient.type != this.type)
+- return false;
+- if (this._stops.length == gradient._stops.length) {
+- for (var i = 0, l = this._stops.length; i < l; i++) {
+- if (!this._stops[i].equals(gradient._stops[i]))
+- return false;
+- }
+- return true;
+- }
+- return false;
+- }
+-});
++ get: function(el, key) {
++ return el
++ ? special.test(key)
++ ? key === 'value' || typeof el[key] !== 'string'
++ ? el[key]
++ : true
++ : key in translated
++ ? el[translated[key]]
++ : el.getAttribute(key)
++ : null;
++ },
+
+-var GradientStop = this.GradientStop = Base.extend({
+- initialize: function(arg0, arg1) {
+- if (arg1 === undefined && Array.isArray(arg0)) {
+- this.setColor(arg0[0]);
+- this.setRampPoint(arg0[1]);
+- } else if (arg0.color) {
+- this.setColor(arg0.color);
+- this.setRampPoint(arg0.rampPoint);
+- } else {
+- this.setColor(arg0);
+- this.setRampPoint(arg1);
+- }
+- },
++ set: function(el, key, value) {
++ if (typeof key !== 'string') {
++ for (var name in key)
++ if (key.hasOwnProperty(name))
++ this.set(el, name, key[name]);
++ } else if (!el || value === undefined) {
++ return el;
++ } else if (special.test(key)) {
++ el[key] = value;
++ } else if (key in translated) {
++ el[translated[key]] = value;
++ } else if (key === 'style') {
++ this.setStyle(el, value);
++ } else if (key === 'events') {
++ DomEvent.add(el, value);
++ } else {
++ el.setAttribute(key, value);
++ }
++ return el;
++ },
+
+- clone: function() {
+- return new GradientStop(this._color.clone(), this._rampPoint);
+- },
++ getStyles: function(el) {
++ var view = el && el.ownerDocument.defaultView;
++ return view && view.getComputedStyle(el, '');
++ },
+
+- getRampPoint: function() {
+- return this._rampPoint;
+- },
++ getStyle: function(el, key) {
++ return el && el.style[key] || this.getStyles(el)[key] || null;
++ },
+
+- setRampPoint: function(rampPoint) {
+- this._defaultRamp = rampPoint == null;
+- this._rampPoint = rampPoint || 0;
+- },
++ setStyle: function(el, key, value) {
++ if (typeof key !== 'string') {
++ for (var name in key)
++ if (key.hasOwnProperty(name))
++ this.setStyle(el, name, key[name]);
++ } else {
++ if (/^-?[\d\.]+$/.test(value) && !(key in unitless))
++ value += 'px';
++ el.style[key] = value;
++ }
++ return el;
++ },
+
+- getColor: function() {
+- return this._color;
+- },
++ hasClass: function(el, cls) {
++ return new RegExp('\\s*' + cls + '\\s*').test(el.className);
++ },
+
+- setColor: function(color) {
+- this._color = Color.read(arguments);
+- },
++ addClass: function(el, cls) {
++ el.className = (el.className + ' ' + cls).trim();
++ },
+
+- equals: function(stop) {
+- return stop == this || stop instanceof GradientStop
+- && this._color.equals(stop._color)
+- && this._rampPoint == stop._rampPoint;
+- }
+-});
++ removeClass: function(el, cls) {
++ el.className = el.className.replace(
++ new RegExp('\\s*' + cls + '\\s*'), ' ').trim();
++ },
+
+-var DomElement = {
+- getBounds: function(el, viewport) {
+- var rect = el.getBoundingClientRect(),
+- doc = el.ownerDocument,
+- body = doc.body,
+- docEl = doc.documentElement,
+- x = rect.left - (docEl.clientLeft || body.clientLeft || 0),
+- y = rect.top - (docEl.clientTop || body.clientTop || 0);
+- if (!viewport) {
+- var win = DomElement.getViewport(doc);
+- x += win.pageXOffset || docEl.scrollLeft || body.scrollLeft;
+- y += win.pageYOffset || docEl.scrollTop || body.scrollTop;
+- }
+- return new Rectangle(x, y, rect.width, rect.height);
+- },
++ remove: function(el) {
++ if (el.parentNode)
++ el.parentNode.removeChild(el);
++ },
+
+- getOffset: function(el, viewport) {
+- return this.getBounds(el, viewport).getPoint();
+- },
++ removeChildren: function(el) {
++ while (el.firstChild)
++ el.removeChild(el.firstChild);
++ },
+
+- getSize: function(el) {
+- return this.getBounds(el, true).getSize();
+- },
++ getBounds: function(el, viewport) {
++ var doc = el.ownerDocument,
++ body = doc.body,
++ html = doc.documentElement,
++ rect;
++ try {
++ rect = el.getBoundingClientRect();
++ } catch (e) {
++ rect = { left: 0, top: 0, width: 0, height: 0 };
++ }
++ var x = rect.left - (html.clientLeft || body.clientLeft || 0),
++ y = rect.top - (html.clientTop || body.clientTop || 0);
++ if (!viewport) {
++ var view = doc.defaultView;
++ x += view.pageXOffset || html.scrollLeft || body.scrollLeft;
++ y += view.pageYOffset || html.scrollTop || body.scrollTop;
++ }
++ return new Rectangle(x, y, rect.width, rect.height);
++ },
+
+- isInvisible: function(el) {
+- return this.getSize(el).equals([0, 0]);
+- },
++ getViewportBounds: function(el) {
++ var doc = el.ownerDocument,
++ view = doc.defaultView,
++ html = doc.documentElement;
++ return new Rectangle(0, 0,
++ view.innerWidth || html.clientWidth,
++ view.innerHeight || html.clientHeight
++ );
++ },
+
+- isVisible: function(el) {
+- return !this.isInvisible(el) && this.getViewportBounds(el).intersects(
+- this.getBounds(el, true));
+- },
++ getOffset: function(el, viewport) {
++ return this.getBounds(el, viewport).getPoint();
++ },
+
+- getViewport: function(doc) {
+- return doc.defaultView || doc.parentWindow;
+- },
++ getSize: function(el) {
++ return this.getBounds(el, true).getSize();
++ },
+
+- getViewportBounds: function(el) {
+- var doc = el.ownerDocument,
+- view = this.getViewport(doc),
+- body = doc.getElementsByTagName(
+- doc.compatMode === 'CSS1Compat' ? 'html' : 'body')[0];
+- return Rectangle.create(0, 0,
+- view.innerWidth || body.clientWidth,
+- view.innerHeight || body.clientHeight
+- );
+- },
++ isInvisible: function(el) {
++ return this.getSize(el).equals(new Size(0, 0));
++ },
+
+- getComputedStyle: function(el, name) {
+- if (el.currentStyle)
+- return el.currentStyle[Base.camelize(name)];
+- var style = this.getViewport(el.ownerDocument)
+- .getComputedStyle(el, null);
+- return style ? style.getPropertyValue(Base.hyphenate(name)) : null;
+- }
++ isInView: function(el) {
++ return !this.isInvisible(el) && this.getViewportBounds(el).intersects(
++ this.getBounds(el, true));
++ }
++ };
+ };
+
+ var DomEvent = {
+@@ -6447,7 +8830,7 @@ var DomEvent = {
+ ? event.targetTouches[0]
+ : event.changedTouches[0]
+ : event;
+- return Point.create(
++ return new Point(
+ pos.pageX || pos.clientX + document.documentElement.scrollLeft,
+ pos.pageY || pos.clientY + document.documentElement.scrollTop
+ );
+@@ -6491,7 +8874,7 @@ DomEvent.requestAnimationFrame = new function() {
+ || window['msR' + part];
+ if (request) {
+ request(function(time) {
+- if (time == undefined)
++ if (time == null)
+ request = null;
+ });
+ }
+@@ -6515,13 +8898,13 @@ DomEvent.requestAnimationFrame = new function() {
+ callbacks.push([callback, element]);
+ if (timer)
+ return;
+- timer = window.setInterval(function() {
++ timer = setInterval(function() {
+ for (var i = callbacks.length - 1; i >= 0; i--) {
+ var entry = callbacks[i],
+ func = entry[0],
+ el = entry[1];
+- if (!el || (PaperScript.getAttribute(el, 'keepalive') == 'true'
+- || focused) && DomElement.isVisible(el)) {
++ if (!el || (PaperScope.getAttribute(el, 'keepalive') == 'true'
++ || focused) && DomElement.isInView(el)) {
+ callbacks.splice(i, 1);
+ func(Date.now());
+ }
+@@ -6530,103 +8913,178 @@ DomEvent.requestAnimationFrame = new function() {
+ };
+ };
+
+-var View = this.View = PaperScopeItem.extend({
+- _list: 'views',
+- _reference: 'view',
++var View = Base.extend(Callback, {
++ _class: 'View',
+
+- initialize: function(canvas) {
+- this.base();
++ initialize: function View(element) {
++ this._scope = paper;
++ this._project = paper.project;
++ this._element = element;
+ var size;
+-
+- if (typeof canvas === 'string')
+- canvas = document.getElementById(canvas);
+- if (canvas instanceof HTMLCanvasElement) {
+- this._canvas = canvas;
+- if (PaperScript.hasAttribute(canvas, 'resize')) {
+- var offset = DomElement.getOffset(canvas, true),
+- that = this;
+- size = DomElement.getViewportBounds(canvas)
+- .getSize().subtract(offset);
+- canvas.width = size.width;
+- canvas.height = size.height;
+- DomEvent.add(window, {
+- resize: function(event) {
+- if (!DomElement.isInvisible(canvas))
+- offset = DomElement.getOffset(canvas, true);
+- that.setViewSize(DomElement.getViewportBounds(canvas)
+- .getSize().subtract(offset));
+- }
+- });
+- } else {
+- size = DomElement.isInvisible(canvas)
+- ? Size.create(parseInt(canvas.getAttribute('width')),
+- parseInt(canvas.getAttribute('height')))
+- : DomElement.getSize(canvas);
+- }
+- if (PaperScript.hasAttribute(canvas, 'stats')) {
+- this._stats = new Stats();
+- var element = this._stats.domElement,
+- style = element.style,
+- offset = DomElement.getOffset(canvas);
+- style.position = 'absolute';
+- style.left = offset.x + 'px';
+- style.top = offset.y + 'px';
+- document.body.appendChild(element);
+- }
+- } else {
+- size = Size.read(arguments, 1);
+- if (size.isZero())
+- size = new Size(1024, 768);
+- this._canvas = CanvasProvider.getCanvas(size);
+- }
+- this._id = this._canvas.getAttribute('id');
++ this._id = element.getAttribute('id');
+ if (this._id == null)
+- this._canvas.setAttribute('id', this._id = 'canvas-' + View._id++);
+-
+- View._views[this._id] = this;
+- this._viewSize = LinkedSize.create(this, 'setViewSize',
+- size.width, size.height);
+- this._context = this._canvas.getContext('2d');
++ element.setAttribute('id', this._id = 'view-' + View._id++);
++ DomEvent.add(element, this._viewHandlers);
++ if (PaperScope.hasAttribute(element, 'resize')) {
++ var offset = DomElement.getOffset(element, true),
++ that = this;
++ size = DomElement.getViewportBounds(element)
++ .getSize().subtract(offset);
++ this._windowHandlers = {
++ resize: function() {
++ if (!DomElement.isInvisible(element))
++ offset = DomElement.getOffset(element, true);
++ that.setViewSize(DomElement.getViewportBounds(element)
++ .getSize().subtract(offset));
++ }
++ };
++ DomEvent.add(window, this._windowHandlers);
++ } else {
++ size = new Size(parseInt(element.getAttribute('width'), 10),
++ parseInt(element.getAttribute('height'), 10));
++ if (size.isNaN())
++ size = DomElement.getSize(element);
++ }
++ element.width = size.width;
++ element.height = size.height;
++ if (PaperScope.hasAttribute(element, 'stats')
++ && typeof Stats !== 'undefined') {
++ this._stats = new Stats();
++ var stats = this._stats.domElement,
++ style = stats.style,
++ offset = DomElement.getOffset(element);
++ style.position = 'absolute';
++ style.left = offset.x + 'px';
++ style.top = offset.y + 'px';
++ document.body.appendChild(stats);
++ }
++ View._views.push(this);
++ View._viewsById[this._id] = this;
++ this._viewSize = new LinkedSize(size.width, size.height,
++ this, 'setViewSize');
+ this._matrix = new Matrix();
+ this._zoom = 1;
+-
+- this._events = this._createEvents();
+- DomEvent.add(this._canvas, this._events);
+ if (!View._focused)
+ View._focused = this;
+-
+- this._scope._redrawNotified = false;
++ this._frameItems = {};
++ this._frameItemCount = 0;
+ },
+
+ remove: function() {
+- if (!this.base())
++ if (!this._project)
+ return false;
+ if (View._focused == this)
+ View._focused = null;
+- delete View._views[this._id];
+- DomEvent.remove(this._canvas, this._events);
+- this._canvas = this._events = this._onFrame = null;
++ View._views.splice(View._views.indexOf(this), 1);
++ delete View._viewsById[this._id];
++ if (this._project.view == this)
++ this._project.view = null;
++ DomEvent.remove(this._element, this._viewHandlers);
++ DomEvent.remove(window, this._windowHandlers);
++ this._element = this._project = null;
++ this.detach('frame');
++ this._frameItems = {};
+ return true;
+ },
+
++ _events: {
++ onFrame: {
++ install: function() {
++ if (!this._requested) {
++ this._animate = true;
++ this._requestFrame();
++ }
++ },
++
++ uninstall: function() {
++ this._animate = false;
++ }
++ },
++
++ onResize: {}
++ },
++
++ _animate: false,
++ _time: 0,
++ _count: 0,
++
++ _requestFrame: function() {
++ var that = this;
++ DomEvent.requestAnimationFrame(function() {
++ that._requested = false;
++ if (!that._animate)
++ return;
++ that._requestFrame();
++ that._handleFrame();
++ }, this._element);
++ this._requested = true;
++ },
++
++ _handleFrame: function() {
++ paper = this._scope;
++ var now = Date.now() / 1000,
++ delta = this._before ? now - this._before : 0;
++ this._before = now;
++ this._handlingFrame = true;
++ this.fire('frame', Base.merge({
++ delta: delta,
++ time: this._time += delta,
++ count: this._count++
++ }));
++ if (this._stats)
++ this._stats.update();
++ this._handlingFrame = false;
++ this.draw(true);
++ },
++
++ _animateItem: function(item, animate) {
++ var items = this._frameItems;
++ if (animate) {
++ items[item._id] = {
++ item: item,
++ time: 0,
++ count: 0
++ };
++ if (++this._frameItemCount === 1)
++ this.attach('frame', this._handleFrameItems);
++ } else {
++ delete items[item._id];
++ if (--this._frameItemCount === 0) {
++ this.detach('frame', this._handleFrameItems);
++ }
++ }
++ },
++
++ _handleFrameItems: function(event) {
++ for (var i in this._frameItems) {
++ var entry = this._frameItems[i];
++ entry.item.fire('frame', Base.merge(event, {
++ time: entry.time += event.delta,
++ count: entry.count++
++ }));
++ }
++ },
++
+ _redraw: function() {
+- this._redrawNeeded = true;
+- if (this._onFrameCallback) {
+- this._onFrameCallback(0, true);
++ this._project._needsRedraw = true;
++ if (this._handlingFrame)
++ return;
++ if (this._animate) {
++ this._handleFrame();
+ } else {
+ this.draw();
+ }
+ },
+
+- _transform: function(matrix, flags) {
+- this._matrix.preConcatenate(matrix);
++ _transform: function(matrix) {
++ this._matrix.concatenate(matrix);
+ this._bounds = null;
+ this._inverse = null;
+ this._redraw();
+ },
+
+- getCanvas: function() {
+- return this._canvas;
++ getElement: function() {
++ return this._element;
+ },
+
+ getViewSize: function() {
+@@ -6638,17 +9096,14 @@ var View = this.View = PaperScopeItem.extend({
+ var delta = size.subtract(this._viewSize);
+ if (delta.isZero())
+ return;
+- this._canvas.width = size.width;
+- this._canvas.height = size.height;
++ this._element.width = size.width;
++ this._element.height = size.height;
+ this._viewSize.set(size.width, size.height, true);
+- this._bounds = null;
+- this._redrawNeeded = true;
+- if (this.onResize) {
+- this.onResize({
+- size: size,
+- delta: delta
+- });
+- }
++ this._bounds = null;
++ this.fire('resize', {
++ size: size,
++ delta: delta
++ });
+ this._redraw();
+ },
+
+@@ -6660,15 +9115,16 @@ var View = this.View = PaperScopeItem.extend({
+ },
+
+ getSize: function() {
+- return this.getBounds().getSize();
++ return this.getBounds().getSize(arguments[0]);
+ },
+
+ getCenter: function() {
+- return this.getBounds().getCenter();
++ return this.getBounds().getCenter(arguments[0]);
+ },
+
+ setCenter: function(center) {
+- this.scrollBy(Point.read(arguments).subtract(this.getCenter()));
++ center = Point.read(arguments);
++ this.scrollBy(center.subtract(this.getCenter()));
+ },
+
+ getZoom: function() {
+@@ -6676,164 +9132,120 @@ var View = this.View = PaperScopeItem.extend({
+ },
+
+ setZoom: function(zoom) {
+- this._transform(new Matrix().scale(zoom / this._zoom, this.getCenter()));
++ this._transform(new Matrix().scale(zoom / this._zoom,
++ this.getCenter()));
+ this._zoom = zoom;
+ },
+
+ isVisible: function() {
+- return DomElement.isVisible(this._canvas);
++ return DomElement.isInView(this._element);
+ },
+
+- scrollBy: function(point) {
++ scrollBy: function() {
+ this._transform(new Matrix().translate(Point.read(arguments).negate()));
+ },
+
+- draw: function(checkRedraw) {
+- if (checkRedraw && !this._redrawNeeded)
+- return false;
+- if (this._stats)
+- this._stats.update();
+- var ctx = this._context,
+- size = this._viewSize;
+- ctx.clearRect(0, 0, size._width + 1, size._height + 1);
+-
+- ctx.save();
+- this._matrix.applyToContext(ctx);
+- this._scope.project.draw(ctx);
+- ctx.restore();
+- if (this._redrawNeeded) {
+- this._redrawNeeded = false;
+- this._scope._redrawNotified = false;
+- }
+- return true;
+- },
+-
+- projectToView: function(point) {
++ projectToView: function() {
+ return this._matrix._transformPoint(Point.read(arguments));
+ },
+
+- viewToProject: function(point) {
++ viewToProject: function() {
+ return this._getInverse()._transformPoint(Point.read(arguments));
+ },
+
+ _getInverse: function() {
+ if (!this._inverse)
+- this._inverse = this._matrix.createInverse();
++ this._inverse = this._matrix.inverted();
+ return this._inverse;
+- },
+-
+- getOnFrame: function() {
+- return this._onFrame;
+- },
+-
+- setOnFrame: function(onFrame) {
+- this._onFrame = onFrame;
+- if (!onFrame) {
+- delete this._onFrameCallback;
+- return;
+- }
+- var that = this,
+- requested = false,
+- before,
+- time = 0,
+- count = 0;
+- this._onFrameCallback = function(param, dontRequest) {
+- requested = false;
+- if (!that._onFrame)
+- return;
+- paper = that._scope;
+- requested = true;
+- if (!dontRequest) {
+- DomEvent.requestAnimationFrame(that._onFrameCallback,
+- that._canvas);
+- }
+- var now = Date.now() / 1000,
+- delta = before ? now - before : 0;
+- that._onFrame(Base.merge({
+- delta: delta,
+- time: time += delta,
+- count: count++
+- }));
+- before = now;
+- that.draw(true);
+- };
+- if (!requested)
+- this._onFrameCallback();
+- },
++ }
+
+- onResize: null
+ }, {
+ statics: {
+- _views: {},
+- _id: 0
++ _views: [],
++ _viewsById: {},
++ _id: 0,
++
++ create: function(element) {
++ if (typeof element === 'string')
++ element = document.getElementById(element);
++ return new CanvasView(element);
++ }
+ }
+ }, new function() {
+ var tool,
+- timer,
+- curPoint,
++ prevFocus,
+ tempFocus,
+ dragging = false;
+
++ function getView(event) {
++ var target = DomEvent.getTarget(event);
++ return target.getAttribute && View._viewsById[target.getAttribute('id')];
++ }
++
+ function viewToProject(view, event) {
+- return view.viewToProject(DomEvent.getOffset(event, view._canvas));
++ return view.viewToProject(DomEvent.getOffset(event, view._element));
+ }
+
+ function updateFocus() {
+ if (!View._focused || !View._focused.isVisible()) {
+- PaperScope.each(function(scope) {
+- for (var i = 0, l = scope.views.length; i < l; i++) {
+- var view = scope.views[i];
+- if (view.isVisible()) {
+- View._focused = tempFocus = view;
+- throw Base.stop;
+- }
++ for (var i = 0, l = View._views.length; i < l; i++) {
++ var view = View._views[i];
++ if (view && view.isVisible()) {
++ View._focused = tempFocus = view;
++ break;
+ }
+- });
++ }
+ }
+ }
+
++ function mousedown(event) {
++ var view = View._focused = getView(event),
++ point = viewToProject(view, event);
++ dragging = true;
++ if (view._onMouseDown)
++ view._onMouseDown(event, point);
++ if (tool = view._scope._tool)
++ tool._onHandleEvent('mousedown', point, event);
++ view.draw(true);
++ }
++
+ function mousemove(event) {
+ var view;
+ if (!dragging) {
+- view = View._views[DomEvent.getTarget(event).getAttribute('id')];
++ view = getView(event);
+ if (view) {
++ prevFocus = View._focused;
+ View._focused = tempFocus = view;
+ } else if (tempFocus && tempFocus == View._focused) {
+- View._focused = null;
++ View._focused = prevFocus;
+ updateFocus();
+ }
+ }
+- if (!(view = view || View._focused) || !(tool = view._scope.tool))
++ if (!(view = view || View._focused))
+ return;
+ var point = event && viewToProject(view, event);
+- var onlyMove = !!(!tool.onMouseDrag && tool.onMouseMove);
+- if (dragging && !onlyMove) {
+- curPoint = point || curPoint;
+- if (curPoint && tool.onHandleEvent('mousedrag', curPoint, event)) {
+- view.draw(true);
++ if (view._onMouseMove)
++ view._onMouseMove(event, point);
++ if (tool = view._scope._tool) {
++ if (tool._onHandleEvent(dragging && tool.responds('mousedrag')
++ ? 'mousedrag' : 'mousemove', point, event))
+ DomEvent.stop(event);
+- }
+- } else if ((!dragging || onlyMove)
+- && tool.onHandleEvent('mousemove', point, event)) {
+- view.draw(true);
+- DomEvent.stop(event);
+ }
++ view.draw(true);
+ }
+
+ function mouseup(event) {
+ var view = View._focused;
+ if (!view || !dragging)
+ return;
+- dragging = false;
++ var point = viewToProject(view, event);
+ curPoint = null;
+- if (tool) {
+- if (timer != null)
+- timer = clearInterval(timer);
+- if (tool.onHandleEvent('mouseup', viewToProject(view, event), event)) {
+- view.draw(true);
+- DomEvent.stop(event);
+- }
+- }
++ dragging = false;
++ if (view._onMouseUp)
++ view._onMouseUp(event, point);
++ if (tool && tool._onHandleEvent('mouseup', point, event))
++ DomEvent.stop(event);
++ view.draw(true);
+ }
+
+ function selectstart(event) {
+@@ -6855,50 +9267,155 @@ var View = this.View = PaperScopeItem.extend({
+ });
+
+ return {
+- _createEvents: function() {
+- var view = this;
+-
+- function mousedown(event) {
+- View._focused = view;
+- if (!(tool = view._scope.tool))
+- return;
+- curPoint = viewToProject(view, event);
+- if (tool.onHandleEvent('mousedown', curPoint, event))
+- view.draw(true);
+- if (tool.eventInterval != null)
+- timer = setInterval(mousemove, tool.eventInterval);
+- dragging = true;
+- }
+-
+- return {
+- mousedown: mousedown,
+- touchstart: mousedown,
+- selectstart: selectstart
+- };
++ _viewHandlers: {
++ mousedown: mousedown,
++ touchstart: mousedown,
++ selectstart: selectstart
+ },
+
+ statics: {
+-
+ updateFocus: updateFocus
+ }
+ };
+ });
+
+-var Event = this.Event = Base.extend({
+- initialize: function(event) {
++var CanvasView = View.extend({
++ _class: 'CanvasView',
++
++ initialize: function CanvasView(canvas) {
++ if (!(canvas instanceof HTMLCanvasElement)) {
++ var size = Size.read(arguments, 1);
++ if (size.isZero())
++ size = new Size(1024, 768);
++ canvas = CanvasProvider.getCanvas(size);
++ }
++ this._context = canvas.getContext('2d');
++ this._eventCounters = {};
++ View.call(this, canvas);
++ },
++
++ draw: function(checkRedraw) {
++ if (checkRedraw && !this._project._needsRedraw)
++ return false;
++ var ctx = this._context,
++ size = this._viewSize;
++ ctx.clearRect(0, 0, size._width + 1, size._height + 1);
++ this._project.draw(ctx, this._matrix);
++ this._project._needsRedraw = false;
++ return true;
++ }
++}, new function() {
++
++ var downPoint,
++ lastPoint,
++ overPoint,
++ downItem,
++ lastItem,
++ overItem,
++ hasDrag,
++ doubleClick,
++ clickTime;
++
++ function callEvent(type, event, point, target, lastPoint, bubble) {
++ var item = target,
++ mouseEvent;
++ while (item) {
++ if (item.responds(type)) {
++ if (!mouseEvent)
++ mouseEvent = new MouseEvent(type, event, point, target,
++ lastPoint ? point.subtract(lastPoint) : null);
++ if (item.fire(type, mouseEvent)
++ && (!bubble || mouseEvent._stopped))
++ return false;
++ }
++ item = item.getParent();
++ }
++ return true;
++ }
++
++ function handleEvent(view, type, event, point, lastPoint) {
++ if (view._eventCounters[type]) {
++ var project = view._project,
++ hit = project.hitTest(point, {
++ tolerance: project.options.hitTolerance || 0,
++ fill: true,
++ stroke: true
++ }),
++ item = hit && hit.item;
++ if (item) {
++ if (type === 'mousemove' && item != overItem)
++ lastPoint = point;
++ if (type !== 'mousemove' || !hasDrag)
++ callEvent(type, event, point, item, lastPoint);
++ return item;
++ }
++ }
++ }
++
++ return {
++ _onMouseDown: function(event, point) {
++ var item = handleEvent(this, 'mousedown', event, point);
++ doubleClick = lastItem == item && (Date.now() - clickTime < 300);
++ downItem = lastItem = item;
++ downPoint = lastPoint = overPoint = point;
++ hasDrag = downItem && downItem.responds('mousedrag');
++ },
++
++ _onMouseUp: function(event, point) {
++ var item = handleEvent(this, 'mouseup', event, point);
++ if (hasDrag) {
++ if (lastPoint && !lastPoint.equals(point))
++ callEvent('mousedrag', event, point, downItem, lastPoint);
++ if (item != downItem) {
++ overPoint = point;
++ callEvent('mousemove', event, point, item, overPoint);
++ }
++ }
++ if (item === downItem) {
++ clickTime = Date.now();
++ if (!doubleClick
++ || callEvent('doubleclick', event, downPoint, item))
++ callEvent('click', event, downPoint, item);
++ doubleClick = false;
++ }
++ downItem = null;
++ hasDrag = false;
++ },
++
++ _onMouseMove: function(event, point) {
++ if (downItem)
++ callEvent('mousedrag', event, point, downItem, lastPoint);
++ var item = handleEvent(this, 'mousemove', event, point, overPoint);
++ lastPoint = overPoint = point;
++ if (item !== overItem) {
++ callEvent('mouseleave', event, point, overItem);
++ overItem = item;
++ callEvent('mouseenter', event, point, item);
++ }
++ }
++ };
++});
++
++var Event = Base.extend({
++ _class: 'Event',
++
++ initialize: function Event(event) {
+ this.event = event;
+ },
+
+ preventDefault: function() {
++ this._prevented = true;
+ DomEvent.preventDefault(this.event);
+ },
+
+ stopPropagation: function() {
++ this._stopped = true;
+ DomEvent.stopPropagation(this.event);
+ },
+
+ stop: function() {
+- DomEvent.stop(this.event);
++ this.stopPropagation();
++ this.preventDefault();
+ },
+
+ getModifiers: function() {
+@@ -6906,29 +9423,30 @@ var Event = this.Event = Base.extend({
+ }
+ });
+
+-var KeyEvent = this.KeyEvent = Event.extend(new function() {
+- return {
+- initialize: function(down, key, character, event) {
+- this.base(event);
+- this.type = down ? 'keydown' : 'keyup';
+- this.key = key;
+- this.character = character;
+- },
++var KeyEvent = Event.extend({
++ _class: 'KeyEvent',
+
+- toString: function() {
+- return '{ type: ' + this.type
+- + ', key: ' + this.key
+- + ', character: ' + this.character
+- + ', modifiers: ' + this.getModifiers()
+- + ' }';
+- }
+- };
++ initialize: function KeyEvent(down, key, character, event) {
++ Event.call(this, event);
++ this.type = down ? 'keydown' : 'keyup';
++ this.key = key;
++ this.character = character;
++ },
++
++ toString: function() {
++ return "{ type: '" + this.type
++ + "', key: '" + this.key
++ + "', character: '" + this.character
++ + "', modifiers: " + this.getModifiers()
++ + " }";
++ }
+ });
+
+-var Key = this.Key = new function() {
++var Key = new function() {
+
+ var keys = {
+- 8: 'backspace',
++ 8: 'backspace',
++ 9: 'tab',
+ 13: 'enter',
+ 16: 'shift',
+ 17: 'control',
+@@ -6954,7 +9472,8 @@ var Key = this.Key = new function() {
+ control: false,
+ option: false,
+ command: false,
+- capsLock: false
++ capsLock: false,
++ space: false
+ }),
+
+ charCodeMap = {},
+@@ -6964,15 +9483,13 @@ var Key = this.Key = new function() {
+ function handleKey(down, keyCode, charCode, event) {
+ var character = String.fromCharCode(charCode),
+ key = keys[keyCode] || character.toLowerCase(),
+- handler = down ? 'onKeyDown' : 'onKeyUp',
++ type = down ? 'keydown' : 'keyup',
+ view = View._focused,
+ scope = view && view.isVisible() && view._scope,
+- tool = scope && scope.tool;
++ tool = scope && scope._tool;
+ keyMap[key] = down;
+- if (tool && tool[handler]) {
+- var keyEvent = new KeyEvent(down, key, character, event);
+- if (tool[handler](keyEvent) === false)
+- keyEvent.preventDefault();
++ if (tool && tool.responds(type)) {
++ tool.fire(type, new KeyEvent(down, key, character, event));
+ if (view)
+ view.draw(true);
+ }
+@@ -7022,8 +9539,243 @@ var Key = this.Key = new function() {
+ };
+ };
+
+-var ToolEvent = this.ToolEvent = Event.extend({
+- initialize: function(tool, type, event) {
++var MouseEvent = Event.extend({
++ _class: 'MouseEvent',
++
++ initialize: function MouseEvent(type, event, point, target, delta) {
++ Event.call(this, event);
++ this.type = type;
++ this.point = point;
++ this.target = target;
++ this.delta = delta;
++ },
++
++ toString: function() {
++ return "{ type: '" + this.type
++ + "', point: " + this.point
++ + ', target: ' + this.target
++ + (this.delta ? ', delta: ' + this.delta : '')
++ + ', modifiers: ' + this.getModifiers()
++ + ' }';
++ }
++});
++
++ Base.extend(Callback, {
++ _class: 'Palette',
++ _events: [ 'onChange' ],
++
++ initialize: function Palette(title, components, values) {
++ var parent = DomElement.find('.palettejs-panel')
++ || DomElement.find('body').appendChild(
++ DomElement.create('div', { 'class': 'palettejs-panel' }));
++ this._element = parent.appendChild(
++ DomElement.create('table', { 'class': 'palettejs-pane' })),
++ this._title = title;
++ if (!values)
++ values = {};
++ for (var name in (this._components = components)) {
++ var component = components[name];
++ if (!(component instanceof Component)) {
++ if (component.value == null)
++ component.value = values[name];
++ component.name = name;
++ component = components[name] = new Component(component);
++ }
++ this._element.appendChild(component._element);
++ component._palette = this;
++ if (values[name] === undefined)
++ values[name] = component.value;
++ }
++ this._values = Base.each(values, function(value, name) {
++ var component = components[name];
++ if (component) {
++ Base.define(values, name, {
++ enumerable: true,
++ configurable: true,
++ get: function() {
++ return component._value;
++ },
++ set: function(val) {
++ component.setValue(val);
++ }
++ });
++ }
++ });
++ if (window.paper)
++ paper.palettes.push(this);
++ },
++
++ reset: function() {
++ for (var i in this._components)
++ this._components[i].reset();
++ },
++
++ remove: function() {
++ DomElement.remove(this._element);
++ }
++});
++
++var Component = Base.extend(Callback, {
++ _class: 'Component',
++ _events: [ 'onChange', 'onClick' ],
++
++ _types: {
++ 'boolean': {
++ type: 'checkbox',
++ value: 'checked'
++ },
++
++ string: {
++ type: 'text'
++ },
++
++ number: {
++ type: 'number',
++ number: true
++ },
++
++ button: {
++ type: 'button'
++ },
++
++ text: {
++ tag: 'div',
++ value: 'text'
++ },
++
++ slider: {
++ type: 'range',
++ number: true
++ },
++
++ list: {
++ tag: 'select',
++
++ options: function() {
++ DomElement.removeChildren(this._inputItem);
++ DomElement.create(Base.each(this._options, function(option) {
++ this.push('option', { value: option, text: option });
++ }, []), this._inputItem);
++ }
++ }
++ },
++
++ initialize: function Component(obj) {
++ this._type = obj.type in this._types
++ ? obj.type
++ : 'options' in obj
++ ? 'list'
++ : 'onClick' in obj
++ ? 'button'
++ : typeof obj.value;
++ this._info = this._types[this._type] || { type: this._type };
++ var that = this,
++ fireChange = false;
++ this._inputItem = DomElement.create(this._info.tag || 'input', {
++ type: this._info.type,
++ events: {
++ change: function() {
++ that.setValue(
++ DomElement.get(this, that._info.value || 'value'));
++ if (fireChange) {
++ that._palette.fire('change', that, that.name, that._value);
++ that.fire('change', that._value);
++ }
++ },
++ click: function() {
++ that.fire('click');
++ }
++ }
++ });
++ this._element = DomElement.create('tr', [
++ this._labelItem = DomElement.create('td'),
++ 'td', [this._inputItem]
++ ]);
++ Base.each(obj, function(value, key) {
++ this[key] = value;
++ }, this);
++ this._defaultValue = this._value;
++ fireChange = true;
++ },
++
++ getType: function() {
++ return this._type;
++ },
++
++ getLabel: function() {
++ return this._label;
++ },
++
++ setLabel: function(label) {
++ this._label = label;
++ DomElement.set(this._labelItem, 'text', label + ':');
++ },
++
++ getOptions: function() {
++ return this._options;
++ },
++
++ setOptions: function(options) {
++ this._options = options;
++ if (this._info.options)
++ this._info.options.call(this);
++ },
++
++ getValue: function() {
++ return this._value;
++ },
++
++ setValue: function(value) {
++ var key = this._info.value || 'value';
++ DomElement.set(this._inputItem, key, value);
++ value = DomElement.get(this._inputItem, key);
++ this._value = this._info.number ? parseFloat(value, 10) : value;
++ },
++
++ getRange: function() {
++ return [parseFloat(DomElement.get(this._inputItem, 'min')),
++ parseFloat(DomElement.get(this._inputItem, 'max'))];
++ },
++
++ setRange: function(min, max) {
++ var range = Array.isArray(min) ? min : [min, max];
++ DomElement.set(this._inputItem, { min: range[0], max: range[1] });
++ },
++
++ getMin: function() {
++ return this.getRange()[0];
++ },
++
++ setMin: function(min) {
++ this.setRange(min, this.getMax());
++ },
++
++ getMax: function() {
++ return this.getRange()[1];
++ },
++
++ setMax: function(max) {
++ this.setRange(this.getMin(), max);
++ },
++
++ getStep: function() {
++ return parseFloat(DomElement.get(this._inputItem, 'step'));
++ },
++
++ setStep: function(step) {
++ DomElement.set(this._inputItem, 'step', step);
++ },
++
++ reset: function() {
++ this.setValue(this._defaultValue);
++ }
++});
++
++var ToolEvent = Event.extend({
++ _class: 'ToolEvent',
++ _item: null,
++
++ initialize: function ToolEvent(tool, type, event) {
+ this.tool = tool;
+ this.type = type;
+ this.event = event;
+@@ -7061,7 +9813,7 @@ var ToolEvent = this.ToolEvent = Event.extend({
+ if (!this._middlePoint && this.tool._lastPoint) {
+ return this.tool._point.add(this.tool._lastPoint).divide(2);
+ }
+- return this.middlePoint;
++ return this._middlePoint;
+ },
+
+ setMiddlePoint: function(middlePoint) {
+@@ -7095,8 +9847,7 @@ var ToolEvent = this.ToolEvent = Event.extend({
+ if (result) {
+ var item = result.item,
+ parent = item._parent;
+- while ((parent instanceof Group && !(parent instanceof Layer))
+- || parent instanceof CompoundPath) {
++ while (/^(group|compound-path)$/.test(parent._type)) {
+ item = parent;
+ parent = parent._parent;
+ }
+@@ -7118,19 +9869,22 @@ var ToolEvent = this.ToolEvent = Event.extend({
+ }
+ });
+
+-var Tool = this.Tool = PaperScopeItem.extend({
++var Tool = PaperScopeItem.extend({
++ _class: 'Tool',
+ _list: 'tools',
+- _reference: 'tool',
++ _reference: '_tool',
++ _events: [ 'onActivate', 'onDeactivate', 'onEditOptions',
++ 'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',
++ 'onKeyDown', 'onKeyUp' ],
+
+- initialize: function() {
+- this.base();
++ initialize: function Tool(props) {
++ PaperScopeItem.call(this);
+ this._firstMove = true;
+ this._count = 0;
+ this._downCount = 0;
++ this._set(props);
+ },
+
+- eventInterval: null,
+-
+ getMinDistance: function() {
+ return this._minDistance;
+ },
+@@ -7165,29 +9919,29 @@ var Tool = this.Tool = PaperScopeItem.extend({
+ this._maxDistance = distance;
+ },
+
+- updateEvent: function(type, pt, minDistance, maxDistance, start,
++ _updateEvent: function(type, point, minDistance, maxDistance, start,
+ needsChange, matchMaxDistance) {
+ if (!start) {
+ if (minDistance != null || maxDistance != null) {
+- var minDist = minDistance != null ? minDistance : 0;
+- var vector = pt.subtract(this._point);
+- var distance = vector.getLength();
++ var minDist = minDistance != null ? minDistance : 0,
++ vector = point.subtract(this._point),
++ distance = vector.getLength();
+ if (distance < minDist)
+ return false;
+ var maxDist = maxDistance != null ? maxDistance : 0;
+ if (maxDist != 0) {
+ if (distance > maxDist) {
+- pt = this._point.add(vector.normalize(maxDist));
++ point = this._point.add(vector.normalize(maxDist));
+ } else if (matchMaxDistance) {
+ return false;
+ }
+ }
+ }
+- if (needsChange && pt.equals(this._point))
++ if (needsChange && point.equals(this._point))
+ return false;
+ }
+- this._lastPoint = start && type == 'mousemove' ? pt : this._point;
+- this._point = pt;
++ this._lastPoint = start && type == 'mousemove' ? point : this._point;
++ this._point = point;
+ switch (type) {
+ case 'mousedown':
+ this._lastPoint = this._downPoint;
+@@ -7202,453 +9956,1288 @@ var Tool = this.Tool = PaperScopeItem.extend({
+ return true;
+ },
+
+- onHandleEvent: function(type, pt, event) {
++ _fireEvent: function(type, event) {
++ var sets = paper.project._removeSets;
++ if (sets) {
++ if (type === 'mouseup')
++ sets.mousedrag = null;
++ var set = sets[type];
++ if (set) {
++ for (var id in set) {
++ var item = set[id];
++ for (var key in sets) {
++ var other = sets[key];
++ if (other && other != set)
++ delete other[item._id];
++ }
++ item.remove();
++ }
++ sets[type] = null;
++ }
++ }
++ return this.responds(type)
++ && this.fire(type, new ToolEvent(this, type, event));
++ },
++
++ _onHandleEvent: function(type, point, event) {
+ paper = this._scope;
+ var called = false;
+ switch (type) {
+ case 'mousedown':
+- this.updateEvent(type, pt, null, null, true, false, false);
+- if (this.onMouseDown) {
+- this.onMouseDown(new ToolEvent(this, type, event));
+- called = true;
+- }
++ this._updateEvent(type, point, null, null, true, false, false);
++ called = this._fireEvent(type, event);
+ break;
+ case 'mousedrag':
+ var needsChange = false,
+ matchMaxDistance = false;
+- while (this.updateEvent(type, pt, this.minDistance,
++ while (this._updateEvent(type, point, this.minDistance,
+ this.maxDistance, false, needsChange, matchMaxDistance)) {
+- if (this.onMouseDrag) {
+- this.onMouseDrag(new ToolEvent(this, type, event));
+- called = true;
+- }
++ called = this._fireEvent(type, event) || called;
+ needsChange = true;
+ matchMaxDistance = true;
+ }
+ break;
+ case 'mouseup':
+- if ((this._point.x != pt.x || this._point.y != pt.y)
+- && this.updateEvent('mousedrag', pt, this.minDistance,
++ if (!point.equals(this._point)
++ && this._updateEvent('mousedrag', point, this.minDistance,
+ this.maxDistance, false, false, false)) {
+- if (this.onMouseDrag) {
+- this.onMouseDrag(new ToolEvent(this, type, event));
+- called = true;
+- }
++ called = this._fireEvent('mousedrag', event);
+ }
+- this.updateEvent(type, pt, null, this.maxDistance, false,
++ this._updateEvent(type, point, null, this.maxDistance, false,
+ false, false);
+- if (this.onMouseUp) {
+- this.onMouseUp(new ToolEvent(this, type, event));
+- called = true;
+- }
+- this.updateEvent(type, pt, null, null, true, false, false);
++ called = this._fireEvent(type, event) || called;
++ this._updateEvent(type, point, null, null, true, false, false);
+ this._firstMove = true;
+ break;
+ case 'mousemove':
+- while (this.updateEvent(type, pt, this.minDistance,
++ while (this._updateEvent(type, point, this.minDistance,
+ this.maxDistance, this._firstMove, true, false)) {
+- if (this.onMouseMove) {
+- this.onMouseMove(new ToolEvent(this, type, event));
+- called = true;
+- }
++ called = this._fireEvent(type, event) || called;
+ this._firstMove = false;
+ }
+ break;
+ }
+ return called;
+ }
++
+ });
+
+ var CanvasProvider = {
+ canvases: [],
+- getCanvas: function(size) {
++
++ getCanvas: function(width, height) {
++ var size = height === undefined ? width : new Size(width, height),
++ canvas,
++ init = true;
+ if (this.canvases.length) {
+- var canvas = this.canvases.pop();
+- if ((canvas.width != size.width)
+- || (canvas.height != size.height)) {
+- canvas.width = size.width;
+- canvas.height = size.height;
+- } else {
+- canvas.getContext('2d').clearRect(0, 0,
+- size.width + 1, size.height + 1);
+- }
+- return canvas;
++ canvas = this.canvases.pop();
++ } else {
++ canvas = document.createElement('canvas');
++
++ }
++ var ctx = canvas.getContext('2d');
++ ctx.save();
++ if (canvas.width === size.width && canvas.height === size.height) {
++ if (init)
++ ctx.clearRect(0, 0, size.width + 1, size.height + 1);
+ } else {
+- var canvas = document.createElement('canvas');
+ canvas.width = size.width;
+ canvas.height = size.height;
+- return canvas;
+ }
++ return canvas;
++ },
++
++ getContext: function(width, height) {
++ return this.getCanvas(width, height).getContext('2d');
+ },
+
+- returnCanvas: function(canvas) {
++ release: function(obj) {
++ var canvas = obj.canvas ? obj.canvas : obj;
++ canvas.getContext('2d').restore();
+ this.canvases.push(canvas);
+ }
+ };
+
+-var Numerical = new function() {
++var BlendMode = new function() {
++ var min = Math.min,
++ max = Math.max,
++ abs = Math.abs,
++ sr, sg, sb, sa,
++ br, bg, bb, ba,
++ dr, dg, db;
+
+- var abscissas = [
+- [ 0.5773502691896257645091488],
+- [0,0.7745966692414833770358531],
+- [ 0.3399810435848562648026658,0.8611363115940525752239465],
+- [0,0.5384693101056830910363144,0.9061798459386639927976269],
+- [ 0.2386191860831969086305017,0.6612093864662645136613996,0.9324695142031520278123016],
+- [0,0.4058451513773971669066064,0.7415311855993944398638648,0.9491079123427585245261897],
+- [ 0.1834346424956498049394761,0.5255324099163289858177390,0.7966664774136267395915539,0.9602898564975362316835609],
+- [0,0.3242534234038089290385380,0.6133714327005903973087020,0.8360311073266357942994298,0.9681602395076260898355762],
+- [ 0.1488743389816312108848260,0.4333953941292471907992659,0.6794095682990244062343274,0.8650633666889845107320967,0.9739065285171717200779640],
+- [0,0.2695431559523449723315320,0.5190961292068118159257257,0.7301520055740493240934163,0.8870625997680952990751578,0.9782286581460569928039380],
+- [ 0.1252334085114689154724414,0.3678314989981801937526915,0.5873179542866174472967024,0.7699026741943046870368938,0.9041172563704748566784659,0.9815606342467192506905491],
+- [0,0.2304583159551347940655281,0.4484927510364468528779129,0.6423493394403402206439846,0.8015780907333099127942065,0.9175983992229779652065478,0.9841830547185881494728294],
+- [ 0.1080549487073436620662447,0.3191123689278897604356718,0.5152486363581540919652907,0.6872929048116854701480198,0.8272013150697649931897947,0.9284348836635735173363911,0.9862838086968123388415973],
+- [0,0.2011940939974345223006283,0.3941513470775633698972074,0.5709721726085388475372267,0.7244177313601700474161861,0.8482065834104272162006483,0.9372733924007059043077589,0.9879925180204854284895657],
+- [ 0.0950125098376374401853193,0.2816035507792589132304605,0.4580167776572273863424194,0.6178762444026437484466718,0.7554044083550030338951012,0.8656312023878317438804679,0.9445750230732325760779884,0.9894009349916499325961542]
+- ];
++ function getLum(r, g, b) {
++ return 0.2989 * r + 0.587 * g + 0.114 * b;
++ }
+
+- var weights = [
+- [1],
+- [0.8888888888888888888888889,0.5555555555555555555555556],
+- [0.6521451548625461426269361,0.3478548451374538573730639],
+- [0.5688888888888888888888889,0.4786286704993664680412915,0.2369268850561890875142640],
+- [0.4679139345726910473898703,0.3607615730481386075698335,0.1713244923791703450402961],
+- [0.4179591836734693877551020,0.3818300505051189449503698,0.2797053914892766679014678,0.1294849661688696932706114],
+- [0.3626837833783619829651504,0.3137066458778872873379622,0.2223810344533744705443560,0.1012285362903762591525314],
+- [0.3302393550012597631645251,0.3123470770400028400686304,0.2606106964029354623187429,0.1806481606948574040584720,0.0812743883615744119718922],
+- [0.2955242247147528701738930,0.2692667193099963550912269,0.2190863625159820439955349,0.1494513491505805931457763,0.0666713443086881375935688],
+- [0.2729250867779006307144835,0.2628045445102466621806889,0.2331937645919904799185237,0.1862902109277342514260976,0.1255803694649046246346943,0.0556685671161736664827537],
+- [0.2491470458134027850005624,0.2334925365383548087608499,0.2031674267230659217490645,0.1600783285433462263346525,0.1069393259953184309602547,0.0471753363865118271946160],
+- [0.2325515532308739101945895,0.2262831802628972384120902,0.2078160475368885023125232,0.1781459807619457382800467,0.1388735102197872384636018,0.0921214998377284479144218,0.0404840047653158795200216],
+- [0.2152638534631577901958764,0.2051984637212956039659241,0.1855383974779378137417166,0.1572031671581935345696019,0.1215185706879031846894148,0.0801580871597602098056333,0.0351194603317518630318329],
+- [0.2025782419255612728806202,0.1984314853271115764561183,0.1861610000155622110268006,0.1662692058169939335532009,0.1395706779261543144478048,0.1071592204671719350118695,0.0703660474881081247092674,0.0307532419961172683546284],
+- [0.1894506104550684962853967,0.1826034150449235888667637,0.1691565193950025381893121,0.1495959888165767320815017,0.1246289712555338720524763,0.0951585116824927848099251,0.0622535239386478928628438,0.0271524594117540948517806]
+- ];
++ function setLum(r, g, b, l) {
++ var d = l - getLum(r, g, b);
++ dr = r + d;
++ dg = g + d;
++ db = b + d;
++ var l = getLum(dr, dg, db),
++ mn = min(dr, dg, db),
++ mx = max(dr, dg, db);
++ if (mn < 0) {
++ var lmn = l - mn;
++ dr = l + (dr - l) * l / lmn;
++ dg = l + (dg - l) * l / lmn;
++ db = l + (db - l) * l / lmn;
++ }
++ if (mx > 255) {
++ var ln = 255 - l,
++ mxl = mx - l;
++ dr = l + (dr - l) * ln / mxl;
++ dg = l + (dg - l) * ln / mxl;
++ db = l + (db - l) * ln / mxl;
++ }
++ }
+
+- var abs = Math.abs,
+- sqrt = Math.sqrt,
+- cos = Math.cos,
+- PI = Math.PI;
++ function getSat(r, g, b) {
++ return max(r, g, b) - min(r, g, b);
++ }
+
+- return {
+- TOLERANCE: 10e-6,
+- EPSILON: 10e-12,
++ function setSat(r, g, b, s) {
++ var col = [r, g, b],
++ mx = max(r, g, b),
++ mn = min(r, g, b),
++ md;
++ mn = mn === r ? 0 : mn === g ? 1 : 2;
++ mx = mx === r ? 0 : mx === g ? 1 : 2;
++ md = min(mn, mx) === 0 ? max(mn, mx) === 1 ? 2 : 1 : 0;
++ if (col[mx] > col[mn]) {
++ col[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]);
++ col[mx] = s;
++ } else {
++ col[md] = col[mx] = 0;
++ }
++ col[mn] = 0;
++ dr = col[0];
++ dg = col[1];
++ db = col[2];
++ }
+
+- integrate: function(f, a, b, n) {
+- var x = abscissas[n - 2],
+- w = weights[n - 2],
+- A = 0.5 * (b - a),
+- B = A + a,
+- i = 0,
+- m = (n + 1) >> 1,
+- sum = n & 1 ? w[i++] * f(B) : 0;
+- while (i < m) {
+- var Ax = A * x[i];
+- sum += w[i++] * (f(B + Ax) + f(B - Ax));
+- }
+- return A * sum;
++ var modes = {
++ multiply: function() {
++ dr = br * sr / 255;
++ dg = bg * sg / 255;
++ db = bb * sb / 255;
+ },
+
+- findRoot: function(f, df, x, a, b, n, tolerance) {
+- for (var i = 0; i < n; i++) {
+- var fx = f(x),
+- dx = fx / df(x);
+- if (abs(dx) < tolerance)
+- return x;
+- var nx = x - dx;
+- if (fx > 0) {
+- b = x;
+- x = nx <= a ? 0.5 * (a + b) : nx;
+- } else {
+- a = x;
+- x = nx >= b ? 0.5 * (a + b) : nx;
+- }
+- }
++ screen: function() {
++ dr = br + sr - (br * sr / 255);
++ dg = bg + sg - (bg * sg / 255);
++ db = bb + sb - (bb * sb / 255);
+ },
+
+- solveQuadratic: function(a, b, c, roots, tolerance) {
+- if (abs(a) < tolerance) {
+- if (abs(b) >= tolerance) {
+- roots[0] = -c / b;
+- return 1;
+- }
+- if (abs(c) < tolerance)
+- return -1;
+- return 0;
+- }
+- var q = b * b - 4 * a * c;
+- if (q < 0)
+- return 0;
+- q = sqrt(q);
+- if (b < 0)
+- q = -q;
+- q = (b + q) * -0.5;
+- var n = 0;
+- if (abs(q) >= tolerance)
+- roots[n++] = c / q;
+- if (abs(a) >= tolerance)
+- roots[n++] = q / a;
+- return n;
++ overlay: function() {
++ dr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255;
++ dg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255;
++ db = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255;
+ },
+
+- solveCubic: function(a, b, c, d, roots, tolerance) {
+- if (abs(a) < tolerance)
+- return Numerical.solveQuadratic(b, c, d, roots, tolerance);
+- b /= a;
+- c /= a;
+- d /= a;
+- var Q = (b * b - 3 * c) / 9,
+- R = (2 * b * b * b - 9 * b * c + 27 * d) / 54,
+- Q3 = Q * Q * Q,
+- R2 = R * R;
+- b /= 3;
+- if (R2 < Q3) {
+- var theta = Math.acos(R / sqrt(Q3)),
+- q = -2 * sqrt(Q);
+- roots[0] = q * cos(theta / 3) - b;
+- roots[1] = q * cos((theta + 2 * PI) / 3) - b;
+- roots[2] = q * cos((theta - 2 * PI) / 3) - b;
+- return 3;
+- } else {
+- var A = -Math.pow(abs(R) + sqrt(R2 - Q3), 1 / 3);
+- if (R < 0) A = -A;
+- var B = (abs(A) < tolerance) ? 0 : Q / A;
+- roots[0] = (A + B) - b;
+- return 1;
+- }
+- return 0;
++ 'soft-light': function() {
++ var t = sr * br / 255;
++ dr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255;
++ t = sg * bg / 255;
++ dg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255;
++ t = sb * bb / 255;
++ db = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255;
++ },
++
++ 'hard-light': function() {
++ dr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255;
++ dg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255;
++ db = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255;
++ },
++
++ 'color-dodge': function() {
++ dr = br === 0 ? 0 : sr === 255 ? 255 : min(255, 255 * br / (255 - sr));
++ dg = bg === 0 ? 0 : sg === 255 ? 255 : min(255, 255 * bg / (255 - sg));
++ db = bb === 0 ? 0 : sb === 255 ? 255 : min(255, 255 * bb / (255 - sb));
++ },
++
++ 'color-burn': function() {
++ dr = br === 255 ? 255 : sr === 0 ? 0 : max(0, 255 - (255 - br) * 255 / sr);
++ dg = bg === 255 ? 255 : sg === 0 ? 0 : max(0, 255 - (255 - bg) * 255 / sg);
++ db = bb === 255 ? 255 : sb === 0 ? 0 : max(0, 255 - (255 - bb) * 255 / sb);
++ },
++
++ darken: function() {
++ dr = br < sr ? br : sr;
++ dg = bg < sg ? bg : sg;
++ db = bb < sb ? bb : sb;
++ },
++
++ lighten: function() {
++ dr = br > sr ? br : sr;
++ dg = bg > sg ? bg : sg;
++ db = bb > sb ? bb : sb;
++ },
++
++ difference: function() {
++ dr = br - sr;
++ if (dr < 0)
++ dr = -dr;
++ dg = bg - sg;
++ if (dg < 0)
++ dg = -dg;
++ db = bb - sb;
++ if (db < 0)
++ db = -db;
++ },
++
++ exclusion: function() {
++ dr = br + sr * (255 - br - br) / 255;
++ dg = bg + sg * (255 - bg - bg) / 255;
++ db = bb + sb * (255 - bb - bb) / 255;
++ },
++
++ hue: function() {
++ setSat(sr, sg, sb, getSat(br, bg, bb));
++ setLum(dr, dg, db, getLum(br, bg, bb));
++ },
++
++ saturation: function() {
++ setSat(br, bg, bb, getSat(sr, sg, sb));
++ setLum(dr, dg, db, getLum(br, bg, bb));
++ },
++
++ luminosity: function() {
++ setLum(br, bg, bb, getLum(sr, sg, sb));
++ },
++
++ color: function() {
++ setLum(sr, sg, sb, getLum(br, bg, bb));
++ },
++
++ add: function() {
++ dr = min(br + sr, 255);
++ dg = min(bg + sg, 255);
++ db = min(bb + sb, 255);
++ },
++
++ subtract: function() {
++ dr = max(br - sr, 0);
++ dg = max(bg - sg, 0);
++ db = max(bb - sb, 0);
++ },
++
++ average: function() {
++ dr = (br + sr) / 2;
++ dg = (bg + sg) / 2;
++ db = (bb + sb) / 2;
++ },
++
++ negation: function() {
++ dr = 255 - abs(255 - sr - br);
++ dg = 255 - abs(255 - sg - bg);
++ db = 255 - abs(255 - sb - bb);
+ }
+ };
+-};
+
+-var BlendMode = {
+- process: function(blendMode, srcContext, dstContext, alpha, offset) {
++ var nativeModes = this.nativeModes = Base.each([
++ 'source-over', 'source-in', 'source-out', 'source-atop',
++ 'destination-over', 'destination-in', 'destination-out',
++ 'destination-atop', 'lighter', 'darker', 'copy', 'xor'
++ ], function(mode) {
++ this[mode] = true;
++ }, {});
++
++ var ctx = CanvasProvider.getContext(1, 1);
++ Base.each(modes, function(func, mode) {
++ ctx.save();
++ var darken = mode === 'darken',
++ ok = false;
++ ctx.fillStyle = darken ? '#300' : '#a00';
++ ctx.fillRect(0, 0, 1, 1);
++ ctx.globalCompositeOperation = mode;
++ if (ctx.globalCompositeOperation === mode) {
++ ctx.fillStyle = darken ? '#a00' : '#300';
++ ctx.fillRect(0, 0, 1, 1);
++ ok = ctx.getImageData(0, 0, 1, 1).data[0] !== (darken ? 170 : 51);
++ }
++ nativeModes[mode] = ok;
++ ctx.restore();
++ });
++ CanvasProvider.release(ctx);
++
++ this.process = function(mode, srcContext, dstContext, alpha, offset) {
+ var srcCanvas = srcContext.canvas,
+- dstData = dstContext.getImageData(offset.x, offset.y,
++ normal = mode === 'normal';
++ if (normal || nativeModes[mode]) {
++ dstContext.save();
++ dstContext.setTransform(1, 0, 0, 1, 0, 0);
++ dstContext.globalAlpha = alpha;
++ if (!normal)
++ dstContext.globalCompositeOperation = mode;
++ dstContext.drawImage(srcCanvas, offset.x, offset.y);
++ dstContext.restore();
++ } else {
++ var process = modes[mode];
++ if (!process)
++ return;
++ var dstData = dstContext.getImageData(offset.x, offset.y,
+ srcCanvas.width, srcCanvas.height),
+- dst = dstData.data,
+- src = srcContext.getImageData(0, 0,
+- srcCanvas.width, srcCanvas.height).data,
+- min = Math.min,
+- max = Math.max,
+- abs = Math.abs,
+- sr, sg, sb, sa,
+- br, bg, bb, ba,
+- dr, dg, db;
+-
+- function getLum(r, g, b) {
+- return 0.2989 * r + 0.587 * g + 0.114 * b;
+- }
+-
+- function setLum(r, g, b, l) {
+- var d = l - getLum(r, g, b);
+- dr = r + d;
+- dg = g + d;
+- db = b + d;
+- var l = getLum(dr, dg, db),
+- mn = min(dr, dg, db),
+- mx = max(dr, dg, db);
+- if (mn < 0) {
+- var lmn = l - mn;
+- dr = l + (dr - l) * l / lmn;
+- dg = l + (dg - l) * l / lmn;
+- db = l + (db - l) * l / lmn;
+- }
+- if (mx > 255) {
+- var ln = 255 - l, mxl = mx - l;
+- dr = l + (dr - l) * ln / mxl;
+- dg = l + (dg - l) * ln / mxl;
+- db = l + (db - l) * ln / mxl;
+- }
+- }
+-
+- function getSat(r, g, b) {
+- return max(r, g, b) - min(r, g, b);
+- }
+-
+- function setSat(r, g, b, s) {
+- var col = [r, g, b],
+- mx = max(r, g, b),
+- mn = min(r, g, b),
+- md;
+- mn = mn == r ? 0 : mn == g ? 1 : 2;
+- mx = mx == r ? 0 : mx == g ? 1 : 2;
+- md = min(mn, mx) == 0 ? max(mn, mx) == 1 ? 2 : 1 : 0;
+- if (col[mx] > col[mn]) {
+- col[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]);
+- col[mx] = s;
++ dst = dstData.data,
++ src = srcContext.getImageData(0, 0,
++ srcCanvas.width, srcCanvas.height).data;
++ for (var i = 0, l = dst.length; i < l; i += 4) {
++ sr = src[i];
++ br = dst[i];
++ sg = src[i + 1];
++ bg = dst[i + 1];
++ sb = src[i + 2];
++ bb = dst[i + 2];
++ sa = src[i + 3];
++ ba = dst[i + 3];
++ process();
++ var a1 = sa * alpha / 255,
++ a2 = 1 - a1;
++ dst[i] = a1 * dr + a2 * br;
++ dst[i + 1] = a1 * dg + a2 * bg;
++ dst[i + 2] = a1 * db + a2 * bb;
++ dst[i + 3] = sa * alpha + a2 * ba;
++ }
++ dstContext.putImageData(dstData, offset.x, offset.y);
++ }
++ };
++};
++
++var SVGStyles = Base.each({
++ fillColor: ['fill', 'color'],
++ strokeColor: ['stroke', 'color'],
++ strokeWidth: ['stroke-width', 'number'],
++ strokeCap: ['stroke-linecap', 'string'],
++ strokeJoin: ['stroke-linejoin', 'string'],
++ miterLimit: ['stroke-miterlimit', 'number'],
++ dashArray: ['stroke-dasharray', 'array'],
++ dashOffset: ['stroke-dashoffset', 'number'],
++ font: ['font-family', 'string'],
++ fontSize: ['font-size', 'number'],
++ justification: ['text-anchor', 'lookup', {
++ left: 'start',
++ center: 'middle',
++ right: 'end'
++ }],
++ opacity: ['opacity', 'number'],
++ blendMode: ['mix-blend-mode', 'string']
++}, function(entry, key) {
++ var part = Base.capitalize(key),
++ lookup = entry[2];
++ this[key] = {
++ type: entry[1],
++ property: key,
++ attribute: entry[0],
++ toSVG: lookup,
++ fromSVG: lookup && Base.each(lookup, function(value, name) {
++ this[value] = name;
++ }, {}),
++ get: 'get' + part,
++ set: 'set' + part
++ };
++}, {});
++
++var SVGNamespaces = {
++ href: 'http://www.w3.org/1999/xlink',
++ xlink: 'http://www.w3.org/2000/xmlns'
++};
++
++new function() {
++ var formatter;
++
++ function setAttributes(node, attrs) {
++ for (var key in attrs) {
++ var val = attrs[key],
++ namespace = SVGNamespaces[key];
++ if (typeof val === 'number')
++ val = formatter.number(val);
++ if (namespace) {
++ node.setAttributeNS(namespace, key, val);
+ } else {
+- col[md] = col[mx] = 0;
++ node.setAttribute(key, val);
+ }
+- col[mn] = 0;
+- dr = col[0];
+- dg = col[1];
+- db = col[2];
+ }
++ return node;
++ }
+
+- var modes = {
+- multiply: function() {
+- dr = br * sr / 255;
+- dg = bg * sg / 255;
+- db = bb * sb / 255;
+- },
++ function createElement(tag, attrs) {
++ return setAttributes(
++ document.createElementNS('http://www.w3.org/2000/svg', tag), attrs);
++ }
+
+- screen: function() {
+- dr = 255 - (255 - br) * (255 - sr) / 255;
+- dg = 255 - (255 - bg) * (255 - sg) / 255;
+- db = 255 - (255 - bb) * (255 - sb) / 255;
+- },
++ function getDistance(segments, index1, index2) {
++ return segments[index1]._point.getDistance(segments[index2]._point);
++ }
+
+- overlay: function() {
+- dr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255;
+- dg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255;
+- db = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255;
+- },
++ function getTransform(item, coordinates) {
++ var matrix = item._matrix,
++ trans = matrix.getTranslation(),
++ attrs = {};
++ if (coordinates) {
++ matrix = matrix.shiftless();
++ var point = matrix._inverseTransform(trans);
++ attrs.x = point.x;
++ attrs.y = point.y;
++ trans = null;
++ }
++ if (matrix.isIdentity())
++ return attrs;
++ var decomposed = matrix.decompose();
++ if (decomposed && !decomposed.shearing) {
++ var parts = [],
++ angle = decomposed.rotation,
++ scale = decomposed.scaling;
++ if (trans && !trans.isZero())
++ parts.push('translate(' + formatter.point(trans) + ')');
++ if (!Numerical.isZero(scale.x - 1) || !Numerical.isZero(scale.y - 1))
++ parts.push('scale(' + formatter.point(scale) +')');
++ if (angle)
++ parts.push('rotate(' + formatter.number(angle) + ')');
++ attrs.transform = parts.join(' ');
++ } else {
++ attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')';
++ }
++ return attrs;
++ }
+
+- 'soft-light': function() {
+- var t = sr * br / 255;
+- dr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255;
+- t = sg * bg / 255;
+- dg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255;
+- t = sb * bb / 255;
+- db = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255;
+- },
++ function determineAngle(path, segments, type, center) {
++ var topCenter = type === 'rect'
++ ? segments[1]._point.add(segments[2]._point).divide(2)
++ : type === 'roundrect'
++ ? segments[3]._point.add(segments[4]._point).divide(2)
++ : type === 'circle' || type === 'ellipse'
++ ? segments[1]._point
++ : null;
++ var angle = topCenter && topCenter.subtract(center).getAngle() + 90;
++ return Numerical.isZero(angle || 0) ? 0 : angle;
++ }
+
+- 'hard-light': function() {
+- dr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255;
+- dg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255;
+- db = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255;
+- },
++ function determineType(path, segments) {
++ function isColinear(i, j) {
++ var seg1 = segments[i],
++ seg2 = seg1.getNext(),
++ seg3 = segments[j],
++ seg4 = seg3.getNext();
++ return seg1._handleOut.isZero() && seg2._handleIn.isZero()
++ && seg3._handleOut.isZero() && seg4._handleIn.isZero()
++ && seg2._point.subtract(seg1._point).isColinear(
++ seg4._point.subtract(seg3._point));
++ }
+
+- 'color-dodge': function() {
+- dr = sr == 255 ? sr : min(255, br * 255 / (255 - sr));
+- dg = sg == 255 ? sg : min(255, bg * 255 / (255 - sg));
+- db = sb == 255 ? sb : min(255, bb * 255 / (255 - sb));
+- },
++ function isArc(i) {
++ var segment = segments[i],
++ next = segment.getNext(),
++ handle1 = segment._handleOut,
++ handle2 = next._handleIn,
++ kappa = Numerical.KAPPA;
++ if (handle1.isOrthogonal(handle2)) {
++ var from = segment._point,
++ to = next._point,
++ corner = new Line(from, handle1, true).intersect(
++ new Line(to, handle2, true), true);
++ return corner && Numerical.isZero(handle1.getLength() /
++ corner.subtract(from).getLength() - kappa)
++ && Numerical.isZero(handle2.getLength() /
++ corner.subtract(to).getLength() - kappa);
++ }
++ }
++
++ if (path.isPolygon()) {
++ return segments.length === 4 && path._closed
++ && isColinear(0, 2) && isColinear(1, 3)
++ ? 'rect'
++ : segments.length === 0
++ ? 'empty'
++ : segments.length >= 3
++ ? path._closed ? 'polygon' : 'polyline'
++ : 'line';
++ } else if (path._closed) {
++ if (segments.length === 8
++ && isArc(0) && isArc(2) && isArc(4) && isArc(6)
++ && isColinear(1, 5) && isColinear(3, 7)) {
++ return 'roundrect';
++ } else if (segments.length === 4
++ && isArc(0) && isArc(1) && isArc(2) && isArc(3)) {
++ return Numerical.isZero(getDistance(segments, 0, 2)
++ - getDistance(segments, 1, 3))
++ ? 'circle'
++ : 'ellipse';
++ }
++ }
++ return 'path';
++ }
+
+- 'color-burn': function() {
+- dr = sr == 0 ? 0 : max(255 - ((255 - br) * 255) / sr, 0);
+- dg = sg == 0 ? 0 : max(255 - ((255 - bg) * 255) / sg, 0);
+- db = sb == 0 ? 0 : max(255 - ((255 - bb) * 255) / sb, 0);
+- },
++ function exportGroup(item) {
++ var attrs = getTransform(item),
++ children = item._children;
++ var node = createElement('g', attrs);
++ for (var i = 0, l = children.length; i < l; i++) {
++ var child = children[i];
++ var childNode = exportSVG(child);
++ if (childNode) {
++ if (child.isClipMask()) {
++ var clip = createElement('clipPath');
++ clip.appendChild(childNode);
++ setDefinition(child, clip, 'clip');
++ setAttributes(node, {
++ 'clip-path': 'url(#' + clip.id + ')'
++ });
++ } else {
++ node.appendChild(childNode);
++ }
++ }
++ }
++ return node;
++ }
+
+- darken: function() {
+- dr = br < sr ? br : sr;
+- dg = bg < sg ? bg : sg;
+- db = bb < sb ? bb : sb;
+- },
++ function exportRaster(item) {
++ var attrs = getTransform(item, true),
++ size = item.getSize();
++ attrs.x -= size.width / 2;
++ attrs.y -= size.height / 2;
++ attrs.width = size.width;
++ attrs.height = size.height;
++ attrs.href = item.toDataURL();
++ return createElement('image', attrs);
++ }
+
+- lighten: function() {
+- dr = br > sr ? br : sr;
+- dg = bg > sg ? bg : sg;
+- db = bb > sb ? bb : sb;
+- },
++ function exportPath(item) {
++ var segments = item._segments,
++ center = item.getPosition(true),
++ type = determineType(item, segments),
++ angle = determineAngle(item, segments, type, center),
++ attrs;
++ switch (type) {
++ case 'empty':
++ return null;
++ case 'path':
++ var data = item.getPathData();
++ attrs = data && { d: data };
++ break;
++ case 'polyline':
++ case 'polygon':
++ var parts = [];
++ for(i = 0, l = segments.length; i < l; i++)
++ parts.push(formatter.point(segments[i]._point));
++ attrs = {
++ points: parts.join(' ')
++ };
++ break;
++ case 'rect':
++ var width = getDistance(segments, 0, 3),
++ height = getDistance(segments, 0, 1),
++ point = segments[1]._point.rotate(-angle, center);
++ attrs = {
++ x: point.x,
++ y: point.y,
++ width: width,
++ height: height
++ };
++ break;
++ case 'roundrect':
++ type = 'rect';
++ var width = getDistance(segments, 1, 6),
++ height = getDistance(segments, 0, 3),
++ rx = (width - getDistance(segments, 0, 7)) / 2,
++ ry = (height - getDistance(segments, 1, 2)) / 2,
++ left = segments[3]._point,
++ right = segments[4]._point,
++ point = left.subtract(right.subtract(left).normalize(rx))
++ .rotate(-angle, center);
++ attrs = {
++ x: point.x,
++ y: point.y,
++ width: width,
++ height: height,
++ rx: rx,
++ ry: ry
++ };
++ break;
++ case'line':
++ var first = segments[0]._point,
++ last = segments[segments.length - 1]._point;
++ attrs = {
++ x1: first.x,
++ y1: first.y,
++ x2: last.x,
++ y2: last.y
++ };
++ break;
++ case 'circle':
++ var radius = getDistance(segments, 0, 2) / 2;
++ attrs = {
++ cx: center.x,
++ cy: center.y,
++ r: radius
++ };
++ break;
++ case 'ellipse':
++ var rx = getDistance(segments, 2, 0) / 2,
++ ry = getDistance(segments, 3, 1) / 2;
++ attrs = {
++ cx: center.x,
++ cy: center.y,
++ rx: rx,
++ ry: ry
++ };
++ break;
++ }
++ if (angle) {
++ attrs.transform = 'rotate(' + formatter.number(angle) + ','
++ + formatter.point(center) + ')';
++ item._gradientMatrix = new Matrix().rotate(-angle, center);
++ }
++ return createElement(type, attrs);
++ }
+
+- difference: function() {
+- dr = br - sr;
+- if (dr < 0)
+- dr = -dr;
+- dg = bg - sg;
+- if (dg < 0)
+- dg = -dg;
+- db = bb - sb;
+- if (db < 0)
+- db = -db;
+- },
++ function exportCompoundPath(item) {
++ var attrs = getTransform(item, true);
++ var data = item.getPathData();
++ if (data)
++ attrs.d = data;
++ return createElement('path', attrs);
++ }
+
+- exclusion: function() {
+- dr = br + sr * (255 - br - br) / 255;
+- dg = bg + sg * (255 - bg - bg) / 255;
+- db = bb + sb * (255 - bb - bb) / 255;
+- },
++ function exportPlacedSymbol(item) {
++ var attrs = getTransform(item, true),
++ symbol = item.getSymbol(),
++ symbolNode = getDefinition(symbol, 'symbol');
++ definition = symbol.getDefinition(),
++ bounds = definition.getBounds();
++ if (!symbolNode) {
++ symbolNode = createElement('symbol', {
++ viewBox: formatter.rectangle(bounds)
++ });
++ symbolNode.appendChild(exportSVG(definition));
++ setDefinition(symbol, symbolNode, 'symbol');
++ }
++ attrs.href = '#' + symbolNode.id;
++ attrs.x += bounds.x;
++ attrs.y += bounds.y;
++ attrs.width = formatter.number(bounds.width);
++ attrs.height = formatter.number(bounds.height);
++ return createElement('use', attrs);
++ }
+
+- hue: function() {
+- setSat(sr, sg, sb, getSat(br, bg, bb));
+- setLum(dr, dg, db, getLum(br, bg, bb));
+- },
++ function exportGradient(color, item) {
++ var gradientNode = getDefinition(color, 'color');
++ if (!gradientNode) {
++ var gradient = color.getGradient(),
++ radial = gradient._radial,
++ matrix = item._gradientMatrix,
++ origin = color.getOrigin().transform(matrix),
++ destination = color.getDestination().transform(matrix),
++ attrs;
++ if (radial) {
++ attrs = {
++ cx: origin.x,
++ cy: origin.y,
++ r: origin.getDistance(destination)
++ };
++ var highlight = color.getHighlight();
++ if (highlight) {
++ highlight = highlight.transform(matrix);
++ attrs.fx = highlight.x;
++ attrs.fy = highlight.y;
++ }
++ } else {
++ attrs = {
++ x1: origin.x,
++ y1: origin.y,
++ x2: destination.x,
++ y2: destination.y
++ };
++ }
++ attrs.gradientUnits = 'userSpaceOnUse';
++ gradientNode = createElement(
++ (radial ? 'radial' : 'linear') + 'Gradient', attrs);
++ var stops = gradient._stops;
++ for (var i = 0, l = stops.length; i < l; i++) {
++ var stop = stops[i],
++ stopColor = stop._color,
++ alpha = stopColor.getAlpha();
++ attrs = {
++ offset: stop._rampPoint,
++ 'stop-color': stopColor.toCSS(true)
++ };
++ if (alpha < 1)
++ attrs['stop-opacity'] = alpha;
++ gradientNode.appendChild(createElement('stop', attrs));
++ }
++ setDefinition(color, gradientNode, 'color');
++ }
++ return 'url(#' + gradientNode.id + ')';
++ }
+
+- saturation: function() {
+- setSat(br, bg, bb, getSat(sr, sg, sb));
+- setLum(dr, dg, db, getLum(br, bg, bb));
+- },
++ function exportText(item) {
++ var node = createElement('text', getTransform(item, true));
++ node.textContent = item._content;
++ return node;
++ }
+
+- luminosity: function() {
+- setLum(br, bg, bb, getLum(sr, sg, sb));
+- },
++ var exporters = {
++ group: exportGroup,
++ layer: exportGroup,
++ raster: exportRaster,
++ path: exportPath,
++ 'compound-path': exportCompoundPath,
++ 'placed-symbol': exportPlacedSymbol,
++ 'point-text': exportText
++ };
+
+- color: function() {
+- setLum(sr, sg, sb, getLum(br, bg, bb));
+- },
++ function applyStyle(item, node) {
++ var attrs = {},
++ parent = item.getParent();
++
++ if (item._name != null)
++ attrs.id = item._name;
++
++ Base.each(SVGStyles, function(entry) {
++ var get = entry.get,
++ type = entry.type,
++ value = item[get]();
++ if (!parent || !Base.equals(parent[get](), value)) {
++ if (type === 'color' && value != null) {
++ var alpha = value.getAlpha();
++ if (alpha < 1)
++ attrs[entry.attribute + '-opacity'] = alpha;
++ }
++ attrs[entry.attribute] = value == null
++ ? 'none'
++ : type === 'number'
++ ? formatter.number(value)
++ : type === 'color'
++ ? value.gradient
++ ? exportGradient(value, item)
++ : value.toCSS(true)
++ : type === 'array'
++ ? value.join(',')
++ : type === 'lookup'
++ ? entry.toSVG[value]
++ : value;
++ }
++ });
+
+- add: function() {
+- dr = min(br + sr, 255);
+- dg = min(bg + sg, 255);
+- db = min(bb + sb, 255);
+- },
++ if (attrs.opacity === 1)
++ delete attrs.opacity;
+
+- subtract: function() {
+- dr = max(br - sr, 0);
+- dg = max(bg - sg, 0);
+- db = max(bb - sb, 0);
+- },
++ if (item._visibility != null && !item._visibility)
++ attrs.visibility = 'hidden';
+
+- average: function() {
+- dr = (br + sr) / 2;
+- dg = (bg + sg) / 2;
+- db = (bb + sb) / 2;
+- },
++ delete item._gradientMatrix;
++ return setAttributes(node, attrs);
++ }
+
+- negation: function() {
+- dr = 255 - abs(255 - sr - br);
+- dg = 255 - abs(255 - sg - bg);
+- db = 255 - abs(255 - sb - bb);
++ var definitions;
++ function getDefinition(item, type) {
++ if (!definitions)
++ definitions = { ids: {}, svgs: {} };
++ return item && definitions.svgs[type + '-' + item._id];
++ }
++
++ function setDefinition(item, node, type) {
++ if (!definitions)
++ getDefinition();
++ var id = definitions.ids[type] = (definitions.ids[type] || 0) + 1;
++ node.id = type + '-' + id;
++ definitions.svgs[type + '-' + item._id] = node;
++ }
++
++ function exportDefinitions(node, options) {
++ if (!definitions)
++ return node;
++ var svg = node.nodeName.toLowerCase() === 'svg' && node,
++ defs = null;
++ for (var i in definitions.svgs) {
++ if (!defs) {
++ if (!svg) {
++ svg = createElement('svg');
++ svg.appendChild(node);
++ }
++ defs = svg.insertBefore(createElement('defs'), svg.firstChild);
+ }
++ defs.appendChild(definitions.svgs[i]);
++ }
++ definitions = null;
++ return options && options.asString
++ ? new XMLSerializer().serializeToString(svg)
++ : svg;
++ }
++
++ function exportSVG(item) {
++ var exporter = exporters[item._type],
++ node = exporter && exporter(item, item._type);
++ if (node && item._data)
++ node.setAttribute('data-paper-data', JSON.stringify(item._data));
++ return node && applyStyle(item, node);
++ }
++
++ function setOptions(options) {
++ formatter = options && options.precision
++ ? new Formatter(options.precision)
++ : Formatter.instance;
++ }
++
++ Item.inject({
++ exportSVG: function(options) {
++ setOptions(options);
++ return exportDefinitions(exportSVG(this), options);
++ }
++ });
++
++ Project.inject({
++ exportSVG: function(options) {
++ setOptions(options);
++ var layers = this.layers,
++ size = this.view.getSize(),
++ node = createElement('svg', {
++ x: 0,
++ y: 0,
++ width: size.width,
++ height: size.height,
++ version: '1.1',
++ xmlns: 'http://www.w3.org/2000/svg',
++ 'xmlns:xlink': 'http://www.w3.org/1999/xlink'
++ });
++ for (var i = 0, l = layers.length; i < l; i++)
++ node.appendChild(exportSVG(layers[i]));
++ return exportDefinitions(node, options);
++ }
++ });
++};
++
++new function() {
++
++ function getValue(node, name, isString, allowNull) {
++ var namespace = SVGNamespaces[name],
++ value = namespace
++ ? node.getAttributeNS(namespace, name)
++ : node.getAttribute(name);
++ if (value === 'null')
++ value = null;
++ return value == null
++ ? allowNull
++ ? null
++ : isString
++ ? ''
++ : 0
++ : isString
++ ? value
++ : parseFloat(value);
++ }
++
++ function getPoint(node, x, y, allowNull) {
++ x = getValue(node, x, false, allowNull);
++ y = getValue(node, y, false, allowNull);
++ return allowNull && x == null && y == null ? null
++ : new Point(x || 0, y || 0);
++ }
++
++ function getSize(node, w, h, allowNull) {
++ w = getValue(node, w, false, allowNull);
++ h = getValue(node, h, false, allowNull);
++ return allowNull && w == null && h == null ? null
++ : new Size(w || 0, h || 0);
++ }
++
++ function convertValue(value, type, lookup) {
++ return value === 'none'
++ ? null
++ : type === 'number'
++ ? parseFloat(value)
++ : type === 'array'
++ ? value ? value.split(/[\s,]+/g).map(parseFloat) : []
++ : type === 'color'
++ ? getDefinition(value) || value
++ : type === 'lookup'
++ ? lookup[value]
++ : value;
++ }
++
++ function importGroup(node, type) {
++ var nodes = node.childNodes,
++ clip = type === 'clippath',
++ item = clip ? new CompoundPath() : new Group(),
++ project = item._project,
++ currentStyle = project._currentStyle,
++ children = [];
++ if (!clip) {
++ item._transformContent = false;
++ item = applyAttributes(item, node);
++ project._currentStyle = item._style.clone();
++ }
++ for (var i = 0, l = nodes.length; i < l; i++) {
++ var childNode = nodes[i],
++ child;
++ if (childNode.nodeType == 1 && (child = importSVG(childNode))) {
++ if (clip && child instanceof CompoundPath) {
++ children.push.apply(children, child.removeChildren());
++ child.remove();
++ } else if (!(child instanceof Symbol)) {
++ children.push(child);
++ }
++ }
++ }
++ item.addChildren(children);
++ if (clip)
++ item = applyAttributes(item.reduce(), node);
++ project._currentStyle = currentStyle;
++ if (clip || type === 'defs') {
++ item.remove();
++ item = null;
++ }
++ return item;
++ }
++
++ function importPoly(node, type) {
++ var path = new Path(),
++ points = node.points;
++ path.moveTo(points.getItem(0));
++ for (var i = 1, l = points.numberOfItems; i < l; i++)
++ path.lineTo(points.getItem(i));
++ if (type === 'polygon')
++ path.closePath();
++ return path;
++ }
++
++ function importPath(node) {
++ var data = node.getAttribute('d'),
++ path = data.match(/m/gi).length > 1
++ ? new CompoundPath()
++ : new Path();
++ path.setPathData(data);
++ return path;
++ }
++
++ function importGradient(node, type) {
++ var nodes = node.childNodes,
++ stops = [];
++ for (var i = 0, l = nodes.length; i < l; i++) {
++ var child = nodes[i];
++ if (child.nodeType == 1)
++ stops.push(applyAttributes(new GradientStop(), child));
++ }
++ var isRadial = type === 'radialgradient',
++ gradient = new Gradient(stops, isRadial),
++ origin, destination, highlight;
++ if (isRadial) {
++ origin = getPoint(node, 'cx', 'cy');
++ destination = origin.add(getValue(node, 'r'), 0);
++ highlight = getPoint(node, 'fx', 'fy', true);
++ } else {
++ origin = getPoint(node, 'x1', 'y1');
++ destination = getPoint(node, 'x2', 'y2');
++ }
++ applyAttributes(
++ new Color(gradient, origin, destination, highlight), node);
++ return null;
++ }
++
++ var importers = {
++ g: importGroup,
++ svg: importGroup,
++ clippath: importGroup,
++ polygon: importPoly,
++ polyline: importPoly,
++ path: importPath,
++ lineargradient: importGradient,
++ radialgradient: importGradient,
++
++ image: function (node) {
++ var raster = new Raster(getValue(node, 'href', true));
++ raster.attach('load', function() {
++ var size = getSize(node, 'width', 'height');
++ this.setSize(size);
++ this.translate(getPoint(node, 'x', 'y').add(size.divide(2)));
++ });
++ return raster;
++ },
++
++ symbol: function(node, type) {
++ return new Symbol(importGroup(node, type), true);
++ },
++
++ defs: importGroup,
++
++ use: function(node) {
++ var id = (getValue(node, 'href', true) || '').substring(1),
++ definition = definitions[id],
++ point = getPoint(node, 'x', 'y');
++ return definition
++ ? definition instanceof Symbol
++ ? definition.place(point)
++ : definition.clone().translate(point)
++ : null;
++ },
++
++ circle: function(node) {
++ return new Path.Circle(getPoint(node, 'cx', 'cy'),
++ getValue(node, 'r'));
++ },
++
++ ellipse: function(node) {
++ var center = getPoint(node, 'cx', 'cy'),
++ radius = getSize(node, 'rx', 'ry');
++ return new Path.Ellipse(new Rectangle(center.subtract(radius),
++ center.add(radius)));
++ },
++
++ rect: function(node) {
++ var point = getPoint(node, 'x', 'y'),
++ size = getSize(node, 'width', 'height'),
++ radius = getSize(node, 'rx', 'ry');
++ return new Path.Rectangle(new Rectangle(point, size), radius);
++ },
++
++ line: function(node) {
++ return new Path.Line(getPoint(node, 'x1', 'y1'),
++ getPoint(node, 'x2', 'y2'));
++ },
++
++ text: function(node) {
++ var text = new PointText(getPoint(node, 'x', 'y', false)
++ .add(getPoint(node, 'dx', 'dy', false)));
++ text.setContent(node.textContent.trim() || '');
++ return text;
++ }
++ };
++
++ function applyTransform(item, value, name, node) {
++ var transforms = (node.getAttribute(name) || '').split(/\)\s*/g),
++ matrix = new Matrix();
++ for (var i = 0, l = transforms.length; i < l; i++) {
++ var transform = transforms[i];
++ if (!transform)
++ break;
++ var parts = transform.split('('),
++ command = parts[0],
++ v = parts[1].split(/[\s,]+/g);
++ for (var j = 0, m = v.length; j < m; j++)
++ v[j] = parseFloat(v[j]);
++ switch (command) {
++ case 'matrix':
++ matrix.concatenate(
++ new Matrix(v[0], v[2], v[1], v[3], v[4], v[5]));
++ break;
++ case 'rotate':
++ matrix.rotate(v[0], v[1], v[2]);
++ break;
++ case 'translate':
++ matrix.translate(v[0], v[1]);
++ break;
++ case 'scale':
++ matrix.scale(v);
++ break;
++ case 'skewX':
++ case 'skewY':
++ var value = Math.tan(v[0] * Math.PI / 180),
++ isX = command == 'skewX';
++ matrix.shear(isX ? value : 0, isX ? 0 : value);
++ break;
++ }
++ }
++ item.transform(matrix);
++ }
++
++ function applyOpacity(item, value, name) {
++ var color = item[name === 'fill-opacity' ? 'getFillColor'
++ : 'getStrokeColor']();
++ if (color)
++ color.setAlpha(parseFloat(value));
++ }
++
++ var attributes = Base.merge(Base.each(SVGStyles, function(entry) {
++ this[entry.attribute] = function(item, value) {
++ item[entry.set](
++ convertValue(value, entry.type, entry.fromSVG));
+ };
++ }, {}), {
++ id: function(item, value) {
++ definitions[value] = item;
++ if (item.setName)
++ item.setName(value);
++ },
+
+- var process = modes[blendMode];
+- if (!process)
+- return;
++ 'clip-path': function(item, value) {
++ var clip = getDefinition(value);
++ if (clip) {
++ clip = clip.clone();
++ clip.setClipMask(true);
++ if (item instanceof Group) {
++ item.insertChild(0, clip);
++ } else {
++ return new Group(clip, item);
++ }
++ }
++ },
++
++ gradientTransform: applyTransform,
++ transform: applyTransform,
++
++ 'fill-opacity': applyOpacity,
++ 'stroke-opacity': applyOpacity,
++
++ visibility: function(item, value) {
++ item.setVisible(value === 'visible');
++ },
++
++ 'stop-color': function(item, value) {
++ if (item.setColor)
++ item.setColor(value);
++ },
++
++ 'stop-opacity': function(item, value) {
++ if (item._color)
++ item._color.setAlpha(parseFloat(value));
++ },
++
++ offset: function(item, value) {
++ var percentage = value.match(/(.*)%$/);
++ item.setRampPoint(percentage
++ ? percentage[1] / 100
++ : parseFloat(value));
++ },
++
++ viewBox: function(item, value, name, node, styles) {
++ var rect = new Rectangle(convertValue(value, 'array')),
++ size = getSize(node, 'width', 'height', true);
++ if (item instanceof Group) {
++ var scale = size ? rect.getSize().divide(size) : 1,
++ matrix = new Matrix().translate(rect.getPoint()).scale(scale);
++ item.transform(matrix.inverted());
++ } else if (item instanceof Symbol) {
++ if (size)
++ rect.setSize(size);
++ var clip = getAttribute(node, 'overflow', styles) != 'visible',
++ group = item._definition;
++ if (clip && !rect.contains(group.getBounds())) {
++ clip = new Path.Rectangle(rect).transform(group._matrix);
++ clip.setClipMask(true);
++ group.addChild(clip);
++ }
++ }
++ }
++ });
++
++ function getAttribute(node, name, styles) {
++ var attr = node.attributes[name],
++ value = attr && attr.value;
++ if (!value) {
++ var style = Base.camelize(name);
++ value = node.style[style];
++ if (!value && styles.node[style] !== styles.parent[style])
++ value = styles.node[style];
++ }
++ return !value
++ ? undefined
++ : value === 'none'
++ ? null
++ : value;
++ }
+
+- for (var i = 0, l = dst.length; i < l; i += 4) {
+- sr = src[i];
+- br = dst[i];
+- sg = src[i + 1];
+- bg = dst[i + 1];
+- sb = src[i + 2];
+- bb = dst[i + 2];
+- sa = src[i + 3];
+- ba = dst[i + 3];
+- process();
+- var a1 = sa * alpha / 255,
+- a2 = 1 - a1;
+- dst[i] = a1 * dr + a2 * br;
+- dst[i + 1] = a1 * dg + a2 * bg;
+- dst[i + 2] = a1 * db + a2 * bb;
+- dst[i + 3] = sa * alpha + a2 * ba;
+- }
+- dstContext.putImageData(dstData, offset.x, offset.y);
++ function applyAttributes(item, node) {
++ var styles = {
++ node: DomElement.getStyles(node) || {},
++ parent: DomElement.getStyles(node.parentNode) || {}
++ };
++ Base.each(attributes, function(apply, name) {
++ var value = getAttribute(node, name, styles);
++ if (value !== undefined)
++ item = Base.pick(apply(item, value, name, node, styles), item);
++ });
++ return item;
+ }
++
++ var definitions = {};
++ function getDefinition(value) {
++ var match = value && value.match(/\((?:#|)([^)']+)/);
++ return match && definitions[match[1]];
++ }
++
++ function importSVG(node, clearDefs) {
++ if (typeof node === 'string')
++ node = new DOMParser().parseFromString(node, 'image/svg+xml');
++ var type = node.nodeName.toLowerCase(),
++ importer = importers[type],
++ item = importer && importer(node, type),
++ data = node.getAttribute('data-paper-data');
++ if (item && !(item instanceof Group))
++ item = applyAttributes(item, node);
++ if (item && data)
++ item._data = JSON.parse(data);
++ if (clearDefs)
++ definitions = {};
++ return item;
++ }
++
++ Item.inject({
++ importSVG: function(node) {
++ return this.addChild(importSVG(node, true));
++ }
++ });
++
++ Project.inject({
++ importSVG: function(node) {
++ this.activate();
++ return importSVG(node, true);
++ }
++ });
+ };
+
+-var PaperScript = this.PaperScript = new function() {
+-var parse_js=new function(){function W(a,b,c){var d=[];for(var e=0;e0,g=j(function(){return h(a[0]?k(["case",z(a[0])+":"]):"default:")},.5)+(f?e+j(function(){return u(a[1]).join(e)}):"");!c&&f&&d0?h(a):a}).join(e)},block:w,"var":function(a){return"var "+l(W(a,x))+";"},"const":function(a){return"const "+l(W(a,x))+";"},"try":function(a,b,c){var d=["try",w(a)];b&&d.push("catch","("+b[0]+")",w(b[1])),c&&d.push("finally",w(c));return k(d)},"throw":function(a){return k(["throw",z(a)])+";"},"new":function(a,b){b=b.length>0?"("+l(W(b,z))+")":"";return k(["new",m(a,"seq","binary","conditional","assign",function(a){var b=N(),c={};try{b.with_walkers({call:function(){throw c},"function":function(){return this}},function(){b.walk(a)})}catch(d){if(d===c)return!0;throw d}})+b])},"switch":function(a,b){return k(["switch","("+z(a)+")",v(b)])},"break":function(a){var b="break";a!=null&&(b+=" "+g(a));return b+";"},"continue":function(a){var b="continue";a!=null&&(b+=" "+g(a));return b+";"},conditional:function(a,b,c){return k([m(a,"assign","seq","conditional"),"?",m(b,"seq"),":",m(c,"seq")])},assign:function(a,b,c){a&&a!==!0?a+="=":a="=";return k([z(b),a,m(c,"seq")])},dot:function(a){var b=z(a),c=1;a[0]=="num"?/\./.test(a[1])||(b+="."):o(a)&&(b="("+b+")");while(cB[b[1]])d="("+d+")";if(L(c[0],["assign","conditional","seq"])||c[0]=="binary"&&B[a]>=B[c[1]]&&(c[1]!=a||!L(a,["&&","||","*"])))e="("+e+")";return k([d,a,e])},"unary-prefix":function(a,b){var c=z(b);b[0]=="num"||b[0]=="unary-prefix"&&!M(i,a+b[1])||!o(b)||(c="("+c+")");return a+(p(a.charAt(0))?" ":"")+c},"unary-postfix":function(a,b){var c=z(b);b[0]=="num"||b[0]=="unary-postfix"&&!M(i,a+b[1])||!o(b)||(c="("+c+")");return c+a},sub:function(a,b){var c=z(a);o(a)&&(c="("+c+")");return c+"["+z(b)+"]"},object:function(a){return a.length==0?"{}":"{"+e+j(function(){return W(a,function(a){if(a.length==3)return h(t(a[0],a[1][2],a[1][3],a[2]));var d=a[0],e=z(a[1]);b.quote_keys?d=Q(d):(typeof d=="number"||!c&&+d+""==d)&&parseFloat(d)>=0?d=q(+d):V(d)||(d=Q(d));return h(k(c&&b.space_colon?[d,":",e]:[d+":",e]))}).join(","+e)})+e+h("}")},regexp:function(a,b){return"/"+a+"/"+b},array:function(a){return a.length==0?"[]":k(["[",l(W(a,function(a){return!c&&a[0]=="atom"&&a[1]=="undefined"?"":m(a,"seq")})),"]"])},stat:function(a){return z(a).replace(/;*\s*$/,";")},seq:function(){return l(W(J(arguments),z))},label:function(a,b){return k([g(a),":",z(b)])},"with":function(a,b){return k(["with","("+z(a)+")",z(b)])},atom:function(a){return g(a)}},y=[];return z(a)}function Q(a){var b=0,c=0;a=a.replace(/[\\\b\f\n\r\t\x22\x27]/g,function(a){switch(a){case"\\":return"\\\\";case"\b":return"\\b";case"\f":return"\\f";case"\n":return"\\n";case"\r":return"\\r";case"\t":return"\\t";case'"':++b;return'"';case"'":++c;return"'"}return a});return b>c?"'"+a.replace(/\x27/g,"\\'")+"'":'"'+a.replace(/\x22/g,'\\"')+'"'}function O(a){return!a||a[0]=="block"&&(!a[1]||a[1].length==0)}function N(){function g(a,b){var c={},e;for(e in a)M(a,e)&&(c[e]=d[e],d[e]=a[e]);var f=b();for(e in c)M(c,e)&&(c[e]?d[e]=c[e]:delete d[e]);return f}function f(a){if(a==null)return null;try{e.push(a);var b=a[0],f=d[b];if(f){var g=f.apply(a,a.slice(1));if(g!=null)return g}f=c[b];return f.apply(a,a.slice(1))}finally{e.pop()}}function b(a){var b=[this[0]];a!=null&&b.push(W(a,f));return b}function a(a){return[this[0],W(a,function(a){var b=[a[0]];a.length>1&&(b[1]=f(a[1]));return b})]}var c={string:function(a){return[this[0],a]},num:function(a){return[this[0],a]},name:function(a){return[this[0],a]},toplevel:function(a){return[this[0],W(a,f)]},block:b,splice:b,"var":a,"const":a,"try":function(a,b,c){return[this[0],W(a,f),b!=null?[b[0],W(b[1],f)]:null,c!=null?W(c,f):null]},"throw":function(a){return[this[0],f(a)]},"new":function(a,b){return[this[0],f(a),W(b,f)]},"switch":function(a,b){return[this[0],f(a),W(b,function(a){return[a[0]?f(a[0]):null,W(a[1],f)]})]},"break":function(a){return[this[0],a]},"continue":function(a){return[this[0],a]},conditional:function(a,b,c){return[this[0],f(a),f(b),f(c)]},assign:function(a,b,c){return[this[0],a,f(b),f(c)]},dot:function(a){return[this[0],f(a)].concat(J(arguments,1))},call:function(a,b){return[this[0],f(a),W(b,f)]},"function":function(a,b,c){return[this[0],a,b.slice(),W(c,f)]},defun:function(a,b,c){return[this[0],a,b.slice(),W(c,f)]},"if":function(a,b,c){return[this[0],f(a),f(b),f(c)]},"for":function(a,b,c,d){return[this[0],f(a),f(b),f(c),f(d)]},"for-in":function(a,b,c,d){return[this[0],f(a),f(b),f(c),f(d)]},"while":function(a,b){return[this[0],f(a),f(b)]},"do":function(a,b){return[this[0],f(a),f(b)]},"return":function(a){return[this[0],f(a)]},binary:function(a,b,c){return[this[0],a,f(b),f(c)]},"unary-prefix":function(a,b){return[this[0],a,f(b)]},"unary-postfix":function(a,b){return[this[0],a,f(b)]},sub:function(a,b){return[this[0],f(a),f(b)]},object:function(a){return[this[0],W(a,function(a){return a.length==2?[a[0],f(a[1])]:[a[0],f(a[1]),a[2]]})]},regexp:function(a,b){return[this[0],a,b]},array:function(a){return[this[0],W(a,f)]},stat:function(a){return[this[0],f(a)]},seq:function(){return[this[0]].concat(W(J(arguments),f))},label:function(a,b){return[this[0],a,f(b)]},"with":function(a,b){return[this[0],f(a),f(b)]},atom:function(a){return[this[0],a]}},d={},e=[];return{walk:f,with_walkers:g,parent:function(){return e[e.length-2]},stack:function(){return e}}}function M(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function L(a,b){for(var c=b.length;--c>=0;)if(b[c]===a)return!0;return!1}function K(a){return a.split("")}function J(a,b){return Array.prototype.slice.call(a,b||0)}function I(a){var b={};for(var c=0;c0;++b)arguments[b]();return a}function G(a){var b=J(arguments,1);return function(){return a.apply(this,b.concat(J(arguments)))}}function F(a,b,c){function bk(a){try{++d.in_loop;return a()}finally{--d.in_loop}}function bi(a){var b=bg(a),c=d.token.value;if(e("operator")&&M(A,c)){if(bh(b)){g();return p("assign",A[c],b,bi(a))}i("Invalid assignment")}return b}function bh(a){if(!b)return!0;switch(a[0]){case"dot":case"sub":case"new":case"call":return!0;case"name":return a[1]!="this"}}function bg(a){var b=bf(a);if(e("operator","?")){g();var c=bj(!1);m(":");return p("conditional",b,c,bj(!1,a))}return b}function bf(a){return be(Y(!0),0,a)}function be(a,b,c){var f=e("operator")?d.token.value:null;f&&f=="in"&&c&&(f=null);var h=f!=null?B[f]:null;if(h!=null&&h>b){g();var i=be(Y(!0),h,c);return be(p("binary",f,a,i),b,c)}return a}function bd(a,b,c){(b=="++"||b=="--")&&!bh(c)&&i("Invalid use of "+b+" operator");return p(a,b,c)}function bc(a,b){if(e("punc",".")){g();return bc(p("dot",a,bb()),b)}if(e("punc","[")){g();return bc(p("sub",a,H(bj,G(m,"]"))),b)}if(b&&e("punc","(")){g();return bc(p("call",a,Z(")")),!0)}return b&&e("operator")&&M(z,d.token.value)?H(G(bd,"unary-postfix",d.token.value,a),g):a}function bb(){switch(d.token.type){case"name":case"operator":case"keyword":case"atom":return H(d.token.value,g);default:k()}}function ba(){switch(d.token.type){case"num":case"string":return H(d.token.value,g)}return bb()}function _(){var a=!0,c=[];while(!e("punc","}")){a?a=!1:m(",");if(!b&&e("punc","}"))break;var f=d.token.type,h=ba();f!="name"||h!="get"&&h!="set"||!!e("punc",":")?(m(":"),c.push([h,bj(!1)])):c.push([bb(),P(!1),h])}g();return p("object",c)}function $(){return p("array",Z("]",!b,!0))}function Z(a,b,c){var d=!0,f=[];while(!e("punc",a)){d?d=!1:m(",");if(b&&e("punc",a))break;e("punc",",")&&c?f.push(["atom","undefined"]):f.push(bj(!1))}g();return f}function X(){var a=Y(!1),b;e("punc","(")?(g(),b=Z(")")):b=[];return bc(p("new",a,b),!0)}function W(){return p("const",U())}function V(a){return p("var",U(a))}function U(a){var b=[];for(;;){e("name")||k();var c=d.token.value;g(),e("operator","=")?(g(),b.push([c,bj(!1,a)])):b.push([c]);if(!e("punc",","))break;g()}return b}function T(){var a=R(),b,c;if(e("keyword","catch")){g(),m("("),e("name")||i("Name expected");var f=d.token.value;g(),m(")"),b=[f,R()]}e("keyword","finally")&&(g(),c=R()),!b&&!c&&i("Missing catch/finally blocks");return p("try",a,b,c)}function R(){m("{");var a=[];while(!e("punc","}"))e("eof")&&k(),a.push(t());g();return a}function Q(){var a=q(),b=t(),c;e("keyword","else")&&(g(),c=t());return p("if",a,b,c)}function O(a){var b=a[0]=="var"?p("name",a[1][0]):a;g();var c=bj();m(")");return p("for-in",a,b,c,bk(t))}function N(a){m(";");var b=e("punc",";")?null:bj();m(";");var c=e("punc",")")?null:bj();m(")");return p("for",a,b,c,bk(t))}function K(){m("(");var a=null;if(!e("punc",";")){a=e("keyword","var")?(g(),V(!0)):bj(!0,!0);if(e("operator","in"))return O(a)}return N(a)}function I(a){var b;n()||(b=e("name")?d.token.value:null),b!=null?(g(),L(b,d.labels)||i("Label "+b+" without matching loop or statement")):d.in_loop==0&&i(a+" not inside a loop or switch"),o();return p(a,b)}function F(){return p("stat",H(bj,o))}function w(a){d.labels.push(a);var c=d.token,e=t();b&&!M(C,e[0])&&k(c),d.labels.pop();return p("label",a,e)}function s(a){return c?function(){var b=d.token,c=a.apply(this,arguments);c[0]=r(c[0],b,h());return c}:a}function r(a,b,c){return a instanceof E?a:new E(a,b,c)}function q(){m("(");var a=bj();m(")");return a}function p(){return J(arguments)}function o(){e("punc",";")?g():n()||k()}function n(){return!b&&(d.token.nlb||e("eof")||e("punc","}"))}function m(a){return l("punc",a)}function l(a,b){if(e(a,b))return g();j(d.token,"Unexpected token "+d.token.type+", expected "+a)}function k(a){a==null&&(a=d.token),j(a,"Unexpected token: "+a.type+" ("+a.value+")")}function j(a,b){i(b,a.line,a.col)}function i(a,b,c,e){var f=d.input.context();u(a,b!=null?b:f.tokline,c!=null?c:f.tokcol,e!=null?e:f.tokpos)}function h(){return d.prev}function g(){d.prev=d.token,d.peeked?(d.token=d.peeked,d.peeked=null):d.token=d.input();return d.token}function f(){return d.peeked||(d.peeked=d.input())}function e(a,b){return v(d.token,a,b)}var d={input:typeof a=="string"?x(a,!0):a,token:null,prev:null,peeked:null,in_function:0,in_loop:0,labels:[]};d.token=g();var t=s(function(){e("operator","/")&&(d.peeked=null,d.token=d.input(!0));switch(d.token.type){case"num":case"string":case"regexp":case"operator":case"atom":return F();case"name":return v(f(),"punc",":")?w(H(d.token.value,g,g)):F();case"punc":switch(d.token.value){case"{":return p("block",R());case"[":case"(":return F();case";":g();return p("block");default:k()};case"keyword":switch(H(d.token.value,g)){case"break":return I("break");case"continue":return I("continue");case"debugger":o();return p("debugger");case"do":return function(a){l("keyword","while");return p("do",H(q,o),a)}(bk(t));case"for":return K();case"function":return P(!0);case"if":return Q();case"return":d.in_function==0&&i("'return' outside of function");return p("return",e("punc",";")?(g(),null):n()?null:H(bj,o));case"switch":return p("switch",q(),S());case"throw":return p("throw",H(bj,o));case"try":return T();case"var":return H(V,o);case"const":return H(W,o);case"while":return p("while",q(),bk(t));case"with":return p("with",q(),t());default:k()}}}),P=s(function(a){var b=e("name")?H(d.token.value,g):null;a&&!b&&k(),m("(");return p(a?"defun":"function",b,function(a,b){while(!e("punc",")"))a?a=!1:m(","),e("name")||k(),b.push(d.token.value),g();g();return b}(!0,[]),function(){++d.in_function;var a=d.in_loop;d.in_loop=0;var b=R();--d.in_function,d.in_loop=a;return b}())}),S=G(bk,function(){m("{");var a=[],b=null;while(!e("punc","}"))e("eof")&&k(),e("keyword","case")?(g(),b=[],a.push([bj(),b]),m(":")):e("keyword","default")?(g(),m(":"),b=[],a.push([null,b])):(b||k(),b.push(t()));g();return a}),Y=s(function(a){if(e("operator","new")){g();return X()}if(e("operator")&&M(y,d.token.value))return bd("unary-prefix",H(d.token.value,g),Y(a));if(e("punc")){switch(d.token.value){case"(":g();return bc(H(bj,G(m,")")),a);case"[":g();return bc($(),a);case"{":g();return bc(_(),a)}k()}if(e("keyword","function")){g();return bc(P(!1),a)}if(M(D,d.token.type)){var b=d.token.type=="regexp"?p("regexp",d.token.value[0],d.token.value[1]):p(d.token.type,d.token.value);return bc(H(b,g),a)}k()}),bj=s(function(a,b){arguments.length==0&&(a=!0);var c=bi(b);if(a&&e("punc",",")){g();return p("seq",c,bj(!0,b))}return c});return p("toplevel",function(a){while(!e("eof"))a.push(t());return a}([]))}function E(a,b,c){this.name=a,this.start=b,this.end=c}function x(b){function P(a){if(a)return I();y(),v();var b=g();if(!b)return x("eof");if(o(b))return C();if(b=='"'||b=="'")return F();if(M(l,b))return x("punc",h());if(b==".")return L();if(b=="/")return K();if(M(e,b))return J();if(b=="\\"||q(b))return N();B("Unexpected character '"+b+"'")}function O(a,b){try{return b()}catch(c){if(c===w)B(a);else throw c}}function N(){var b=A(r);return M(a,b)?M(i,b)?x("operator",b):M(d,b)?x("atom",b):x("keyword",b):x("name",b)}function L(){h();return o(g())?C("."):x("punc",".")}function K(){h();var a=f.regex_allowed;switch(g()){case"/":f.comments_before.push(G()),f.regex_allowed=a;return P();case"*":f.comments_before.push(H()),f.regex_allowed=a;return P()}return f.regex_allowed?I():J("/")}function J(a){function b(a){if(!g())return a;var c=a+g();if(M(i,c)){h();return b(c)}return a}return x("operator",b(a||h()))}function I(){return O("Unterminated regular expression",function(){var a=!1,b="",c,d=!1;while(c=h(!0))if(a)b+="\\"+c,a=!1;else if(c=="[")d=!0,b+=c;else if(c=="]"&&d)d=!1,b+=c;else{if(c=="/"&&!d)break;c=="\\"?a=!0:b+=c}var e=A(function(a){return M(m,a)});return x("regexp",[b,e])})}function H(){h();return O("Unterminated multiline comment",function(){var a=t("*/",!0),b=f.text.substring(f.pos,a),c=x("comment2",b,!0);f.pos=a+2,f.line+=b.split("\n").length-1,f.newline_before=b.indexOf("\n")>=0;return c})}function G(){h();var a=t("\n"),b;a==-1?(b=f.text.substr(f.pos),f.pos=f.text.length):(b=f.text.substring(f.pos,a),f.pos=a);return x("comment1",b,!0)}function F(){return O("Unterminated string constant",function(){var a=h(),b="";for(;;){var c=h(!0);if(c=="\\")c=D();else if(c==a)break;b+=c}return x("string",b)})}function E(a){var b=0;for(;a>0;--a){var c=parseInt(h(!0),16);isNaN(c)&&B("Invalid hex-character pattern in string"),b=b<<4|c}return b}function D(){var a=h(!0);switch(a){case"n":return"\n";case"r":return"\r";case"t":return"\t";case"b":return"\b";case"v":return"";case"f":return"\f";case"0":return" ";case"x":return String.fromCharCode(E(2));case"u":return String.fromCharCode(E(4));case"\n":return"";default:return a}}function C(a){var b=!1,c=!1,d=!1,e=a==".",f=A(function(f,g){if(f=="x"||f=="X")return d?!1:d=!0;if(!d&&(f=="E"||f=="e"))return b?!1:b=c=!0;if(f=="-")return c||g==0&&!a?!0:!1;if(f=="+")return c;c=!1;if(f==".")return!e&&!d?e=!0:!1;return p(f)});a&&(f=a+f);var g=s(f);if(!isNaN(g))return x("num",g);B("Invalid syntax: "+f)}function B(a){u(a,f.tokline,f.tokcol,f.tokpos)}function A(a){var b="",c=g(),d=0;while(c&&a(c,d++))b+=h(),c=g();return b}function y(){while(M(j,g()))h()}function x(a,b,d){f.regex_allowed=a=="operator"&&!M(z,b)||a=="keyword"&&M(c,b)||a=="punc"&&M(k,b);var e={type:a,value:b,line:f.tokline,col:f.tokcol,pos:f.tokpos,nlb:f.newline_before};d||(e.comments_before=f.comments_before,f.comments_before=[]),f.newline_before=!1;return e}function v(){f.tokline=f.line,f.tokcol=f.col,f.tokpos=f.pos}function t(a,b){var c=f.text.indexOf(a,f.pos);if(b&&c==-1)throw w;return c}function n(){return!f.peek()}function h(a){var b=f.text.charAt(f.pos++);if(a&&!b)throw w;b=="\n"?(f.newline_before=!0,++f.line,f.col=0):++f.col;return b}function g(){return f.text.charAt(f.pos)}var f={text:b.replace(/\r\n?|[\n\u2028\u2029]/g,"\n").replace(/^\uFEFF/,""),pos:0,tokpos:0,line:0,tokline:0,col:0,tokcol:0,newline_before:!1,regex_allowed:!1,comments_before:[]};P.context=function(a){a&&(f=a);return f};return P}function v(a,b,c){return a.type==b&&(c==null||a.value==c)}function u(a,b,c,d){throw new t(a,b,c,d)}function t(a,b,c,d){this.message=a,this.line=b,this.col=c,this.pos=d}function s(a){if(f.test(a))return parseInt(a.substr(2),16);if(g.test(a))return parseInt(a.substr(1),8);if(h.test(a))return parseFloat(a)}function r(a){return q(a)||o(a)}function q(a){return a=="$"||a=="_"||n(a)}function p(a){return o(a)||n(a)}function o(a){a=a.charCodeAt(0);return a>=48&&a<=57}function n(a){a=a.charCodeAt(0);return a>=65&&a<=90||a>=97&&a<=122}var a=I(["break","case","catch","const","continue","default","delete","do","else","finally","for","function","if","in","instanceof","new","return","switch","throw","try","typeof","var","void","while","with"]),b=I(["abstract","boolean","byte","char","class","debugger","double","enum","export","extends","final","float","goto","implements","import","int","interface","long","native","package","private","protected","public","short","static","super","synchronized","throws","transient","volatile"]),c=I(["return","new","delete","throw","else","case"]),d=I(["false","null","true","undefined"]),e=I(K("+-*&%=<>!?|~^")),f=/^0x[0-9a-f]+$/i,g=/^0[0-7]+$/,h=/^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i,i=I(["in","instanceof","typeof","new","void","delete","++","--","+","-","!","~","&","|","^","*","/","%",">>","<<",">>>","<",">","<=",">=","==","===","!=","!==","?","=","+=","-=","/=","*=","%=",">>=","<<=",">>>=","|=","^=","&=","&&","||"]),j=I(K(" \n\r\t")),k=I(K("[{}(,.;:")),l=I(K("[]{}(),;:")),m=I(K("gmsiy"));t.prototype.toString=function(){return this.message+" (line: "+this.line+", col: "+this.col+", pos: "+this.pos+")"};var w={},y=I(["typeof","void","delete","--","++","!","~","-","+"]),z=I(["--","++"]),A=function(a,b,c){while(c>=","<<=",">>>=","|=","^=","&="],{"=":!0},0),B=function(a,b){for(var c=0,d=1;c","<=",">=","in","instanceof"],[">>","<<",">>>"],["+","-"],["*","/","%"]],{}),C=I(["for","do","while","switch"]),D=I(["atom","num","string","regexp","name"]);E.prototype.toString=function(){return this.name};var P=I(["name","array","object","string","dot","sub","call","regexp"]),R=I(["if","while","do","for","for-in","with"]);return{parse:F,gen_code:S,tokenizer:x,ast_walker:N}}
++paper = new (PaperScope.inject(Base.merge(Base.exports, {
++ enumerable: true,
++ Base: Base,
++ Numerical: Numerical,
++ DomElement: DomElement,
++ DomEvent: DomEvent,
++ Key: Key
++})))();
+
+- // Math Operators
++if (typeof define === 'function' && define.amd)
++ define(paper);
+
+- var operators = {
+- '+': 'add',
+- '-': 'subtract',
+- '*': 'multiply',
+- '/': 'divide',
+- '%': 'modulo',
++return paper;
++};
++
++paper.PaperScope.prototype.PaperScript = (function(root) {
++ var Base = paper.Base,
++ PaperScope = paper.PaperScope,
++ exports, define,
++ scope = this;
++!function(e,r){return"object"==typeof exports&&"object"==typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):(r(e.acorn||(e.acorn={})),void 0)}(this,function(e){"use strict";function r(e){fr=e||{};for(var r in hr)Object.prototype.hasOwnProperty.call(fr,r)||(fr[r]=hr[r]);mr=fr.sourceFile||null}function t(e,r){var t=vr(pr,e);r+=" ("+t.line+":"+t.column+")";var n=new SyntaxError(r);throw n.pos=e,n.loc=t,n.raisedAt=br,n}function n(e){function r(e){if(1==e.length)return t+="return str === "+JSON.stringify(e[0])+";";t+="switch(str){";for(var r=0;r3){n.sort(function(e,r){return r.length-e.length}),t+="switch(str.length){";for(var a=0;abr&&10!==t&&13!==t&&8232!==t&&8329!==t;)++br,t=pr.charCodeAt(br);fr.onComment&&fr.onComment(!1,pr.slice(e+2,br),e,br,r,fr.locations&&new a)}function u(){for(;dr>br;){var e=pr.charCodeAt(br);if(32===e)++br;else if(13===e){++br;var r=pr.charCodeAt(br);10===r&&++br,fr.locations&&(++Ar,Sr=br)}else if(10===e)++br,++Ar,Sr=br;else if(14>e&&e>8)++br;else if(47===e){var r=pr.charCodeAt(br+1);if(42===r)s();else{if(47!==r)break;c()}}else if(160===e)++br;else{if(!(e>=5760&&Jt.test(String.fromCharCode(e))))break;++br}}}function l(){var e=pr.charCodeAt(br+1);return e>=48&&57>=e?E(!0):(++br,i(xt))}function f(){var e=pr.charCodeAt(br+1);return Er?(++br,k()):61===e?x(Et,2):x(wt,1)}function p(){var e=pr.charCodeAt(br+1);return 61===e?x(Et,2):x(Ft,1)}function d(e){var r=pr.charCodeAt(br+1);return r===e?x(124===e?Lt:Ut,2):61===r?x(Et,2):x(124===e?Rt:Vt,1)}function m(){var e=pr.charCodeAt(br+1);return 61===e?x(Et,2):x(Tt,1)}function h(e){var r=pr.charCodeAt(br+1);return r===e?x(St,2):61===r?x(Et,2):x(At,1)}function v(e){var r=pr.charCodeAt(br+1),t=1;return r===e?(t=62===e&&62===pr.charCodeAt(br+2)?3:2,61===pr.charCodeAt(br+t)?x(Et,t+1):x(jt,t)):(61===r&&(t=61===pr.charCodeAt(br+2)?3:2),x(Ot,t))}function b(e){var r=pr.charCodeAt(br+1);return 61===r?x(qt,61===pr.charCodeAt(br+2)?3:2):x(61===e?Ct:It,1)}function y(e){switch(e){case 46:return l();case 40:return++br,i(ht);case 41:return++br,i(vt);case 59:return++br,i(yt);case 44:return++br,i(bt);case 91:return++br,i(ft);case 93:return++br,i(pt);case 123:return++br,i(dt);case 125:return++br,i(mt);case 58:return++br,i(gt);case 63:return++br,i(kt);case 48:var r=pr.charCodeAt(br+1);if(120===r||88===r)return C();case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:return E(!1);case 34:case 39:return A(e);case 47:return f(e);case 37:case 42:return p();case 124:case 38:return d(e);case 94:return m();case 43:case 45:return h(e);case 60:case 62:return v(e);case 61:case 33:return b(e);case 126:return x(It,1)}return!1}function g(e){if(e?br=yr+1:yr=br,fr.locations&&(xr=new a),e)return k();if(br>=dr)return i(Br);var r=pr.charCodeAt(br);if(Qt(r)||92===r)return L();var n=y(r);if(n===!1){var o=String.fromCharCode(r);if("\\"===o||$t.test(o))return L();t(br,"Unexpected character '"+o+"'")}return n}function x(e,r){var t=pr.slice(br,br+r);br+=r,i(e,t)}function k(){for(var e,r,n="",a=br;;){br>=dr&&t(a,"Unterminated regular expression");var o=pr.charAt(br);if(Gt.test(o)&&t(a,"Unterminated regular expression"),e)e=!1;else{if("["===o)r=!0;else if("]"===o&&r)r=!1;else if("/"===o&&!r)break;e="\\"===o}++br}var n=pr.slice(a,br);++br;var s=I();return s&&!/^[gmsiy]*$/.test(s)&&t(a,"Invalid regexp flag"),i(jr,new RegExp(n,s))}function w(e,r){for(var t=br,n=0,a=0,o=null==r?1/0:r;o>a;++a){var i,s=pr.charCodeAt(br);if(i=s>=97?s-97+10:s>=65?s-65+10:s>=48&&57>=s?s-48:1/0,i>=e)break;++br,n=n*e+i}return br===t||null!=r&&br-t!==r?null:n}function C(){br+=2;var e=w(16);return null==e&&t(yr+2,"Expected hexadecimal number"),Qt(pr.charCodeAt(br))&&t(br,"Identifier directly after number"),i(Or,e)}function E(e){var r=br,n=!1,a=48===pr.charCodeAt(br);e||null!==w(10)||t(r,"Invalid number"),46===pr.charCodeAt(br)&&(++br,w(10),n=!0);var o=pr.charCodeAt(br);(69===o||101===o)&&(o=pr.charCodeAt(++br),(43===o||45===o)&&++br,null===w(10)&&t(r,"Invalid number"),n=!0),Qt(pr.charCodeAt(br))&&t(br,"Identifier directly after number");var s,c=pr.slice(r,br);return n?s=parseFloat(c):a&&1!==c.length?/[89]/.test(c)||Vr?t(r,"Invalid number"):s=parseInt(c,8):s=parseInt(c,10),i(Or,s)}function A(e){br++;for(var r="";;){br>=dr&&t(yr,"Unterminated string constant");var n=pr.charCodeAt(br);if(n===e)return++br,i(Fr,r);if(92===n){n=pr.charCodeAt(++br);var a=/^[0-7]+/.exec(pr.slice(br,br+3));for(a&&(a=a[0]);a&&parseInt(a,8)>255;)a=a.slice(0,a.length-1);if("0"===a&&(a=null),++br,a)Vr&&t(br-2,"Octal literal in strict mode"),r+=String.fromCharCode(parseInt(a,8)),br+=a.length-1;else switch(n){case 110:r+="\n";break;case 114:r+="\r";break;case 120:r+=String.fromCharCode(S(2));break;case 117:r+=String.fromCharCode(S(4));break;case 85:r+=String.fromCharCode(S(8));break;case 116:r+=" ";break;case 98:r+="\b";break;case 118:r+="";break;case 102:r+="\f";break;case 48:r+="\0";break;case 13:10===pr.charCodeAt(br)&&++br;case 10:fr.locations&&(Sr=br,++Ar);break;default:r+=String.fromCharCode(n)}}else(13===n||10===n||8232===n||8329===n)&&t(yr,"Unterminated string constant"),r+=String.fromCharCode(n),++br}}function S(e){var r=w(16,e);return null===r&&t(yr,"Bad character escape sequence"),r}function I(){Bt=!1;for(var e,r=!0,n=br;;){var a=pr.charCodeAt(br);if(Yt(a))Bt&&(e+=pr.charAt(br)),++br;else{if(92!==a)break;Bt||(e=pr.slice(n,br)),Bt=!0,117!=pr.charCodeAt(++br)&&t(br,"Expecting Unicode escape sequence \\uXXXX"),++br;var o=S(4),i=String.fromCharCode(o);i||t(br-1,"Invalid Unicode escape"),(r?Qt(o):Yt(o))||t(br-4,"Invalid Unicode escape"),e+=i}r=!1}return Bt?e:pr.slice(n,br)}function L(){var e=I(),r=Dr;return Bt||(Wt(e)?r=lt[e]:(fr.forbidReserved&&(3===fr.ecmaVersion?Mt:zt)(e)||Vr&&Xt(e))&&t(yr,"The keyword '"+e+"' is reserved")),i(r,e)}function U(){Ir=yr,Lr=gr,Ur=kr,g()}function R(e){for(Vr=e,br=Lr;Sr>br;)Sr=pr.lastIndexOf("\n",Sr-2)+1,--Ar;u(),g()}function T(){this.type=null,this.start=yr,this.end=null}function V(){this.start=xr,this.end=null,null!==mr&&(this.source=mr)}function q(){var e=new T;return fr.locations&&(e.loc=new V),fr.ranges&&(e.range=[yr,0]),e}function O(e){var r=new T;return r.start=e.start,fr.locations&&(r.loc=new V,r.loc.start=e.loc.start),fr.ranges&&(r.range=[e.range[0],0]),r}function j(e,r){return e.type=r,e.end=Lr,fr.locations&&(e.loc.end=Ur),fr.ranges&&(e.range[1]=Lr),e}function F(e){return fr.ecmaVersion>=5&&"ExpressionStatement"===e.type&&"Literal"===e.expression.type&&"use strict"===e.expression.value}function D(e){return wr===e?(U(),!0):void 0}function B(){return!fr.strictSemicolons&&(wr===Br||wr===mt||Gt.test(pr.slice(Lr,yr)))}function M(){D(yt)||B()||X()}function z(e){wr===e?U():X()}function X(){t(yr,"Unexpected token")}function N(e){"Identifier"!==e.type&&"MemberExpression"!==e.type&&t(e.start,"Assigning to rvalue"),Vr&&"Identifier"===e.type&&Nt(e.name)&&t(e.start,"Assigning to "+e.name+" in strict mode")}function W(e){Ir=Lr=br,fr.locations&&(Ur=new a),Rr=Vr=null,Tr=[],g();var r=e||q(),t=!0;for(e||(r.body=[]);wr!==Br;){var n=J();r.body.push(n),t&&F(n)&&R(!0),t=!1}return j(r,"Program")}function J(){wr===wt&&g(!0);var e=wr,r=q();switch(e){case Mr:case Nr:U();var n=e===Mr;D(yt)||B()?r.label=null:wr!==Dr?X():(r.label=lr(),M());for(var a=0;ar){var a=O(e);a.left=e,a.operator=Cr,U(),a.right=er(rr(),n,t);var a=j(a,/&&|\|\|/.test(a.operator)?"LogicalExpression":"BinaryExpression");return er(a,r,t)}return e}function rr(){if(wr.prefix){var e=q(),r=wr.isUpdate;return e.operator=Cr,e.prefix=!0,U(),e.argument=rr(),r?N(e.argument):Vr&&"delete"===e.operator&&"Identifier"===e.argument.type&&t(e.start,"Deleting local variable in strict mode"),j(e,r?"UpdateExpression":"UnaryExpression")}for(var n=tr();wr.postfix&&!B();){var e=O(n);e.operator=Cr,e.prefix=!1,e.argument=n,N(n),U(),n=j(e,"UpdateExpression")}return n}function tr(){return nr(ar())}function nr(e,r){if(D(xt)){var t=O(e);return t.object=e,t.property=lr(!0),t.computed=!1,nr(j(t,"MemberExpression"),r)}if(D(ft)){var t=O(e);return t.object=e,t.property=K(),t.computed=!0,z(pt),nr(j(t,"MemberExpression"),r)}if(!r&&D(ht)){var t=O(e);return t.callee=e,t.arguments=ur(vt,!1),nr(j(t,"CallExpression"),r)}return e}function ar(){switch(wr){case ot:var e=q();return U(),j(e,"ThisExpression");case Dr:return lr();case Or:case Fr:case jr:var e=q();return e.value=Cr,e.raw=pr.slice(yr,gr),U(),j(e,"Literal");case it:case st:case ct:var e=q();return e.value=wr.atomValue,e.raw=wr.keyword,U(),j(e,"Literal");case ht:var r=xr,t=yr;U();var n=K();return n.start=t,n.end=gr,fr.locations&&(n.loc.start=r,n.loc.end=kr),fr.ranges&&(n.range=[t,gr]),z(vt),n;case ft:var e=q();return U(),e.elements=ur(pt,!0,!0),j(e,"ArrayExpression");case dt:return ir();case Gr:var e=q();return U(),cr(e,!1);case at:return or();default:X()}}function or(){var e=q();return U(),e.callee=nr(ar(),!0),e.arguments=D(ht)?ur(vt,!1):qr,j(e,"NewExpression")}function ir(){var e=q(),r=!0,n=!1;for(e.properties=[],U();!D(mt);){if(r)r=!1;else if(z(bt),fr.allowTrailingCommas&&D(mt))break;var a,o={key:sr()},i=!1;if(D(gt)?(o.value=K(!0),a=o.kind="init"):fr.ecmaVersion>=5&&"Identifier"===o.key.type&&("get"===o.key.name||"set"===o.key.name)?(i=n=!0,a=o.kind=o.key.name,o.key=sr(),wr!==ht&&X(),o.value=cr(q(),!1)):X(),"Identifier"===o.key.type&&(Vr||n))for(var s=0;si?e.id:e.params[i];if((Xt(s.name)||Nt(s.name))&&t(s.start,"Defining '"+s.name+"' in strict mode"),i>=0)for(var c=0;i>c;++c)s.name===e.params[c].name&&t(s.start,"Argument name clash in strict mode")}return j(e,r?"FunctionDeclaration":"FunctionExpression")}function ur(e,r,t){for(var n=[],a=!0;!D(e);){if(a)a=!1;else if(z(bt),r&&fr.allowTrailingCommas&&D(e))break;t&&wr===bt?n.push(null):n.push(K(!0))}return n}function lr(e){var r=q();return r.name=wr===Dr?Cr:e&&!fr.forbidReserved&&wr.keyword||X(),U(),j(r,"Identifier")}e.version="0.3.2";var fr,pr,dr,mr;e.parse=function(e,t){return pr=String(e),dr=pr.length,r(t),o(),W(fr.program)};var hr=e.defaultOptions={ecmaVersion:5,strictSemicolons:!1,allowTrailingCommas:!0,forbidReserved:!1,locations:!1,onComment:null,ranges:!1,program:null,sourceFile:null},vr=e.getLineInfo=function(e,r){for(var t=1,n=0;;){Kt.lastIndex=n;var a=Kt.exec(e);if(!(a&&a.indexe?36===e:91>e?!0:97>e?95===e:123>e?!0:e>=170&&$t.test(String.fromCharCode(e))},Yt=e.isIdentifierChar=function(e){return 48>e?36===e:58>e?!0:65>e?!1:91>e?!0:97>e?95===e:123>e?!0:e>=170&&_t.test(String.fromCharCode(e))},Zt={kind:"loop"},en={kind:"switch"}});
++
++ var binaryOperators = {
++ '+': '_add',
++ '-': '_subtract',
++ '*': '_multiply',
++ '/': '_divide',
++ '%': '_modulo',
+ '==': 'equals',
+ '!=': 'equals'
+ };
+
+- function $eval(left, operator, right) {
+- var handler = operators[operator];
++ var unaryOperators = {
++ '-': '_negate',
++ '+': null
++ };
++
++ var fields = Base.each(
++ 'add,subtract,multiply,divide,modulo,negate'.split(','),
++ function(name) {
++ this['_' + name] = '#' + name;
++ },
++ {}
++ );
++ paper.Point.inject(fields);
++ paper.Size.inject(fields);
++ paper.Color.inject(fields);
++
++ function _$_(left, operator, right) {
++ var handler = binaryOperators[operator];
+ if (left && left[handler]) {
+ var res = left[handler](right);
+- return operator == '!=' ? !res : res;
++ return operator === '!=' ? !res : res;
+ }
+ switch (operator) {
+ case '+': return left + right;
+@@ -7658,117 +11247,130 @@ var parse_js=new function(){function W(a,b,c){var d=[];for(var e=0;e= offset)
++ break;
++ offset += insertion[1];
++ }
++ return offset;
+ }
+- }
+-
+- /**
+- * Compiles PaperScript code into JavaScript code.
+- *
+- * @name PaperScript.compile
+- * @function
+- * @param {String} code The PaperScript code.
+- * @return {String} The compiled PaperScript as JavaScript code.
+- */
+- function compile(code) {
+- // Use parse-js to translate the code into a AST structure which is then
+- // walked and parsed for operators to overload. The resulting AST is
+- // translated back to code and evaluated.
+- var ast = parse_js.parse(code),
+- walker = parse_js.ast_walker(),
+- walk = walker.walk;
+-
+- ast = walker.with_walkers({
+- 'binary': function(operator, left, right) {
+- // Handle simple mathematical operators here:
+- return handleOperator(operator, left = walk(left),
+- right = walk(right))
+- // Always return something since we're walking left and
+- || [this[0], operator, left, right];
+- },
+
+- 'assign': function(operator, left, right) {
+- var res = handleOperator(operator, left = walk(left),
+- right = walk(right));
+- if (res)
+- return [this[0], true, left, res];
+- return [this[0], operator, left, right];
+- },
++ function getCode(node) {
++ return code.substring(getOffset(node.range[0]),
++ getOffset(node.range[1]));
++ }
+
+- 'unary-prefix': function(operator, exp) {
+- if (signOperators[operator] && isDynamic(exp)) {
+- return ['call', ['name', '$sign'],
+- [['string', operator], walk(exp)]];
++ function replaceCode(node, str) {
++ var start = getOffset(node.range[0]),
++ end = getOffset(node.range[1]);
++ var insert = 0;
++ for (var i = insertions.length - 1; i >= 0; i--) {
++ if (start > insertions[i][0]) {
++ insert = i + 1;
++ break;
+ }
+ }
+- }, function() {
+- return walk(ast);
+- });
++ insertions.splice(insert, 0, [start, str.length - end + start]);
++ code = code.substring(0, start) + str + code.substring(end);
++ }
+
+- return parse_js.gen_code(ast, {
+- beautify: true
+- });
++ function walkAst(node) {
++ if (!node || node.type === 'MemberExpression' && node.computed)
++ return;
++ for (var key in node) {
++ if (key === 'range')
++ continue;
++ var value = node[key];
++ if (Array.isArray(value)) {
++ for (var i = 0, l = value.length; i < l; i++)
++ walkAst(value[i]);
++ } else if (value && typeof value === 'object') {
++ walkAst(value);
++ }
++ }
++ switch (node && node.type) {
++ case 'BinaryExpression':
++ if (node.operator in binaryOperators
++ && node.left.type !== 'Literal') {
++ var left = getCode(node.left),
++ right = getCode(node.right);
++ replaceCode(node, '_$_(' + left + ', "' + node.operator
++ + '", ' + right + ')');
++ }
++ break;
++ case 'AssignmentExpression':
++ if (/^.=$/.test(node.operator)
++ && node.left.type !== 'Literal') {
++ var left = getCode(node.left),
++ right = getCode(node.right);
++ replaceCode(node, left + ' = _$_(' + left + ', "'
++ + node.operator[0] + '", ' + right + ')');
++ }
++ break;
++ case 'UpdateExpression':
++ if (!node.prefix) {
++ var arg = getCode(node.argument);
++ replaceCode(node, arg + ' = _$_(' + arg + ', "'
++ + node.operator[0] + '", 1)');
++ }
++ break;
++ case 'UnaryExpression':
++ if (node.operator in unaryOperators
++ && node.argument.type !== 'Literal') {
++ var arg = getCode(node.argument);
++ replaceCode(node, '$_("' + node.operator + '", '
++ + arg + ')');
++ }
++ break;
++ }
++ }
++ walkAst(scope.acorn.parse(code, { ranges: true }));
++ return code;
+ }
+
+ function evaluate(code, scope) {
+ paper = scope;
+- var view = scope.view,
+- tool = /on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)
+- && new Tool(),
++ var view = scope.project && scope.project.view,
+ res;
+ with (scope) {
+ (function() {
+- var onEditOptions, onSelect, onDeselect, onReselect, onMouseDown,
+- onMouseUp, onMouseDrag, onMouseMove, onKeyDown, onKeyUp,
+- onFrame, onResize,
+- handlers = [ 'onEditOptions', 'onSelect', 'onDeselect',
+- 'onReselect', 'onMouseDown', 'onMouseUp', 'onMouseDrag',
+- 'onMouseMove', 'onKeyDown', 'onKeyUp'];
++ var onActivate, onDeactivate, onEditOptions,
++ onMouseDown, onMouseUp, onMouseDrag, onMouseMove,
++ onKeyDown, onKeyUp, onFrame, onResize;
+ res = eval(compile(code));
+- if (tool) {
+- Base.each(handlers, function(key) {
+- tool[key] = eval(key);
++ if (/on(?:Key|Mouse)(?:Up|Down|Move|Drag)/.test(code)) {
++ Base.each(paper.Tool.prototype._events, function(key) {
++ var value = eval(key);
++ if (value) {
++ scope.getTool()[key] = value;
++ }
+ });
+ }
+ if (view) {
+- view.onResize = onResize;
++ view.setOnResize(onResize);
++ view.fire('resize', {
++ size: view.size,
++ delta: new Point()
++ });
+ view.setOnFrame(onFrame);
+ view.draw();
+ }
+@@ -7781,9 +11383,8 @@ var parse_js=new function(){function W(a,b,c){var d=[];for(var e=0;e
+ } else {
+ definition();
+ }
+})(function () {
+
+/**
+ * Brings an environment as close to ECMAScript 5 compliance
+ * as is possible with the facilities of erstwhile engines.
+ *
+ * Annotated ES5: http://es5.github.com/ (specific links below)
+ * ES5 Spec: http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
+ * Required reading: http://javascriptweblog.wordpress.com/2011/12/05/extending-javascript-natives/
+ */
+
+//
+// Function
+// ========
+//
+
+// ES-5 15.3.4.5
+// http://es5.github.com/#x15.3.4.5
+
+function Empty() {}
+
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function bind(that) { // .length is 1
+ // 1. Let Target be the this value.
+ var target = this;
+ // 2. If IsCallable(Target) is false, throw a TypeError exception.
+ if (typeof target != "function") {
+ throw new TypeError("Function.prototype.bind called on incompatible " + target);
+ }
+ // 3. Let A be a new (possibly empty) internal list of all of the
+ // argument values provided after thisArg (arg1, arg2 etc), in order.
+ // XXX slicedArgs will stand in for "A" if used
+ var args = slice.call(arguments, 1); // for normal call
+ // 4. Let F be a new native ECMAScript object.
+ // 11. Set the [[Prototype]] internal property of F to the standard
+ // built-in Function prototype object as specified in 15.3.3.1.
+ // 12. Set the [[Call]] internal property of F as described in
+ // 15.3.4.5.1.
+ // 13. Set the [[Construct]] internal property of F as described in
+ // 15.3.4.5.2.
+ // 14. Set the [[HasInstance]] internal property of F as described in
+ // 15.3.4.5.3.
+ var bound = function () {
+
+ if (this instanceof bound) {
+ // 15.3.4.5.2 [[Construct]]
+ // When the [[Construct]] internal method of a function object,
+ // F that was created using the bind function is called with a
+ // list of arguments ExtraArgs, the following steps are taken:
+ // 1. Let target be the value of F's [[TargetFunction]]
+ // internal property.
+ // 2. If target has no [[Construct]] internal method, a
+ // TypeError exception is thrown.
+ // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
+ // property.
+ // 4. Let args be a new list containing the same values as the
+ // list boundArgs in the same order followed by the same
+ // values as the list ExtraArgs in the same order.
+ // 5. Return the result of calling the [[Construct]] internal
+ // method of target providing args as the arguments.
+
+ var result = target.apply(
+ this,
+ args.concat(slice.call(arguments))
+ );
+ if (Object(result) === result) {
+ return result;
+ }
+ return this;
+
+ } else {
+ // 15.3.4.5.1 [[Call]]
+ // When the [[Call]] internal method of a function object, F,
+ // which was created using the bind function is called with a
+ // this value and a list of arguments ExtraArgs, the following
+ // steps are taken:
+ // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
+ // property.
+ // 2. Let boundThis be the value of F's [[BoundThis]] internal
+ // property.
+ // 3. Let target be the value of F's [[TargetFunction]] internal
+ // property.
+ // 4. Let args be a new list containing the same values as the
+ // list boundArgs in the same order followed by the same
+ // values as the list ExtraArgs in the same order.
+ // 5. Return the result of calling the [[Call]] internal method
+ // of target providing boundThis as the this value and
+ // providing args as the arguments.
+
+ // equiv: target.call(this, ...boundArgs, ...args)
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+ if(target.prototype) {
+ Empty.prototype = target.prototype;
+ bound.prototype = new Empty();
+ // Clean up dangling references.
+ Empty.prototype = null;
+ }
+ // XXX bound.length is never writable, so don't even try
+ //
+ // 15. If the [[Class]] internal property of Target is "Function", then
+ // a. Let L be the length property of Target minus the length of A.
+ // b. Set the length own property of F to either 0 or L, whichever is
+ // larger.
+ // 16. Else set the length own property of F to 0.
+ // 17. Set the attributes of the length own property of F to the values
+ // specified in 15.3.5.1.
+
+ // TODO
+ // 18. Set the [[Extensible]] internal property of F to true.
+
+ // TODO
+ // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
+ // 20. Call the [[DefineOwnProperty]] internal method of F with
+ // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
+ // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
+ // false.
+ // 21. Call the [[DefineOwnProperty]] internal method of F with
+ // arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
+ // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
+ // and false.
+
+ // TODO
+ // NOTE Function objects created using Function.prototype.bind do not
+ // have a prototype property or the [[Code]], [[FormalParameters]], and
+ // [[Scope]] internal properties.
+ // XXX can't delete prototype in pure-js.
+
+ // 22. Return F.
+ return bound;
+ };
+}
+
+// Shortcut to an often accessed properties, in order to avoid multiple
+// dereference that costs universally.
+// _Please note: Shortcuts are defined after `Function.prototype.bind` as we
+// us it in defining shortcuts.
+var call = Function.prototype.call;
+var prototypeOfArray = Array.prototype;
+var prototypeOfObject = Object.prototype;
+var slice = prototypeOfArray.slice;
+// Having a toString local variable name breaks in Opera so use _toString.
+var _toString = call.bind(prototypeOfObject.toString);
+var owns = call.bind(prototypeOfObject.hasOwnProperty);
+
+// If JS engine supports accessors creating shortcuts.
+var defineGetter;
+var defineSetter;
+var lookupGetter;
+var lookupSetter;
+var supportsAccessors;
+if ((supportsAccessors = owns(prototypeOfObject, "__defineGetter__"))) {
+ defineGetter = call.bind(prototypeOfObject.__defineGetter__);
+ defineSetter = call.bind(prototypeOfObject.__defineSetter__);
+ lookupGetter = call.bind(prototypeOfObject.__lookupGetter__);
+ lookupSetter = call.bind(prototypeOfObject.__lookupSetter__);
+}
+
+//
+// Array
+// =====
+//
+
+// ES5 15.4.4.12
+// http://es5.github.com/#x15.4.4.12
+// Default value for second param
+// [bugfix, ielt9, old browsers]
+// IE < 9 bug: [1,2].splice(0).join("") == "" but should be "12"
+if ([1,2].splice(0).length != 2) {
+ var array_splice = Array.prototype.splice;
+ Array.prototype.splice = function(start, deleteCount) {
+ if (!arguments.length) {
+ return [];
+ } else {
+ return array_splice.apply(this, [
+ start === void 0 ? 0 : start,
+ deleteCount === void 0 ? (this.length - start) : deleteCount
+ ].concat(slice.call(arguments, 2)))
+ }
+ };
+}
+
+// ES5 15.4.4.12
+// http://es5.github.com/#x15.4.4.13
+// Return len+argCount.
+// [bugfix, ielt8]
+// IE < 8 bug: [].unshift(0) == undefined but should be "1"
+if ([].unshift(0) != 1) {
+ var array_unshift = Array.prototype.unshift;
+ Array.prototype.unshift = function() {
+ array_unshift.apply(this, arguments);
+ return this.length;
+ };
+}
+
+// ES5 15.4.3.2
+// http://es5.github.com/#x15.4.3.2
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
+if (!Array.isArray) {
+ Array.isArray = function isArray(obj) {
+ return _toString(obj) == "[object Array]";
+ };
+}
+
+// The IsCallable() check in the Array functions
+// has been replaced with a strict check on the
+// internal class of the object to trap cases where
+// the provided function was actually a regular
+// expression literal, which in V8 and
+// JavaScriptCore is a typeof "function". Only in
+// V8 are regular expression literals permitted as
+// reduce parameters, so it is desirable in the
+// general case for the shim to match the more
+// strict and common behavior of rejecting regular
+// expressions.
+
+// ES5 15.4.4.18
+// http://es5.github.com/#x15.4.4.18
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/forEach
+
+// Check failure of by-index access of string characters (IE < 9)
+// and failure of `0 in boxedString` (Rhino)
+var boxedString = Object("a"),
+ splitString = boxedString[0] != "a" || !(0 in boxedString);
+
+if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function forEach(fun /*, thisp*/) {
+ var object = toObject(this),
+ self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ object,
+ thisp = arguments[1],
+ i = -1,
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(); // TODO message
+ }
+
+ while (++i < length) {
+ if (i in self) {
+ // Invoke the callback function with call, passing arguments:
+ // context, property value, property key, thisArg object
+ // context
+ fun.call(thisp, self[i], i, object);
+ }
+ }
+ };
+}
+
+// ES5 15.4.4.19
+// http://es5.github.com/#x15.4.4.19
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map
+if (!Array.prototype.map) {
+ Array.prototype.map = function map(fun /*, thisp*/) {
+ var object = toObject(this),
+ self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ object,
+ length = self.length >>> 0,
+ result = Array(length),
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self)
+ result[i] = fun.call(thisp, self[i], i, object);
+ }
+ return result;
+ };
+}
+
+// ES5 15.4.4.20
+// http://es5.github.com/#x15.4.4.20
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter
+if (!Array.prototype.filter) {
+ Array.prototype.filter = function filter(fun /*, thisp */) {
+ var object = toObject(this),
+ self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ object,
+ length = self.length >>> 0,
+ result = [],
+ value,
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self) {
+ value = self[i];
+ if (fun.call(thisp, value, i, object)) {
+ result.push(value);
+ }
+ }
+ }
+ return result;
+ };
+}
+
+// ES5 15.4.4.16
+// http://es5.github.com/#x15.4.4.16
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/every
+if (!Array.prototype.every) {
+ Array.prototype.every = function every(fun /*, thisp */) {
+ var object = toObject(this),
+ self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ object,
+ length = self.length >>> 0,
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self && !fun.call(thisp, self[i], i, object)) {
+ return false;
+ }
+ }
+ return true;
+ };
+}
+
+// ES5 15.4.4.17
+// http://es5.github.com/#x15.4.4.17
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
+if (!Array.prototype.some) {
+ Array.prototype.some = function some(fun /*, thisp */) {
+ var object = toObject(this),
+ self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ object,
+ length = self.length >>> 0,
+ thisp = arguments[1];
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ for (var i = 0; i < length; i++) {
+ if (i in self && fun.call(thisp, self[i], i, object)) {
+ return true;
+ }
+ }
+ return false;
+ };
+}
+
+// ES5 15.4.4.21
+// http://es5.github.com/#x15.4.4.21
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce
+if (!Array.prototype.reduce) {
+ Array.prototype.reduce = function reduce(fun /*, initial*/) {
+ var object = toObject(this),
+ self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ object,
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ // no value to return if no initial value and an empty array
+ if (!length && arguments.length == 1) {
+ throw new TypeError("reduce of empty array with no initial value");
+ }
+
+ var i = 0;
+ var result;
+ if (arguments.length >= 2) {
+ result = arguments[1];
+ } else {
+ do {
+ if (i in self) {
+ result = self[i++];
+ break;
+ }
+
+ // if array contains no values, no initial value to return
+ if (++i >= length) {
+ throw new TypeError("reduce of empty array with no initial value");
+ }
+ } while (true);
+ }
+
+ for (; i < length; i++) {
+ if (i in self) {
+ result = fun.call(void 0, result, self[i], i, object);
+ }
+ }
+
+ return result;
+ };
+}
+
+// ES5 15.4.4.22
+// http://es5.github.com/#x15.4.4.22
+// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduceRight
+if (!Array.prototype.reduceRight) {
+ Array.prototype.reduceRight = function reduceRight(fun /*, initial*/) {
+ var object = toObject(this),
+ self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ object,
+ length = self.length >>> 0;
+
+ // If no callback function or if callback is not a callable function
+ if (_toString(fun) != "[object Function]") {
+ throw new TypeError(fun + " is not a function");
+ }
+
+ // no value to return if no initial value, empty array
+ if (!length && arguments.length == 1) {
+ throw new TypeError("reduceRight of empty array with no initial value");
+ }
+
+ var result, i = length - 1;
+ if (arguments.length >= 2) {
+ result = arguments[1];
+ } else {
+ do {
+ if (i in self) {
+ result = self[i--];
+ break;
+ }
+
+ // if array contains no values, no initial value to return
+ if (--i < 0) {
+ throw new TypeError("reduceRight of empty array with no initial value");
+ }
+ } while (true);
+ }
+
+ do {
+ if (i in this) {
+ result = fun.call(void 0, result, self[i], i, object);
+ }
+ } while (i--);
+
+ return result;
+ };
+}
+
+// ES5 15.4.4.14
+// http://es5.github.com/#x15.4.4.14
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
+if (!Array.prototype.indexOf || ([0, 1].indexOf(1, 2) != -1)) {
+ Array.prototype.indexOf = function indexOf(sought /*, fromIndex */ ) {
+ var self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ toObject(this),
+ length = self.length >>> 0;
+
+ if (!length) {
+ return -1;
+ }
+
+ var i = 0;
+ if (arguments.length > 1) {
+ i = toInteger(arguments[1]);
+ }
+
+ // handle negative indices
+ i = i >= 0 ? i : Math.max(0, length + i);
+ for (; i < length; i++) {
+ if (i in self && self[i] === sought) {
+ return i;
+ }
+ }
+ return -1;
+ };
+}
+
+// ES5 15.4.4.15
+// http://es5.github.com/#x15.4.4.15
+// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
+if (!Array.prototype.lastIndexOf || ([0, 1].lastIndexOf(0, -3) != -1)) {
+ Array.prototype.lastIndexOf = function lastIndexOf(sought /*, fromIndex */) {
+ var self = splitString && _toString(this) == "[object String]" ?
+ this.split("") :
+ toObject(this),
+ length = self.length >>> 0;
+
+ if (!length) {
+ return -1;
+ }
+ var i = length - 1;
+ if (arguments.length > 1) {
+ i = Math.min(i, toInteger(arguments[1]));
+ }
+ // handle negative indices
+ i = i >= 0 ? i : length - Math.abs(i);
+ for (; i >= 0; i--) {
+ if (i in self && sought === self[i]) {
+ return i;
+ }
+ }
+ return -1;
+ };
+}
+
+//
+// Object
+// ======
+//
+
+// ES5 15.2.3.14
+// http://es5.github.com/#x15.2.3.14
+if (!Object.keys) {
+ // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation
+ var hasDontEnumBug = true,
+ dontEnums = [
+ "toString",
+ "toLocaleString",
+ "valueOf",
+ "hasOwnProperty",
+ "isPrototypeOf",
+ "propertyIsEnumerable",
+ "constructor"
+ ],
+ dontEnumsLength = dontEnums.length;
+
+ for (var key in {"toString": null}) {
+ hasDontEnumBug = false;
+ }
+
+ Object.keys = function keys(object) {
+
+ if (
+ (typeof object != "object" && typeof object != "function") ||
+ object === null
+ ) {
+ throw new TypeError("Object.keys called on a non-object");
+ }
+
+ var keys = [];
+ for (var name in object) {
+ if (owns(object, name)) {
+ keys.push(name);
+ }
+ }
+
+ if (hasDontEnumBug) {
+ for (var i = 0, ii = dontEnumsLength; i < ii; i++) {
+ var dontEnum = dontEnums[i];
+ if (owns(object, dontEnum)) {
+ keys.push(dontEnum);
+ }
+ }
+ }
+ return keys;
+ };
+
+}
+
+//
+// Date
+// ====
+//
+
+// ES5 15.9.5.43
+// http://es5.github.com/#x15.9.5.43
+// This function returns a String value represent the instance in time
+// represented by this Date object. The format of the String is the Date Time
+// string format defined in 15.9.1.15. All fields are present in the String.
+// The time zone is always UTC, denoted by the suffix Z. If the time value of
+// this object is not a finite Number a RangeError exception is thrown.
+var negativeDate = -62198755200000,
+ negativeYearString = "-000001";
+if (
+ !Date.prototype.toISOString ||
+ (new Date(negativeDate).toISOString().indexOf(negativeYearString) === -1)
+) {
+ Date.prototype.toISOString = function toISOString() {
+ var result, length, value, year, month;
+ if (!isFinite(this)) {
+ throw new RangeError("Date.prototype.toISOString called on non-finite value.");
+ }
+
+ year = this.getUTCFullYear();
+
+ month = this.getUTCMonth();
+ // see https://github.com/kriskowal/es5-shim/issues/111
+ year += Math.floor(month / 12);
+ month = (month % 12 + 12) % 12;
+
+ // the date time string format is specified in 15.9.1.15.
+ result = [month + 1, this.getUTCDate(),
+ this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds()];
+ year = (
+ (year < 0 ? "-" : (year > 9999 ? "+" : "")) +
+ ("00000" + Math.abs(year))
+ .slice(0 <= year && year <= 9999 ? -4 : -6)
+ );
+
+ length = result.length;
+ while (length--) {
+ value = result[length];
+ // pad months, days, hours, minutes, and seconds to have two
+ // digits.
+ if (value < 10) {
+ result[length] = "0" + value;
+ }
+ }
+ // pad milliseconds to have three digits.
+ return (
+ year + "-" + result.slice(0, 2).join("-") +
+ "T" + result.slice(2).join(":") + "." +
+ ("000" + this.getUTCMilliseconds()).slice(-3) + "Z"
+ );
+ };
+}
+
+
+// ES5 15.9.5.44
+// http://es5.github.com/#x15.9.5.44
+// This function provides a String representation of a Date object for use by
+// JSON.stringify (15.12.3).
+var dateToJSONIsSupported = false;
+try {
+ dateToJSONIsSupported = (
+ Date.prototype.toJSON &&
+ new Date(NaN).toJSON() === null &&
+ new Date(negativeDate).toJSON().indexOf(negativeYearString) !== -1 &&
+ Date.prototype.toJSON.call({ // generic
+ toISOString: function () {
+ return true;
+ }
+ })
+ );
+} catch (e) {
+}
+if (!dateToJSONIsSupported) {
+ Date.prototype.toJSON = function toJSON(key) {
+ // When the toJSON method is called with argument key, the following
+ // steps are taken:
+
+ // 1. Let O be the result of calling ToObject, giving it the this
+ // value as its argument.
+ // 2. Let tv be toPrimitive(O, hint Number).
+ var o = Object(this),
+ tv = toPrimitive(o),
+ toISO;
+ // 3. If tv is a Number and is not finite, return null.
+ if (typeof tv === "number" && !isFinite(tv)) {
+ return null;
+ }
+ // 4. Let toISO be the result of calling the [[Get]] internal method of
+ // O with argument "toISOString".
+ toISO = o.toISOString;
+ // 5. If IsCallable(toISO) is false, throw a TypeError exception.
+ if (typeof toISO != "function") {
+ throw new TypeError("toISOString property is not callable");
+ }
+ // 6. Return the result of calling the [[Call]] internal method of
+ // toISO with O as the this value and an empty argument list.
+ return toISO.call(o);
+
+ // NOTE 1 The argument is ignored.
+
+ // NOTE 2 The toJSON function is intentionally generic; it does not
+ // require that its this value be a Date object. Therefore, it can be
+ // transferred to other kinds of objects for use as a method. However,
+ // it does require that any such object have a toISOString method. An
+ // object is free to use the argument key to filter its
+ // stringification.
+ };
+}
+
+// ES5 15.9.4.2
+// http://es5.github.com/#x15.9.4.2
+// based on work shared by Daniel Friesen (dantman)
+// http://gist.github.com/303249
+if (!Date.parse || "Date.parse is buggy") {
+ // XXX global assignment won't work in embeddings that use
+ // an alternate object for the context.
+ Date = (function(NativeDate) {
+
+ // Date.length === 7
+ function Date(Y, M, D, h, m, s, ms) {
+ var length = arguments.length;
+ if (this instanceof NativeDate) {
+ var date = length == 1 && String(Y) === Y ? // isString(Y)
+ // We explicitly pass it through parse:
+ new NativeDate(Date.parse(Y)) :
+ // We have to manually make calls depending on argument
+ // length here
+ length >= 7 ? new NativeDate(Y, M, D, h, m, s, ms) :
+ length >= 6 ? new NativeDate(Y, M, D, h, m, s) :
+ length >= 5 ? new NativeDate(Y, M, D, h, m) :
+ length >= 4 ? new NativeDate(Y, M, D, h) :
+ length >= 3 ? new NativeDate(Y, M, D) :
+ length >= 2 ? new NativeDate(Y, M) :
+ length >= 1 ? new NativeDate(Y) :
+ new NativeDate();
+ // Prevent mixups with unfixed Date object
+ date.constructor = Date;
+ return date;
+ }
+ return NativeDate.apply(this, arguments);
+ };
+
+ // 15.9.1.15 Date Time String Format.
+ var isoDateExpression = new RegExp("^" +
+ "(\\d{4}|[\+\-]\\d{6})" + // four-digit year capture or sign +
+ // 6-digit extended year
+ "(?:-(\\d{2})" + // optional month capture
+ "(?:-(\\d{2})" + // optional day capture
+ "(?:" + // capture hours:minutes:seconds.milliseconds
+ "T(\\d{2})" + // hours capture
+ ":(\\d{2})" + // minutes capture
+ "(?:" + // optional :seconds.milliseconds
+ ":(\\d{2})" + // seconds capture
+ "(?:\\.(\\d{3}))?" + // milliseconds capture
+ ")?" +
+ "(" + // capture UTC offset component
+ "Z|" + // UTC capture
+ "(?:" + // offset specifier +/-hours:minutes
+ "([-+])" + // sign capture
+ "(\\d{2})" + // hours offset capture
+ ":(\\d{2})" + // minutes offset capture
+ ")" +
+ ")?)?)?)?" +
+ "$");
+
+ var months = [
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+ ];
+
+ function dayFromMonth(year, month) {
+ var t = month > 1 ? 1 : 0;
+ return (
+ months[month] +
+ Math.floor((year - 1969 + t) / 4) -
+ Math.floor((year - 1901 + t) / 100) +
+ Math.floor((year - 1601 + t) / 400) +
+ 365 * (year - 1970)
+ );
+ }
+
+ // Copy any custom methods a 3rd party library may have added
+ for (var key in NativeDate) {
+ Date[key] = NativeDate[key];
+ }
+
+ // Copy "native" methods explicitly; they may be non-enumerable
+ Date.now = NativeDate.now;
+ Date.UTC = NativeDate.UTC;
+ Date.prototype = NativeDate.prototype;
+ Date.prototype.constructor = Date;
+
+ // Upgrade Date.parse to handle simplified ISO 8601 strings
+ Date.parse = function parse(string) {
+ var match = isoDateExpression.exec(string);
+ if (match) {
+ // parse months, days, hours, minutes, seconds, and milliseconds
+ // provide default values if necessary
+ // parse the UTC offset component
+ var year = Number(match[1]),
+ month = Number(match[2] || 1) - 1,
+ day = Number(match[3] || 1) - 1,
+ hour = Number(match[4] || 0),
+ minute = Number(match[5] || 0),
+ second = Number(match[6] || 0),
+ millisecond = Number(match[7] || 0),
+ // When time zone is missed, local offset should be used
+ // (ES 5.1 bug)
+ // see https://bugs.ecmascript.org/show_bug.cgi?id=112
+ offset = !match[4] || match[8] ?
+ 0 : Number(new NativeDate(1970, 0)),
+ signOffset = match[9] === "-" ? 1 : -1,
+ hourOffset = Number(match[10] || 0),
+ minuteOffset = Number(match[11] || 0),
+ result;
+ if (
+ hour < (
+ minute > 0 || second > 0 || millisecond > 0 ?
+ 24 : 25
+ ) &&
+ minute < 60 && second < 60 && millisecond < 1000 &&
+ month > -1 && month < 12 && hourOffset < 24 &&
+ minuteOffset < 60 && // detect invalid offsets
+ day > -1 &&
+ day < (
+ dayFromMonth(year, month + 1) -
+ dayFromMonth(year, month)
+ )
+ ) {
+ result = (
+ (dayFromMonth(year, month) + day) * 24 +
+ hour +
+ hourOffset * signOffset
+ ) * 60;
+ result = (
+ (result + minute + minuteOffset * signOffset) * 60 +
+ second
+ ) * 1000 + millisecond + offset;
+ if (-8.64e15 <= result && result <= 8.64e15) {
+ return result;
+ }
+ }
+ return NaN;
+ }
+ return NativeDate.parse.apply(this, arguments);
+ };
+
+ return Date;
+ })(Date);
+}
+
+// ES5 15.9.4.4
+// http://es5.github.com/#x15.9.4.4
+if (!Date.now) {
+ Date.now = function now() {
+ return new Date().getTime();
+ };
+}
+
+
+//
+// String
+// ======
+//
+
+
+// ES5 15.5.4.14
+// http://es5.github.com/#x15.5.4.14
+// [bugfix, chrome]
+// If separator is undefined, then the result array contains just one String,
+// which is the this value (converted to a String). If limit is not undefined,
+// then the output array is truncated so that it contains no more than limit
+// elements.
+// "0".split(undefined, 0) -> []
+if("0".split(void 0, 0).length) {
+ var string_split = String.prototype.split;
+ String.prototype.split = function(separator, limit) {
+ if(separator === void 0 && limit === 0)return [];
+ return string_split.apply(this, arguments);
+ }
+}
+
+// ECMA-262, 3rd B.2.3
+// Note an ECMAScript standart, although ECMAScript 3rd Edition has a
+// non-normative section suggesting uniform semantics and it should be
+// normalized across all browsers
+// [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE
+if("".substr && "0b".substr(-1) !== "b") {
+ var string_substr = String.prototype.substr;
+ /**
+ * Get the substring of a string
+ * @param {integer} start where to start the substring
+ * @param {integer} length how many characters to return
+ * @return {string}
+ */
+ String.prototype.substr = function(start, length) {
+ return string_substr.call(
+ this,
+ start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start,
+ length
+ );
+ }
+}
+
+// ES5 15.5.4.20
+// http://es5.github.com/#x15.5.4.20
+var ws = "\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003" +
+ "\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028" +
+ "\u2029\uFEFF";
+if (!String.prototype.trim || ws.trim()) {
+ // http://blog.stevenlevithan.com/archives/faster-trim-javascript
+ // http://perfectionkills.com/whitespace-deviations/
+ ws = "[" + ws + "]";
+ var trimBeginRegexp = new RegExp("^" + ws + ws + "*"),
+ trimEndRegexp = new RegExp(ws + ws + "*$");
+ String.prototype.trim = function trim() {
+ if (this === undefined || this === null) {
+ throw new TypeError("can't convert "+this+" to object");
+ }
+ return String(this)
+ .replace(trimBeginRegexp, "")
+ .replace(trimEndRegexp, "");
+ };
+}
+
+//
+// Util
+// ======
+//
+
+// ES5 9.4
+// http://es5.github.com/#x9.4
+// http://jsperf.com/to-integer
+
+function toInteger(n) {
+ n = +n;
+ if (n !== n) { // isNaN
+ n = 0;
+ } else if (n !== 0 && n !== (1/0) && n !== -(1/0)) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ return n;
+}
+
+function isPrimitive(input) {
+ var type = typeof input;
+ return (
+ input === null ||
+ type === "undefined" ||
+ type === "boolean" ||
+ type === "number" ||
+ type === "string"
+ );
+}
+
+function toPrimitive(input) {
+ var val, valueOf, toString;
+ if (isPrimitive(input)) {
+ return input;
+ }
+ valueOf = input.valueOf;
+ if (typeof valueOf === "function") {
+ val = valueOf.call(input);
+ if (isPrimitive(val)) {
+ return val;
+ }
+ }
+ toString = input.toString;
+ if (typeof toString === "function") {
+ val = toString.call(input);
+ if (isPrimitive(val)) {
+ return val;
+ }
+ }
+ throw new TypeError();
+}
+
+// ES5 9.9
+// http://es5.github.com/#x9.9
+var toObject = function (o) {
+ if (o == null) { // this matches both null and undefined
+ throw new TypeError("can't convert "+o+" to object");
+ }
+ return Object(o);
+};
+
+});
diff --git a/node_modules/es5-shimify/package.json b/node_modules/es5-shimify/package.json
new file mode 100644
index 0000000..35837d0
--- /dev/null
+++ b/node_modules/es5-shimify/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "es5-shimify",
+ "description": "ES5-shim lib for browsers",
+ "version": "2.0.5",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/spine/es5-shimify.git"
+ },
+ "author": {
+ "name": "Alex MacCaw",
+ "email": "info@eribium.org"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "main": "./index.js",
+ "bugs": {
+ "url": "https://github.com/spine/es5-shimify/issues"
+ },
+ "readme": "ERROR: No README data found!",
+ "_id": "es5-shimify@2.0.5",
+ "_from": "es5-shimify@~2.0.5"
+}
diff --git a/node_modules/jqueryify/index.js b/node_modules/jqueryify/index.js
new file mode 100644
index 0000000..f97955b
--- /dev/null
+++ b/node_modules/jqueryify/index.js
@@ -0,0 +1,8758 @@
+/*!
+ * jQuery JavaScript Library v2.0.0
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-04-18
+ */
+(function( window, undefined ) {
+
+// Can't do this because several apps including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+// Support: Firefox 18+
+//"use strict";
+var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Support: IE9
+ // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
+ core_strundefined = typeof undefined,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ location = window.location,
+ document = window.document,
+ docElem = document.documentElement,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // [[Class]] -> type pairs
+ class2type = {},
+
+ // List of deleted data cache ids, so we can reuse them
+ core_deletedIds = [],
+
+ core_version = "2.0.0",
+
+ // Save a reference to some core methods
+ core_concat = core_deletedIds.concat,
+ core_push = core_deletedIds.push,
+ core_slice = core_deletedIds.slice,
+ core_indexOf = core_deletedIds.indexOf,
+ core_toString = class2type.toString,
+ core_hasOwn = class2type.hasOwnProperty,
+ core_trim = core_version.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
+
+ // Used for splitting on whitespace
+ core_rnotwhite = /\S+/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return letter.toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ completed = function() {
+ document.removeEventListener( "DOMContentLoaded", completed, false );
+ window.removeEventListener( "load", completed, false );
+ jQuery.ready();
+ };
+
+jQuery.fn = jQuery.prototype = {
+ // The current version of jQuery being used
+ jquery: core_version,
+
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+
+ // scripts is true for back-compat
+ jQuery.merge( this, jQuery.parseHTML(
+ match[1],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+ // Properties of context are called as methods if possible
+ if ( jQuery.isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+ ret.context = this.context;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ) );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
+
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray,
+
+ isWindow: function( obj ) {
+ return obj != null && obj === obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ if ( obj == null ) {
+ return String( obj );
+ }
+ // Support: Safari <= 5.1 (functionish RegExp)
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ core_toString.call(obj) ] || "object" :
+ typeof obj;
+ },
+
+ isPlainObject: function( obj ) {
+ // Not plain objects:
+ // - Any object or value whose internal [[Class]] property is not "[object Object]"
+ // - DOM nodes
+ // - window
+ if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ // Support: Firefox <20
+ // The try/catch suppresses exceptions thrown when attempting to access
+ // the "constructor" property of certain host objects, ie. |window.location|
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
+ try {
+ if ( obj.constructor &&
+ !core_hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+ return false;
+ }
+ } catch ( e ) {
+ return false;
+ }
+
+ // If the function hasn't returned already, we're confident that
+ // |obj| is a plain object, created by {} or constructed with new Object
+ return true;
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // keepScripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, keepScripts ) {
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ keepScripts = context;
+ context = false;
+ }
+ context = context || document;
+
+ var parsed = rsingleTag.exec( data ),
+ scripts = !keepScripts && [];
+
+ // Single tag
+ if ( parsed ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts );
+
+ if ( scripts ) {
+ jQuery( scripts ).remove();
+ }
+
+ return jQuery.merge( [], parsed.childNodes );
+ },
+
+ parseJSON: JSON.parse,
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+
+ // Support: IE9
+ try {
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } catch ( e ) {
+ xml = undefined;
+ }
+
+ if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ globalEval: function( code ) {
+ var script,
+ indirect = eval;
+
+ code = jQuery.trim( code );
+
+ if ( code ) {
+ // If the code includes a valid, prologue position
+ // strict mode pragma, execute code by injecting a
+ // script tag into the document.
+ if ( code.indexOf("use strict") === 1 ) {
+ script = document.createElement("script");
+ script.text = code;
+ document.head.appendChild( script ).parentNode.removeChild( script );
+ } else {
+ // Otherwise, avoid the DOM node creation, insertion
+ // and removal by using an indirect global eval
+ indirect( code );
+ }
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var value,
+ i = 0,
+ length = obj.length,
+ isArray = isArraylike( obj );
+
+ if ( args ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ trim: function( text ) {
+ return text == null ? "" : core_trim.call( text );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var ret = results || [];
+
+ if ( arr != null ) {
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
+ } else {
+ core_push.call( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ return arr == null ? -1 : core_indexOf.call( arr, elem, i );
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value,
+ i = 0,
+ length = elems.length,
+ isArray = isArraylike( elems ),
+ ret = [];
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return core_concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ length = elems.length,
+ bulk = key == null;
+
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+ }
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
+
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
+ }
+
+ if ( bulk ) {
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
+ }
+ }
+
+ if ( fn ) {
+ for ( ; i < length; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
+ }
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: Date.now,
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations.
+ // Note: this method belongs to the css module but it's needed here for the support module.
+ // If support gets modularized, this method should be moved back to the css module.
+ swap: function( elem, options, callback, args ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.apply( elem, args || [] );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready );
+
+ } else {
+
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed, false );
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+ var length = obj.length,
+ type = jQuery.type( obj );
+
+ if ( jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.nodeType === 1 && length ) {
+ return true;
+ }
+
+ return type === "array" || type !== "function" &&
+ ( length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj );
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+/*!
+ * Sizzle CSS Selector Engine v1.9.2-pre
+ * http://sizzlejs.com/
+ *
+ * Copyright 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2013-04-16
+ */
+(function( window, undefined ) {
+
+var i,
+ cachedruns,
+ Expr,
+ getText,
+ isXML,
+ compile,
+ outermostContext,
+ sortInput,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
+
+ // Instance-specific data
+ expando = "sizzle" + -(new Date()),
+ preferredDoc = window.document,
+ support = {},
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ hasDuplicate = false,
+ sortOrder = function() { return 0; },
+
+ // General-purpose constants
+ strundefined = typeof undefined,
+ MAX_NEGATIVE = 1 << 31,
+
+ // Array methods
+ arr = [],
+ pop = arr.pop,
+ push_native = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+ // Use a stripped-down indexOf if we can't use a native one
+ indexOf = arr.indexOf || function( elem ) {
+ var i = 0,
+ len = this.length;
+ for ( ; i < len; i++ ) {
+ if ( this[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+ // Regular expressions
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+ // Prefer arguments quoted,
+ // then not containing pseudos/brackets,
+ // then attribute selectors/non-parenthetical expressions,
+ // then anything else
+ // These preferences are here to reduce the number of selectors
+ // needing tokenize in the PSEUDO preFilter
+ pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+ rsibling = new RegExp( whitespace + "*[+~]" ),
+ rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ),
+
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "boolean": new RegExp( "^(?:" + booleans + ")$", "i" ),
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+ whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
+
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
+
+ rescape = /'|\\/g,
+
+ // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,
+ funescape = function( _, escaped ) {
+ var high = "0x" + escaped - 0x10000;
+ // NaN means non-codepoint
+ return high !== high ?
+ escaped :
+ // BMP codepoint
+ high < 0 ?
+ String.fromCharCode( high + 0x10000 ) :
+ // Supplemental Plane codepoint (surrogate pair)
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ };
+
+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ (arr = slice.call( preferredDoc.childNodes )),
+ preferredDoc.childNodes
+ );
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = { apply: arr.length ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ push_native.apply( target, slice.call(els) );
+ } :
+
+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+ // Can't trust NodeList.length
+ while ( (target[j++] = els[i++]) ) {}
+ target.length = j - 1;
+ }
+ };
+}
+
+/**
+ * For feature detection
+ * @param {Function} fn The function to test for native support
+ */
+function isNative( fn ) {
+ return rnative.test( fn + "" );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var cache,
+ keys = [];
+
+ return (cache = function( key, value ) {
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key += " " ) > Expr.cacheLength ) {
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return (cache[ key ] = value);
+ });
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+function assert( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return !!fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ if ( div.parentNode ) {
+ div.parentNode.removeChild( div );
+ }
+ // release memory in IE
+ div = null;
+ }
+}
+
+function Sizzle( selector, context, results, seed ) {
+ var match, elem, m, nodeType,
+ // QSA vars
+ i, groups, old, nid, newContext, newSelector;
+
+ if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+ setDocument( context );
+ }
+
+ context = context || document;
+ results = results || [];
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( documentIsHTML && !seed ) {
+
+ // Shortcuts
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // QSA path
+ if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ nid = old = expando;
+ newContext = context;
+ newSelector = nodeType === 9 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + toSelector( groups[i] );
+ }
+ newContext = rsibling.test( selector ) && context.parentNode || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Detect xml
+ * @param {Element|Object} elem An element or a document
+ */
+isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+ var doc = node ? node.ownerDocument || node : preferredDoc;
+
+ // If no document and documentElement is available, return
+ if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Set our document
+ document = doc;
+ docElem = doc.documentElement;
+
+ // Support tests
+ documentIsHTML = !isXML( doc );
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert(function( div ) {
+ div.appendChild( doc.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ });
+
+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
+ support.attributes = assert(function( div ) {
+ div.className = "i";
+ return !div.getAttribute("className");
+ });
+
+ // Check if getElementsByClassName can be trusted
+ support.getElementsByClassName = assert(function( div ) {
+ div.innerHTML = "";
+
+ // Support: Safari<4
+ // Catch class over-caching
+ div.firstChild.className = "i";
+ // Support: Opera<10
+ // Catch gEBCN failure to find non-leading classes
+ return div.getElementsByClassName("i").length === 2;
+ });
+
+ // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+ // Detached nodes confoundingly follow *each other*
+ support.sortDetached = assert(function( div1 ) {
+ // Should return 1, but returns 4 (following)
+ return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+ });
+
+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // Support: Windows 8 Native Apps
+ // Assigning innerHTML with "name" attributes throws uncatchable exceptions
+ // (http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx)
+ // and the broken getElementById methods don't pick up programatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert(function( div ) {
+ docElem.appendChild( div ).id = expando;
+ return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+ });
+
+ // ID find and filter
+ if ( support.getById ) {
+ Expr.find["ID"] = function( id, context ) {
+ if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ };
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute("id") === attrId;
+ };
+ };
+ } else {
+ Expr.find["ID"] = function( id, context ) {
+ if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
+ var m = context.getElementById( id );
+
+ return m ?
+ m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === attrId;
+ };
+ };
+ }
+
+ // Tag
+ Expr.find["TAG"] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+ results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+ if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See http://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
+
+ if ( (support.qsa = isNative(doc.querySelectorAll)) ) {
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ div.innerHTML = "";
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
+
+ assert(function( div ) {
+
+ // Support: Opera 10-12/IE8
+ // ^= $= *= and empty values
+ // Should not select anything
+ // Support: Windows 8 Native Apps
+ // The type attribute is restricted during .innerHTML assignment
+ var input = document.createElement("input");
+ input.setAttribute( "type", "hidden" );
+ div.appendChild( input ).setAttribute( "t", "" );
+
+ if ( div.querySelectorAll("[t^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ div.querySelectorAll("*,:x");
+ rbuggyQSA.push(",.*:");
+ });
+ }
+
+ if ( (support.matchesSelector = isNative( (matches = docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector) )) ) {
+
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( div, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ });
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+ // Element contains another
+ // Purposefully does not implement inclusive descendent
+ // As in, an element does not contain itself
+ contains = isNative(docElem.contains) || docElem.compareDocumentPosition ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ));
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ // Document order sorting
+ sortOrder = docElem.compareDocumentPosition ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
+
+ if ( compare ) {
+ // Disconnected nodes
+ if ( compare & 1 ||
+ (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+ // Choose the first element that is related to our preferred document
+ if ( a === doc || contains(preferredDoc, a) ) {
+ return -1;
+ }
+ if ( b === doc || contains(preferredDoc, b) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ }
+
+ // Not directly comparable, sort on existence of method
+ return a.compareDocumentPosition ? -1 : 1;
+ } :
+ function( a, b ) {
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Parentless nodes are either documents or disconnected
+ } else if ( !aup || !bup ) {
+ return a === doc ? -1 :
+ b === doc ? 1 :
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( (cur = cur.parentNode) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( (cur = cur.parentNode) ) {
+ bp.unshift( cur );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[i] === bp[i] ) {
+ i++;
+ }
+
+ return i ?
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[i], bp[i] ) :
+
+ // Otherwise nodes in our document sort first
+ ap[i] === preferredDoc ? -1 :
+ bp[i] === preferredDoc ? 1 :
+ 0;
+ };
+
+ return document;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ // rbuggyQSA always contains :focus, so no need for an existence check
+ if ( support.matchesSelector && documentIsHTML &&
+ (!rbuggyMatches || !rbuggyMatches.test(expr)) &&
+ (!rbuggyQSA || !rbuggyQSA.test(expr)) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle( expr, document, null, [elem] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+ // Set document vars if needed
+ if ( ( context.ownerDocument || context ) !== document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+ val = fn && fn( elem, name, !documentIsHTML );
+
+ return val === undefined ?
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ (val = elem.getAttributeNode(name)) && val.specified ?
+ val.value :
+ null :
+ val;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+// Document sorting and removing duplicates
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ return results;
+};
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns Returns -1 if a precedes b, 1 if a follows b
+ */
+function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE );
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( (cur = cur.nextSibling) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+}
+
+// Fetches boolean attributes by node
+function boolHandler( elem, name, isXML ) {
+ var val;
+ return isXML ?
+ undefined :
+ (val = elem.getAttributeNode( name )) && val.specified ?
+ val.value :
+ elem[ name ] === true ? name.toLowerCase() : null;
+}
+
+// Fetches attributes without interpolation
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+function interpolationHandler( elem, name, isXML ) {
+ var val;
+ return isXML ?
+ undefined :
+ (val = elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ));
+}
+
+// Returns a function to use in pseudos for input types
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for buttons
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for positionals
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+ // If no nodeType, this is expected to be an array
+ for ( ; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (see #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1].slice( 0, 3 ) === "nth" ) {
+ // nth-* requires argument
+ if ( !match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+ match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[5] && match[2];
+
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[4] ) {
+ match[2] = match[4];
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ match[0] = match[0].slice( 0, excess );
+ match[2] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() { return true; } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+ classCache( className, function( elem ) {
+ return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
+ });
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, what, argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, context, xml ) {
+ var cache, outerCache, node, diff, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( (node = node[ dir ]) ) {
+ if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+ // Seek `elem` from a previously-cached index
+ outerCache = parent[ expando ] || (parent[ expando ] = {});
+ cache = outerCache[ type ] || [];
+ nodeIndex = cache[0] === dirruns && cache[1];
+ diff = cache[0] === dirruns && cache[2];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ // Use previously-cached element index if available
+ } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+ diff = cache[1];
+
+ // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ } else {
+ // Use the same loop as above to seek `elem` from the start
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ // Potentially complex pseudos
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+ // lang value must be a valid identifier
+ if ( !ridentifier.test(lang || "") ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( (elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+ return false;
+ };
+ }),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
+
+ // Boolean properties
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ // Contents
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+ // not comment, processing instructions, or others
+ // Thanks to Diego Perini for the nodeName shortcut
+ // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "text": function( elem ) {
+ var attr;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+function tokenize( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[0].length ) || soFar;
+ }
+ groups.push( tokens = [] );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+ // Cast descendant combinators to space
+ type: match[0].replace( rtrim, " " )
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match ))) ) {
+ matched = match.shift();
+ tokens.push( {
+ value: matched,
+ type: type,
+ matches: match
+ } );
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+}
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[i].value;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var data, cache, outerCache,
+ dirkey = dirruns + " " + doneName;
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || (elem[ expando ] = {});
+ if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
+ if ( (data = cache[1]) === true || data === cachedruns ) {
+ return data === true;
+ }
+ } else {
+ cache = outerCache[ dir ] = [ dirkey ];
+ cache[1] = matcher( elem, context, xml ) || cachedruns;
+ if ( cache[1] === true ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( (elem = temp[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( (matcherIn[i] = elem) );
+ }
+ }
+ postFinder( null, (matcherOut = []), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) &&
+ (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
+
+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+ } else {
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ // A counter to specify which element is currently being matched
+ var matcherCachedRuns = 0,
+ bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, expandContext ) {
+ var elem, j, matcher,
+ setMatched = [],
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ outermost = expandContext != null,
+ contextBackup = outermostContext,
+ // We must always have either seed elements or context
+ elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ cachedruns = matcherCachedRuns;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+ while ( (matcher = elementMatchers[j++]) ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ cachedruns = ++matcherCachedRuns;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( (matcher = setMatchers[j++]) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !group ) {
+ group = tokenize( selector );
+ }
+ i = group.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( group[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+ }
+ return cached;
+};
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
+ }
+ return results;
+}
+
+function select( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ match = tokenize( selector );
+
+ if ( !seed ) {
+ // Try to minimize operations if there is only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ context.nodeType === 9 && documentIsHTML &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+ if ( !context ) {
+ return results;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( runescape, funescape ),
+ rsibling.test( tokens[0].type ) && context.parentNode || context
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function
+ // Provide `match` to avoid retokenization if we modified the selector above
+ compile( selector, match )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ rsibling.test( selector )
+ );
+ return results;
+}
+
+// Deprecated
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Chrome<<14
+// Always assume duplicates if they aren't passed to the comparison function
+[0, 0].sort( sortOrder );
+support.detectDuplicates = hasDuplicate;
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+assert(function( div ) {
+ div.innerHTML = "";
+ if ( div.firstChild.getAttribute("href") !== "#" ) {
+ var attrs = "type|href|height|width".split("|"),
+ i = attrs.length;
+ while ( i-- ) {
+ Expr.attrHandle[ attrs[i] ] = interpolationHandler;
+ }
+ }
+});
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+assert(function( div ) {
+ if ( div.getAttribute("disabled") != null ) {
+ var attrs = booleans.split("|"),
+ i = attrs.length;
+ while ( i-- ) {
+ Expr.attrHandle[ attrs[i] ] = boolHandler;
+ }
+ }
+});
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})( window );
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
+ has: function( fn ) {
+ return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ firingLength = 0;
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ](function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ }
+ });
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ]
+ deferred[ tuple[0] ] = function() {
+ deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ return this;
+ };
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+jQuery.support = (function( support ) {
+ var input = document.createElement("input"),
+ fragment = document.createDocumentFragment(),
+ div = document.createElement("div"),
+ select = document.createElement("select"),
+ opt = select.appendChild( document.createElement("option") );
+
+ // Finish early in limited environments
+ if ( !input.type ) {
+ return support;
+ }
+
+ input.type = "checkbox";
+
+ // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
+ // Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere)
+ support.checkOn = input.value !== "";
+
+ // Must access the parent to make an option select properly
+ // Support: IE9, IE10
+ support.optSelected = opt.selected;
+
+ // Will be defined later
+ support.reliableMarginRight = true;
+ support.boxSizingReliable = true;
+ support.pixelPosition = false;
+
+ // Make sure checked status is properly cloned
+ // Support: IE9, IE10
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Check if an input maintains its value after becoming a radio
+ // Support: IE9, IE10
+ input = document.createElement("input");
+ input.value = "t";
+ input.type = "radio";
+ support.radioValue = input.value === "t";
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "checked", "t" );
+ input.setAttribute( "name", "t" );
+
+ fragment.appendChild( input );
+
+ // Support: Safari 5.1, Android 4.x, Android 2.3
+ // old WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: Firefox, Chrome, Safari
+ // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
+ support.focusinBubbles = "onfocusin" in window;
+
+ div.style.backgroundClip = "content-box";
+ div.cloneNode( true ).style.backgroundClip = "";
+ support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, marginDiv,
+ // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
+ divReset = "padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",
+ body = document.getElementsByTagName("body")[ 0 ];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
+
+ // Check box-sizing and margin behavior.
+ body.appendChild( container ).appendChild( div );
+ div.innerHTML = "";
+ // Support: Firefox, Android 2.3 (Prefixed box-sizing versions).
+ div.style.cssText = "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%";
+
+ // Workaround failing boxSizing test due to offsetWidth returning wrong value
+ // with some non-1 values of body zoom, ticket #13543
+ jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
+ support.boxSizing = div.offsetWidth === 4;
+ });
+
+ // Use window.getComputedStyle because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Support: Android 2.3
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. (#3333)
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = div.appendChild( document.createElement("div") );
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ body.removeChild( container );
+ });
+
+ return support;
+})( {} );
+
+/*
+ Implementation Summary
+
+ 1. Enforce API surface and semantic compatibility with 1.9.x branch
+ 2. Improve the module's maintainability by reducing the storage
+ paths to a single mechanism.
+ 3. Use the same single mechanism to support "private" and "user" data.
+ 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+ 5. Avoid exposing implementation details on user objects (eg. expando properties)
+ 6. Provide a clear path for implementation upgrade to WeakMap in 2014
+*/
+var data_user, data_priv,
+ rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+function Data() {
+ // Support: Android < 4,
+ // Old WebKit does not have Object.preventExtensions/freeze method,
+ // return new empty object instead with no [[set]] accessor
+ Object.defineProperty( this.cache = {}, 0, {
+ get: function() {
+ return {};
+ }
+ });
+
+ this.expando = jQuery.expando + Math.random();
+}
+
+Data.uid = 1;
+
+Data.accepts = function( owner ) {
+ // Accepts only:
+ // - Node
+ // - Node.ELEMENT_NODE
+ // - Node.DOCUMENT_NODE
+ // - Object
+ // - Any
+ return owner.nodeType ?
+ owner.nodeType === 1 || owner.nodeType === 9 : true;
+};
+
+Data.prototype = {
+ key: function( owner ) {
+ // We can accept data for non-element nodes in modern browsers,
+ // but we should not, see #8335.
+ // Always return the key for a frozen object.
+ if ( !Data.accepts( owner ) ) {
+ return 0;
+ }
+
+ var descriptor = {},
+ // Check if the owner object already has a cache key
+ unlock = owner[ this.expando ];
+
+ // If not, create one
+ if ( !unlock ) {
+ unlock = Data.uid++;
+
+ // Secure it in a non-enumerable, non-writable property
+ try {
+ descriptor[ this.expando ] = { value: unlock };
+ Object.defineProperties( owner, descriptor );
+
+ // Support: Android < 4
+ // Fallback to a less secure definition
+ } catch ( e ) {
+ descriptor[ this.expando ] = unlock;
+ jQuery.extend( owner, descriptor );
+ }
+ }
+
+ // Ensure the cache object
+ if ( !this.cache[ unlock ] ) {
+ this.cache[ unlock ] = {};
+ }
+
+ return unlock;
+ },
+ set: function( owner, data, value ) {
+ var prop,
+ // There may be an unlock assigned to this node,
+ // if there is no entry for this "owner", create one inline
+ // and set the unlock as though an owner entry had always existed
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
+
+ // Handle: [ owner, key, value ] args
+ if ( typeof data === "string" ) {
+ cache[ data ] = value;
+
+ // Handle: [ owner, { properties } ] args
+ } else {
+ // Support an expectation from the old data system where plain
+ // objects used to initialize would be set to the cache by
+ // reference, instead of having properties and values copied.
+ // Note, this will kill the connection between
+ // "this.cache[ unlock ]" and "cache"
+ if ( jQuery.isEmptyObject( cache ) ) {
+ this.cache[ unlock ] = data;
+ // Otherwise, copy the properties one-by-one to the cache object
+ } else {
+ for ( prop in data ) {
+ cache[ prop ] = data[ prop ];
+ }
+ }
+ }
+ },
+ get: function( owner, key ) {
+ // Either a valid cache is found, or will be created.
+ // New caches will be created and the unlock returned,
+ // allowing direct access to the newly created
+ // empty data object. A valid owner object must be provided.
+ var cache = this.cache[ this.key( owner ) ];
+
+ return key === undefined ?
+ cache : cache[ key ];
+ },
+ access: function( owner, key, value ) {
+ // In cases where either:
+ //
+ // 1. No key was specified
+ // 2. A string key was specified, but no value provided
+ //
+ // Take the "read" path and allow the get method to determine
+ // which value to return, respectively either:
+ //
+ // 1. The entire cache object
+ // 2. The data stored at the key
+ //
+ if ( key === undefined ||
+ ((key && typeof key === "string") && value === undefined) ) {
+ return this.get( owner, key );
+ }
+
+ // [*]When the key is not a string, or both a key and value
+ // are specified, set or extend (existing objects) with either:
+ //
+ // 1. An object of properties
+ // 2. A key and value
+ //
+ this.set( owner, key, value );
+
+ // Since the "set" path can have two possible entry points
+ // return the expected data based on which path was taken[*]
+ return value !== undefined ? value : key;
+ },
+ remove: function( owner, key ) {
+ var i, name,
+ unlock = this.key( owner ),
+ cache = this.cache[ unlock ];
+
+ if ( key === undefined ) {
+ this.cache[ unlock ] = {};
+
+ } else {
+ // Support array or space separated string of keys
+ if ( jQuery.isArray( key ) ) {
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = key.concat( key.map( jQuery.camelCase ) );
+ } else {
+ // Try the string as a key before any manipulation
+ if ( key in cache ) {
+ name = [ key ];
+ } else {
+ // If a key with the spaces exists, use it.
+ // Otherwise, create an array by matching non-whitespace
+ name = jQuery.camelCase( key );
+ name = name in cache ?
+ [ name ] : ( name.match( core_rnotwhite ) || [] );
+ }
+ }
+
+ i = name.length;
+ while ( i-- ) {
+ delete cache[ name[ i ] ];
+ }
+ }
+ },
+ hasData: function( owner ) {
+ return !jQuery.isEmptyObject(
+ this.cache[ owner[ this.expando ] ] || {}
+ );
+ },
+ discard: function( owner ) {
+ delete this.cache[ this.key( owner ) ];
+ }
+};
+
+// These may be used throughout the jQuery core codebase
+data_user = new Data();
+data_priv = new Data();
+
+
+jQuery.extend({
+ acceptData: Data.accepts,
+
+ hasData: function( elem ) {
+ return data_user.hasData( elem ) || data_priv.hasData( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return data_user.access( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ data_user.remove( elem, name );
+ },
+
+ // TODO: Now that all calls to _data and _removeData have been replaced
+ // with direct calls to data_priv methods, these can be deprecated.
+ _data: function( elem, name, data ) {
+ return data_priv.access( elem, name, data );
+ },
+
+ _removeData: function( elem, name ) {
+ data_priv.remove( elem, name );
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var attrs, name,
+ elem = this[ 0 ],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = data_user.get( elem );
+
+ if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
+ attrs = elem.attributes;
+ for ( ; i < attrs.length; i++ ) {
+ name = attrs[ i ].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ data_priv.set( elem, "hasDataAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ data_user.set( this, key );
+ });
+ }
+
+ return jQuery.access( this, function( value ) {
+ var data,
+ camelKey = jQuery.camelCase( key );
+
+ // The calling jQuery object (element matches) is not empty
+ // (and therefore has an element appears at this[ 0 ]) and the
+ // `value` parameter was not undefined. An empty jQuery object
+ // will result in `undefined` for elem = this[ 0 ] which will
+ // throw an exception if an attempt to read a data cache is made.
+ if ( elem && value === undefined ) {
+ // Attempt to get data from the cache
+ // with the key as-is
+ data = data_user.get( elem, key );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to get data from the cache
+ // with the key camelized
+ data = data_user.get( elem, camelKey );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // Attempt to "discover" the data in
+ // HTML5 custom data-* attrs
+ data = dataAttr( elem, camelKey, undefined );
+ if ( data !== undefined ) {
+ return data;
+ }
+
+ // We tried really hard, but the data doesn't exist.
+ return;
+ }
+
+ // Set the data...
+ this.each(function() {
+ // First, attempt to store a copy or reference of any
+ // data that might've been store with a camelCased key.
+ var data = data_user.get( this, camelKey );
+
+ // For HTML5 data-* attribute interop, we have to
+ // store property names with dashes in a camelCase form.
+ // This might not apply to all properties...*
+ data_user.set( this, camelKey, value );
+
+ // *... In the case of properties that might _actually_
+ // have dashes, we need to also store a copy of that
+ // unchanged property.
+ if ( key.indexOf("-") !== -1 && data !== undefined ) {
+ data_user.set( this, key, value );
+ }
+ });
+ }, null, value, arguments.length > 1, null, true );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ data_user.remove( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ var name;
+
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+ name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? JSON.parse( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ data_user.set( elem, key, data );
+ } else {
+ data = undefined;
+ }
+ }
+ return data;
+}
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = data_priv.get( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray( data ) ) {
+ queue = data_priv.access( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ hooks.cur = fn;
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return data_priv.get( elem, key ) || data_priv.access( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ data_priv.remove( elem, [ type + "queue", key ] );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ tmp = data_priv.get( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var nodeHook, boolHook,
+ rclass = /[\t\r\n]/g,
+ rreturn = /\r/g,
+ rfocusable = /^(?:input|select|textarea|button)$/i;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ return this.each(function() {
+ delete this[ jQuery.propFix[ name ] || name ];
+ });
+ },
+
+ addClass: function( value ) {
+ var classes, elem, cur, clazz, j,
+ i = 0,
+ len = this.length,
+ proceed = typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call( this, j, this.className ) );
+ });
+ }
+
+ if ( proceed ) {
+ // The disjunction here is for better compressibility (see removeClass)
+ classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ " "
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+ cur += clazz + " ";
+ }
+ }
+ elem.className = jQuery.trim( cur );
+
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classes, elem, cur, clazz, j,
+ i = 0,
+ len = this.length,
+ proceed = arguments.length === 0 || typeof value === "string" && value;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call( this, j, this.className ) );
+ });
+ }
+ if ( proceed ) {
+ classes = ( value || "" ).match( core_rnotwhite ) || [];
+
+ for ( ; i < len; i++ ) {
+ elem = this[ i ];
+ // This expression is here for better compressibility (see addClass)
+ cur = elem.nodeType === 1 && ( elem.className ?
+ ( " " + elem.className + " " ).replace( rclass, " " ) :
+ ""
+ );
+
+ if ( cur ) {
+ j = 0;
+ while ( (clazz = classes[j++]) ) {
+ // Remove *all* instances
+ while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+ cur = cur.replace( " " + clazz + " ", " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( cur ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.match( core_rnotwhite ) || [];
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ // Toggle whole class name
+ } else if ( type === core_strundefined || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ data_priv.set( this, "__className__", this.className );
+ }
+
+ // If the element has a class name or if we're passed "false",
+ // then remove the whole classname (if there was one, the above saved it).
+ // Otherwise bring back whatever was previously saved (if anything),
+ // falling back to the empty string if nothing was stored.
+ this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val,
+ self = jQuery(this);
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // IE6-9 doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var optionSet, option,
+ options = elem.options,
+ values = jQuery.makeArray( value ),
+ i = options.length;
+
+ while ( i-- ) {
+ option = options[ i ];
+ if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
+ optionSet = true;
+ }
+ }
+
+ // force browsers to behave consistently when non-matching value is set
+ if ( !optionSet ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attr: function( elem, name, value ) {
+ var hooks, ret,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === core_strundefined ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] ||
+ ( jQuery.expr.match.boolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+
+ } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ ret = jQuery.find.attr( elem, name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret == null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var name, propName,
+ i = 0,
+ attrNames = value && value.match( core_rnotwhite );
+
+ if ( attrNames && elem.nodeType === 1 ) {
+ while ( (name = attrNames[i++]) ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // Boolean attributes get special treatment (#10870)
+ if ( jQuery.expr.match.boolean.test( name ) ) {
+ // Set corresponding property to false
+ elem[ propName ] = false;
+ }
+
+ elem.removeAttribute( name );
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to default in case type is set after value during creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ }
+ },
+
+ propFix: {
+ "for": "htmlFor",
+ "class": "className"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
+ ret :
+ ( elem[ name ] = value );
+
+ } else {
+ return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
+ ret :
+ elem[ name ];
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
+ elem.tabIndex :
+ -1;
+ }
+ }
+ }
+});
+
+// Hooks for boolean attributes
+boolHook = {
+ set: function( elem, value, name ) {
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ elem.setAttribute( name, name );
+ }
+ return name;
+ }
+};
+jQuery.each( jQuery.expr.match.boolean.source.match( /\w+/g ), function( i, name ) {
+ var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;
+
+ jQuery.expr.attrHandle[ name ] = function( elem, name, isXML ) {
+ var fn = jQuery.expr.attrHandle[ name ],
+ ret = isXML ?
+ undefined :
+ /* jshint eqeqeq: false */
+ // Temporarily disable this handler to check existence
+ (jQuery.expr.attrHandle[ name ] = undefined) !=
+ getter( elem, name, isXML ) ?
+
+ name.toLowerCase() :
+ null;
+
+ // Restore handler
+ jQuery.expr.attrHandle[ name ] = fn;
+
+ return ret;
+ };
+});
+
+// Support: IE9+
+// Selectedness for an option in an optgroup can be inaccurate
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+ if ( parent && parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ return null;
+ }
+ };
+}
+
+jQuery.each([
+ "tabIndex",
+ "readOnly",
+ "maxLength",
+ "cellSpacing",
+ "cellPadding",
+ "rowSpan",
+ "colSpan",
+ "useMap",
+ "frameBorder",
+ "contentEditable"
+], function() {
+ jQuery.propFix[ this.toLowerCase() ] = this;
+});
+
+// Radios and checkboxes getter/setter
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ };
+ if ( !jQuery.support.checkOn ) {
+ jQuery.valHooks[ this ].get = function( elem ) {
+ // Support: Webkit
+ // "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ };
+ }
+});
+var rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+ return true;
+}
+
+function returnFalse() {
+ return false;
+}
+
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ global: {},
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var handleObjIn, eventHandle, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.get( elem );
+
+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ if ( !elemData ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ if ( !(events = elemData.events) ) {
+ events = elemData.events = {};
+ }
+ if ( !(eventHandle = elemData.handle) ) {
+ eventHandle = elemData.handle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ types = ( types || "" ).match( core_rnotwhite ) || [""];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: origType,
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ if ( !(handlers = events[ type ]) ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var j, origCount, tmp,
+ events, t, handleObj,
+ special, handlers, type, namespaces, origType,
+ elemData = data_priv.hasData( elem ) && data_priv.get( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = ( types || "" ).match( core_rnotwhite ) || [""];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+ // Remove matching events
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
+
+ if ( handleObj.selector ) {
+ handlers.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( origCount && !handlers.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+ data_priv.remove( elem, "events" );
+ }
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+
+ var i, cur, tmp, bubbleType, ontype, handle, special,
+ eventPath = [ elem || document ],
+ type = core_hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
+
+ cur = tmp = elem = elem || document;
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf(".") >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+ ontype = type.indexOf(":") < 0 && "on" + type;
+
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
+
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+ null;
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( tmp === (elem.ownerDocument || document) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+ }
+ }
+
+ // Fire handlers on the event path
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+
+ // jQuery handler
+ handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Native handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+ jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ tmp = elem[ ontype ];
+
+ if ( tmp ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event );
+
+ var i, j, ret, matched, handleObj,
+ handlerQueue = [],
+ args = core_slice.call( arguments ),
+ handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+ // Triggered event must either 1) have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var i, matches, sel, handleObj,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ // Black-hole SVG