diff --git a/LICENCE.html b/LICENCE.html index 813c07d..f6e0b18 100644 --- a/LICENCE.html +++ b/LICENCE.html @@ -1,261 +1,525 @@ - - - - - - -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.

+ + + + +======= + + + + + + +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.

+ + + + +>>>>>>> 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 instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: Safari 6.0+, Chrome < 28 + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } +}; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && e.preventDefault ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && e.stopPropagation ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +// Support: Chrome 15+ +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// Create "bubbling" focus and blur events +// Support: Firefox, Chrome, Safari +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +var isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self, matched, i, + l = this.length; + + if ( typeof selector !== "string" ) { + self = this; + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + matched = []; + for ( i = 0; i < l; i++ ) { + jQuery.find( selector, this[ i ], matched ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + matched = this.pushStack( l > 1 ? jQuery.unique( matched ) : matched ); + matched.selector = ( this.selector ? this.selector + " " : "" ) + selector; + return matched; + }, + + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter(function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[ 0 ] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = ( rneedsContext.test( selectors ) || typeof selectors !== "string" ) ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + cur = matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return core_indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return core_indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.unique( matched ); + } + + // Reverse order for parents* and prev* + if ( name[ 0 ] === "p" ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); + }, + + dir: function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }, + + sibling: function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( isSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( core_indexOf.call( qualifier, elem ) >= 0 ) !== not; + }); +} +var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + manipulation_rcheckableType = /^(?:checkbox|radio)$/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + + // Support: IE 9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] + }; + +// Support: IE 9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.col = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var + // Snapshot the DOM in case .domManip sweeps something relevant into its fragment + args = jQuery.map( this, function( elem ) { + return [ elem.nextSibling, elem.parentNode ]; + }), + i = 0; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + var next = args[ i++ ], + parent = args[ i++ ]; + + if ( parent ) { + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + // Allow new content to include elements from the context set + }, true ); + + // Force removal if there was no new content (e.g., from empty arguments) + return i ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback, allowIntersection ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + self.domManip( args, callback, allowIntersection ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery._evalUrl( node.src ); + } else { + jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + } + } + } + } + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: QtWebKit + // .get() because core_push.apply(_, arraylike) throws + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Support: IE >= 9 + // Fix Cloning issues + if ( !jQuery.support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var elem, tmp, tag, wrap, contains, j, + i = 0, + l = elems.length, + fragment = context.createDocumentFragment(), + nodes = []; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.firstChild; + } + + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Fixes #12346 + // Support: Webkit, IE + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + }, + + cleanData: function( elems ) { + var data, elem, type, + l = elems.length, + i = 0, + special = jQuery.event.special; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( jQuery.acceptData( elem ) ) { + + data = data_priv.access( elem ); + + if ( data ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + } + // Discard any remaining `private` and `user` data + // One day we'll replace the dual arrays with a WeakMap and this won't be an issue. + // (Splices the data objects out of the internal cache arrays) + data_user.discard( elem ); + data_priv.discard( elem ); + } + }, + + _evalUrl: function( url ) { + return jQuery.ajax({ + url: url, + type: "GET", + dataType: "text", + async: false, + global: false, + success: jQuery.globalEval + }); + } +}); + +// Support: 1.x compatibility +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute("type"); + } + + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var l = elems.length, + i = 0; + + for ( ; i < l; i++ ) { + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); + } +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = jQuery.extend( {}, pdataOld ); + events = pdataOld.events; + + data_priv.set( dest, pdataCur ); + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); + } +} + + +function getAll( context, tag ) { + var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : + context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +// Support: IE >= 9 +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} +jQuery.fn.extend({ + wrapAll: function( html ) { + var wrap; + + if ( jQuery.isFunction( html ) ) { + return this.each(function( i ) { + jQuery( this ).wrapAll( html.call(this, i) ); + }); + } + + if ( this[ 0 ] ) { + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function( i ) { + jQuery( this ).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function( i ) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + } +}); +var curCSS, iframe, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +function getStyles( elem ) { + return window.getComputedStyle( elem, null ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = data_priv.get( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = data_priv.access( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css(elem, "display") ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": "cssFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifying setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + style[ name ] = value; + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + } +}); + +curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // Support: IE9 + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // Support: Safari 5.1 + // A tribute to the "awesome hack by Dean Edwards" + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; +}; + + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("