├── .Rbuildignore ├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── FileSaver_dependency.R ├── fabric_curtail.R ├── fabric_dependency.R ├── fabric_drawing.R ├── fabric_image.R ├── fabric_image_add.R ├── fabric_shape.R ├── fabric_shape_add.R ├── fabric_text.R ├── fabric_text_add.R └── jQuery_dependency.R ├── README.Rmd ├── README.md ├── cran-comments.md ├── fabricerin.Rproj ├── inst └── fabric │ ├── FileSaver.min.js │ ├── fabric.min.js │ └── jquery-3.5.1.min.js └── man ├── fabric_curtail.Rd ├── fabric_drawing.Rd ├── fabric_image.Rd ├── fabric_image_add.Rd ├── fabric_shape.Rd ├── fabric_shape_add.Rd ├── fabric_text.Rd ├── fabric_text_add.Rd └── figures ├── example1.gif ├── example3.gif ├── example4.png ├── example5.gif ├── example6.gif ├── example7.png ├── example8.png ├── gumexample.gif ├── r_dplyr_logo.png └── t_example.gif /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^CODE_OF_CONDUCT\.md$ 6 | ^cran-comments\.md$ 7 | ^CRAN-RELEASE$ 8 | ^\.travis\.yml$ 9 | ^man/figures/t_example\.gif$ 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.buymeacoffee.com/Fodil'] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | cache: packages 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards 42 | of acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies 54 | when an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail 56 | address, posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at [INSERT CONTACT 63 | METHOD]. All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.0, 118 | available at https://www.contributor-covenant.org/version/2/0/ 119 | code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at https:// 128 | www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: fabricerin 2 | Title: Create Easily Canvas in 'shiny' and 'RMarkdown' Documents 3 | Version: 0.1.2 4 | Authors@R: c( 5 | person("Mohamed El Fodil", "Ihaddaden", email = "ihaddaden.fodeil@gmail.com", role = c("aut", "cre")), 6 | person("Garrick", "Aden-Buie", role = c("ctb")), 7 | person("fabricjs contributors", role = c("ctb", "cph"), comment = "fabricjs JavaScript library"), 8 | person("jQuery contributors", role = c("ctb", "cph"), comment = "jQuery JavaScript library"), 9 | person("FileSaver.js contributors", role = c("ctb", "cph"), comment = "FileSaver JavaScript library")) 10 | Description: Allows the user to implement easily canvas elements within a 'shiny' app or an 'RMarkdown' document. 11 | The user can create shapes, images and text elements within the canvas which can also be used as a drawing tool for taking notes. 12 | The package relies on the 'fabricjs' 'JavaScript' library. See . 13 | License: AGPL (>= 3) 14 | Encoding: UTF-8 15 | LazyData: true 16 | Roxygen: list(markdown = TRUE) 17 | RoxygenNote: 7.1.1 18 | Imports: 19 | htmltools, 20 | glue 21 | URL: https://github.com/feddelegrand7/fabricerin 22 | BugReports: https://github.com/feddelegrand7/fabricerin/issues 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2020 2 | COPYRIGHT HOLDER: Mohamed El Fodil Ihaddaden, fabricjs contributors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU Affero General Public License 2 | ================================= 3 | 4 | _Version 3, 19 November 2007_ 5 | _Copyright (C) 2007 Free Software Foundation, Inc. <>_ 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this 8 | license document, but changing it is not allowed. 9 | 10 | ## Preamble 11 | 12 | The GNU Affero General Public License is a free, copyleft license for 13 | software and other kinds of works, specifically designed to ensure 14 | cooperation with the community in the case of network server software. 15 | 16 | The licenses for most software and other practical works are designed 17 | to take away your freedom to share and change the works. By contrast, 18 | our General Public Licenses are intended to guarantee your freedom to 19 | share and change all versions of a program--to make sure it remains 20 | free software for all its users. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | Developers that use our General Public Licenses protect your rights 30 | with two steps: (1) assert copyright on the software, and (2) offer 31 | you this License which gives you legal permission to copy, distribute 32 | and/or modify the software. 33 | 34 | A secondary benefit of defending all users' freedom is that 35 | improvements made in alternate versions of the program, if they 36 | receive widespread use, become available for other developers to 37 | incorporate. Many developers of free software are heartened and 38 | encouraged by the resulting cooperation. However, in the case of 39 | software used on network servers, this result may fail to come about. 40 | The GNU General Public License permits making a modified version and 41 | letting the public access it on a server without ever releasing its 42 | source code to the public. 43 | 44 | The GNU Affero General Public License is designed specifically to 45 | ensure that, in such cases, the modified source code becomes available 46 | to the community. It requires the operator of a network server to 47 | provide the source code of the modified version running there to the 48 | users of that server. Therefore, public use of a modified version, on 49 | a publicly accessible server, gives the public access to the source 50 | code of the modified version. 51 | 52 | An older license, called the Affero General Public License and 53 | published by Affero, was designed to accomplish similar goals. This is 54 | a different license, not a version of the Affero GPL, but Affero has 55 | released a new version of the Affero GPL which permits relicensing 56 | under this license. 57 | 58 | The precise terms and conditions for copying, distribution and 59 | modification follow. 60 | 61 | ## TERMS AND CONDITIONS 62 | 63 | ### 0. Definitions. 64 | 65 | "This License" refers to version 3 of the GNU Affero General Public 66 | License. 67 | 68 | "Copyright" also means copyright-like laws that apply to other kinds 69 | of works, such as semiconductor masks. 70 | 71 | "The Program" refers to any copyrightable work licensed under this 72 | License. Each licensee is addressed as "you". "Licensees" and 73 | "recipients" may be individuals or organizations. 74 | 75 | To "modify" a work means to copy from or adapt all or part of the work 76 | in a fashion requiring copyright permission, other than the making of 77 | an exact copy. The resulting work is called a "modified version" of 78 | the earlier work or a work "based on" the earlier work. 79 | 80 | A "covered work" means either the unmodified Program or a work based 81 | on the Program. 82 | 83 | To "propagate" a work means to do anything with it that, without 84 | permission, would make you directly or secondarily liable for 85 | infringement under applicable copyright law, except executing it on a 86 | computer or modifying a private copy. Propagation includes copying, 87 | distribution (with or without modification), making available to the 88 | public, and in some countries other activities as well. 89 | 90 | To "convey" a work means any kind of propagation that enables other 91 | parties to make or receive copies. Mere interaction with a user 92 | through a computer network, with no transfer of a copy, is not 93 | conveying. 94 | 95 | An interactive user interface displays "Appropriate Legal Notices" to 96 | the extent that it includes a convenient and prominently visible 97 | feature that (1) displays an appropriate copyright notice, and (2) 98 | tells the user that there is no warranty for the work (except to the 99 | extent that warranties are provided), that licensees may convey the 100 | work under this License, and how to view a copy of this License. If 101 | the interface presents a list of user commands or options, such as a 102 | menu, a prominent item in the list meets this criterion. 103 | 104 | ### 1. Source Code. 105 | 106 | The "source code" for a work means the preferred form of the work for 107 | making modifications to it. "Object code" means any non-source form of 108 | a work. 109 | 110 | A "Standard Interface" means an interface that either is an official 111 | standard defined by a recognized standards body, or, in the case of 112 | interfaces specified for a particular programming language, one that 113 | is widely used among developers working in that language. 114 | 115 | The "System Libraries" of an executable work include anything, other 116 | than the work as a whole, that (a) is included in the normal form of 117 | packaging a Major Component, but which is not part of that Major 118 | Component, and (b) serves only to enable use of the work with that 119 | Major Component, or to implement a Standard Interface for which an 120 | implementation is available to the public in source code form. A 121 | "Major Component", in this context, means a major essential component 122 | (kernel, window system, and so on) of the specific operating system 123 | (if any) on which the executable work runs, or a compiler used to 124 | produce the work, or an object code interpreter used to run it. 125 | 126 | The "Corresponding Source" for a work in object code form means all 127 | the source code needed to generate, install, and (for an executable 128 | work) run the object code and to modify the work, including scripts to 129 | control those activities. However, it does not include the work's 130 | System Libraries, or general-purpose tools or generally available free 131 | programs which are used unmodified in performing those activities but 132 | which are not part of the work. For example, Corresponding Source 133 | includes interface definition files associated with source files for 134 | the work, and the source code for shared libraries and dynamically 135 | linked subprograms that the work is specifically designed to require, 136 | such as by intimate data communication or control flow between those 137 | subprograms and other parts of the work. 138 | 139 | The Corresponding Source need not include anything that users can 140 | regenerate automatically from other parts of the Corresponding Source. 141 | 142 | The Corresponding Source for a work in source code form is that same 143 | work. 144 | 145 | ### 2. Basic Permissions. 146 | 147 | All rights granted under this License are granted for the term of 148 | copyright on the Program, and are irrevocable provided the stated 149 | conditions are met. This License explicitly affirms your unlimited 150 | permission to run the unmodified Program. The output from running a 151 | covered work is covered by this License only if the output, given its 152 | content, constitutes a covered work. This License acknowledges your 153 | rights of fair use or other equivalent, as provided by copyright law. 154 | 155 | You may make, run and propagate covered works that you do not convey, 156 | without conditions so long as your license otherwise remains in force. 157 | You may convey covered works to others for the sole purpose of having 158 | them make modifications exclusively for you, or provide you with 159 | facilities for running those works, provided that you comply with the 160 | terms of this License in conveying all material for which you do not 161 | control copyright. Those thus making or running the covered works for 162 | you must do so exclusively on your behalf, under your direction and 163 | control, on terms that prohibit them from making any copies of your 164 | copyrighted material outside their relationship with you. 165 | 166 | Conveying under any other circumstances is permitted solely under the 167 | conditions stated below. Sublicensing is not allowed; section 10 makes 168 | it unnecessary. 169 | 170 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 171 | 172 | No covered work shall be deemed part of an effective technological 173 | measure under any applicable law fulfilling obligations under article 174 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 175 | similar laws prohibiting or restricting circumvention of such 176 | measures. 177 | 178 | When you convey a covered work, you waive any legal power to forbid 179 | circumvention of technological measures to the extent such 180 | circumvention is effected by exercising rights under this License with 181 | respect to the covered work, and you disclaim any intention to limit 182 | operation or modification of the work as a means of enforcing, against 183 | the work's users, your or third parties' legal rights to forbid 184 | circumvention of technological measures. 185 | 186 | ### 4. Conveying Verbatim Copies. 187 | 188 | You may convey verbatim copies of the Program's source code as you 189 | receive it, in any medium, provided that you conspicuously and 190 | appropriately publish on each copy an appropriate copyright notice; 191 | keep intact all notices stating that this License and any 192 | non-permissive terms added in accord with section 7 apply to the code; 193 | keep intact all notices of the absence of any warranty; and give all 194 | recipients a copy of this License along with the Program. 195 | 196 | You may charge any price or no price for each copy that you convey, 197 | and you may offer support or warranty protection for a fee. 198 | 199 | ### 5. Conveying Modified Source Versions. 200 | 201 | You may convey a work based on the Program, or the modifications to 202 | produce it from the Program, in the form of source code under the 203 | terms of section 4, provided that you also meet all of these 204 | conditions: 205 | 206 | - a) The work must carry prominent notices stating that you modified 207 | it, and giving a relevant date. 208 | - b) The work must carry prominent notices stating that it is 209 | released under this License and any conditions added under 210 | section 7. This requirement modifies the requirement in section 4 211 | to "keep intact all notices". 212 | - c) You must license the entire work, as a whole, under this 213 | License to anyone who comes into possession of a copy. This 214 | License will therefore apply, along with any applicable section 7 215 | additional terms, to the whole of the work, and all its parts, 216 | regardless of how they are packaged. This License gives no 217 | permission to license the work in any other way, but it does not 218 | invalidate such permission if you have separately received it. 219 | - d) If the work has interactive user interfaces, each must display 220 | Appropriate Legal Notices; however, if the Program has interactive 221 | interfaces that do not display Appropriate Legal Notices, your 222 | work need not make them do so. 223 | 224 | A compilation of a covered work with other separate and independent 225 | works, which are not by their nature extensions of the covered work, 226 | and which are not combined with it such as to form a larger program, 227 | in or on a volume of a storage or distribution medium, is called an 228 | "aggregate" if the compilation and its resulting copyright are not 229 | used to limit the access or legal rights of the compilation's users 230 | beyond what the individual works permit. Inclusion of a covered work 231 | in an aggregate does not cause this License to apply to the other 232 | parts of the aggregate. 233 | 234 | ### 6. Conveying Non-Source Forms. 235 | 236 | You may convey a covered work in object code form under the terms of 237 | sections 4 and 5, provided that you also convey the machine-readable 238 | Corresponding Source under the terms of this License, in one of these 239 | ways: 240 | 241 | - a) Convey the object code in, or embodied in, a physical product 242 | (including a physical distribution medium), accompanied by the 243 | Corresponding Source fixed on a durable physical medium 244 | customarily used for software interchange. 245 | - b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the Corresponding 255 | Source from a network server at no charge. 256 | - c) Convey individual copies of the object code with a copy of the 257 | written offer to provide the Corresponding Source. This 258 | alternative is allowed only occasionally and noncommercially, and 259 | only if you received the object code with such an offer, in accord 260 | with subsection 6b. 261 | - d) Convey the object code by offering access from a designated 262 | place (gratis or for a charge), and offer equivalent access to the 263 | Corresponding Source in the same way through the same place at no 264 | further charge. You need not require recipients to copy the 265 | Corresponding Source along with the object code. If the place to 266 | copy the object code is a network server, the Corresponding Source 267 | may be on a different server (operated by you or a third party) 268 | that supports equivalent copying facilities, provided you maintain 269 | clear directions next to the object code saying where to find the 270 | Corresponding Source. Regardless of what server hosts the 271 | Corresponding Source, you remain obligated to ensure that it is 272 | available for as long as needed to satisfy these requirements. 273 | - e) Convey the object code using peer-to-peer transmission, 274 | provided you inform other peers where the object code and 275 | Corresponding Source of the work are being offered to the general 276 | public at no charge under subsection 6d. 277 | 278 | A separable portion of the object code, whose source code is excluded 279 | from the Corresponding Source as a System Library, need not be 280 | included in conveying the object code work. 281 | 282 | A "User Product" is either (1) a "consumer product", which means any 283 | tangible personal property which is normally used for personal, 284 | family, or household purposes, or (2) anything designed or sold for 285 | incorporation into a dwelling. In determining whether a product is a 286 | consumer product, doubtful cases shall be resolved in favor of 287 | coverage. For a particular product received by a particular user, 288 | "normally used" refers to a typical or common use of that class of 289 | product, regardless of the status of the particular user or of the way 290 | in which the particular user actually uses, or expects or is expected 291 | to use, the product. A product is a consumer product regardless of 292 | whether the product has substantial commercial, industrial or 293 | non-consumer uses, unless such uses represent the only significant 294 | mode of use of the product. 295 | 296 | "Installation Information" for a User Product means any methods, 297 | procedures, authorization keys, or other information required to 298 | install and execute modified versions of a covered work in that User 299 | Product from a modified version of its Corresponding Source. The 300 | information must suffice to ensure that the continued functioning of 301 | the modified object code is in no case prevented or interfered with 302 | solely because modification has been made. 303 | 304 | If you convey an object code work under this section in, or with, or 305 | specifically for use in, a User Product, and the conveying occurs as 306 | part of a transaction in which the right of possession and use of the 307 | User Product is transferred to the recipient in perpetuity or for a 308 | fixed term (regardless of how the transaction is characterized), the 309 | Corresponding Source conveyed under this section must be accompanied 310 | by the Installation Information. But this requirement does not apply 311 | if neither you nor any third party retains the ability to install 312 | modified object code on the User Product (for example, the work has 313 | been installed in ROM). 314 | 315 | The requirement to provide Installation Information does not include a 316 | requirement to continue to provide support service, warranty, or 317 | updates for a work that has been modified or installed by the 318 | recipient, or for the User Product in which it has been modified or 319 | installed. Access to a network may be denied when the modification 320 | itself materially and adversely affects the operation of the network 321 | or violates the rules and protocols for communication across the 322 | network. 323 | 324 | Corresponding Source conveyed, and Installation Information provided, 325 | in accord with this section must be in a format that is publicly 326 | documented (and with an implementation available to the public in 327 | source code form), and must require no special password or key for 328 | unpacking, reading or copying. 329 | 330 | ### 7. Additional Terms. 331 | 332 | "Additional permissions" are terms that supplement the terms of this 333 | License by making exceptions from one or more of its conditions. 334 | Additional permissions that are applicable to the entire Program shall 335 | be treated as though they were included in this License, to the extent 336 | that they are valid under applicable law. If additional permissions 337 | apply only to part of the Program, that part may be used separately 338 | under those permissions, but the entire Program remains governed by 339 | this License without regard to the additional permissions. 340 | 341 | When you convey a copy of a covered work, you may at your option 342 | remove any additional permissions from that copy, or from any part of 343 | it. (Additional permissions may be written to require their own 344 | removal in certain cases when you modify the work.) You may place 345 | additional permissions on material, added by you to a covered work, 346 | for which you have or can give appropriate copyright permission. 347 | 348 | Notwithstanding any other provision of this License, for material you 349 | add to a covered work, you may (if authorized by the copyright holders 350 | of that material) supplement the terms of this License with terms: 351 | 352 | - a) Disclaiming warranty or limiting liability differently from the 353 | terms of sections 15 and 16 of this License; or 354 | - b) Requiring preservation of specified reasonable legal notices or 355 | author attributions in that material or in the Appropriate Legal 356 | Notices displayed by works containing it; or 357 | - c) Prohibiting misrepresentation of the origin of that material, 358 | or requiring that modified versions of such material be marked in 359 | reasonable ways as different from the original version; or 360 | - d) Limiting the use for publicity purposes of names of licensors 361 | or authors of the material; or 362 | - e) Declining to grant rights under trademark law for use of some 363 | trade names, trademarks, or service marks; or 364 | - f) Requiring indemnification of licensors and authors of that 365 | material by anyone who conveys the material (or modified versions 366 | of it) with contractual assumptions of liability to the recipient, 367 | for any liability that these contractual assumptions directly 368 | impose on those licensors and authors. 369 | 370 | All other non-permissive additional terms are considered "further 371 | restrictions" within the meaning of section 10. If the Program as you 372 | received it, or any part of it, contains a notice stating that it is 373 | governed by this License along with a term that is a further 374 | restriction, you may remove that term. If a license document contains 375 | a further restriction but permits relicensing or conveying under this 376 | License, you may add to a covered work material governed by the terms 377 | of that license document, provided that the further restriction does 378 | not survive such relicensing or conveying. 379 | 380 | If you add terms to a covered work in accord with this section, you 381 | must place, in the relevant source files, a statement of the 382 | additional terms that apply to those files, or a notice indicating 383 | where to find the applicable terms. 384 | 385 | Additional terms, permissive or non-permissive, may be stated in the 386 | form of a separately written license, or stated as exceptions; the 387 | above requirements apply either way. 388 | 389 | ### 8. Termination. 390 | 391 | You may not propagate or modify a covered work except as expressly 392 | provided under this License. Any attempt otherwise to propagate or 393 | modify it is void, and will automatically terminate your rights under 394 | this License (including any patent licenses granted under the third 395 | paragraph of section 11). 396 | 397 | However, if you cease all violation of this License, then your license 398 | from a particular copyright holder is reinstated (a) provisionally, 399 | unless and until the copyright holder explicitly and finally 400 | terminates your license, and (b) permanently, if the copyright holder 401 | fails to notify you of the violation by some reasonable means prior to 402 | 60 days after the cessation. 403 | 404 | Moreover, your license from a particular copyright holder is 405 | reinstated permanently if the copyright holder notifies you of the 406 | violation by some reasonable means, this is the first time you have 407 | received notice of violation of this License (for any work) from that 408 | copyright holder, and you cure the violation prior to 30 days after 409 | your receipt of the notice. 410 | 411 | Termination of your rights under this section does not terminate the 412 | licenses of parties who have received copies or rights from you under 413 | this License. If your rights have been terminated and not permanently 414 | reinstated, you do not qualify to receive new licenses for the same 415 | material under section 10. 416 | 417 | ### 9. Acceptance Not Required for Having Copies. 418 | 419 | You are not required to accept this License in order to receive or run 420 | a copy of the Program. Ancillary propagation of a covered work 421 | occurring solely as a consequence of using peer-to-peer transmission 422 | to receive a copy likewise does not require acceptance. However, 423 | nothing other than this License grants you permission to propagate or 424 | modify any covered work. These actions infringe copyright if you do 425 | not accept this License. Therefore, by modifying or propagating a 426 | covered work, you indicate your acceptance of this License to do so. 427 | 428 | ### 10. Automatic Licensing of Downstream Recipients. 429 | 430 | Each time you convey a covered work, the recipient automatically 431 | receives a license from the original licensors, to run, modify and 432 | propagate that work, subject to this License. You are not responsible 433 | for enforcing compliance by third parties with this License. 434 | 435 | An "entity transaction" is a transaction transferring control of an 436 | organization, or substantially all assets of one, or subdividing an 437 | organization, or merging organizations. If propagation of a covered 438 | work results from an entity transaction, each party to that 439 | transaction who receives a copy of the work also receives whatever 440 | licenses to the work the party's predecessor in interest had or could 441 | give under the previous paragraph, plus a right to possession of the 442 | Corresponding Source of the work from the predecessor in interest, if 443 | the predecessor has it or can get it with reasonable efforts. 444 | 445 | You may not impose any further restrictions on the exercise of the 446 | rights granted or affirmed under this License. For example, you may 447 | not impose a license fee, royalty, or other charge for exercise of 448 | rights granted under this License, and you may not initiate litigation 449 | (including a cross-claim or counterclaim in a lawsuit) alleging that 450 | any patent claim is infringed by making, using, selling, offering for 451 | sale, or importing the Program or any portion of it. 452 | 453 | ### 11. Patents. 454 | 455 | A "contributor" is a copyright holder who authorizes use under this 456 | License of the Program or a work on which the Program is based. The 457 | work thus licensed is called the contributor's "contributor version". 458 | 459 | A contributor's "essential patent claims" are all patent claims owned 460 | or controlled by the contributor, whether already acquired or 461 | hereafter acquired, that would be infringed by some manner, permitted 462 | by this License, of making, using, or selling its contributor version, 463 | but do not include claims that would be infringed only as a 464 | consequence of further modification of the contributor version. For 465 | purposes of this definition, "control" includes the right to grant 466 | patent sublicenses in a manner consistent with the requirements of 467 | this License. 468 | 469 | Each contributor grants you a non-exclusive, worldwide, royalty-free 470 | patent license under the contributor's essential patent claims, to 471 | make, use, sell, offer for sale, import and otherwise run, modify and 472 | propagate the contents of its contributor version. 473 | 474 | In the following three paragraphs, a "patent license" is any express 475 | agreement or commitment, however denominated, not to enforce a patent 476 | (such as an express permission to practice a patent or covenant not to 477 | sue for patent infringement). To "grant" such a patent license to a 478 | party means to make such an agreement or commitment not to enforce a 479 | patent against the party. 480 | 481 | If you convey a covered work, knowingly relying on a patent license, 482 | and the Corresponding Source of the work is not available for anyone 483 | to copy, free of charge and under the terms of this License, through a 484 | publicly available network server or other readily accessible means, 485 | then you must either (1) cause the Corresponding Source to be so 486 | available, or (2) arrange to deprive yourself of the benefit of the 487 | patent license for this particular work, or (3) arrange, in a manner 488 | consistent with the requirements of this License, to extend the patent 489 | license to downstream recipients. "Knowingly relying" means you have 490 | actual knowledge that, but for the patent license, your conveying the 491 | covered work in a country, or your recipient's use of the covered work 492 | in a country, would infringe one or more identifiable patents in that 493 | country that you have reason to believe are valid. 494 | 495 | If, pursuant to or in connection with a single transaction or 496 | arrangement, you convey, or propagate by procuring conveyance of, a 497 | covered work, and grant a patent license to some of the parties 498 | receiving the covered work authorizing them to use, propagate, modify 499 | or convey a specific copy of the covered work, then the patent license 500 | you grant is automatically extended to all recipients of the covered 501 | work and works based on it. 502 | 503 | A patent license is "discriminatory" if it does not include within the 504 | scope of its coverage, prohibits the exercise of, or is conditioned on 505 | the non-exercise of one or more of the rights that are specifically 506 | granted under this License. You may not convey a covered work if you 507 | are a party to an arrangement with a third party that is in the 508 | business of distributing software, under which you make payment to the 509 | third party based on the extent of your activity of conveying the 510 | work, and under which the third party grants, to any of the parties 511 | who would receive the covered work from you, a discriminatory patent 512 | license (a) in connection with copies of the covered work conveyed by 513 | you (or copies made from those copies), or (b) primarily for and in 514 | connection with specific products or compilations that contain the 515 | covered work, unless you entered into that arrangement, or that patent 516 | license was granted, prior to 28 March 2007. 517 | 518 | Nothing in this License shall be construed as excluding or limiting 519 | any implied license or other defenses to infringement that may 520 | otherwise be available to you under applicable patent law. 521 | 522 | ### 12. No Surrender of Others' Freedom. 523 | 524 | If conditions are imposed on you (whether by court order, agreement or 525 | otherwise) that contradict the conditions of this License, they do not 526 | excuse you from the conditions of this License. If you cannot convey a 527 | covered work so as to satisfy simultaneously your obligations under 528 | this License and any other pertinent obligations, then as a 529 | consequence you may not convey it at all. For example, if you agree to 530 | terms that obligate you to collect a royalty for further conveying 531 | from those to whom you convey the Program, the only way you could 532 | satisfy both those terms and this License would be to refrain entirely 533 | from conveying the Program. 534 | 535 | ### 13. Remote Network Interaction; Use with the GNU General Public License. 536 | 537 | Notwithstanding any other provision of this License, if you modify the 538 | Program, your modified version must prominently offer all users 539 | interacting with it remotely through a computer network (if your 540 | version supports such interaction) an opportunity to receive the 541 | Corresponding Source of your version by providing access to the 542 | Corresponding Source from a network server at no charge, through some 543 | standard or customary means of facilitating copying of software. This 544 | Corresponding Source shall include the Corresponding Source for any 545 | work covered by version 3 of the GNU General Public License that is 546 | incorporated pursuant to the following paragraph. 547 | 548 | Notwithstanding any other provision of this License, you have 549 | permission to link or combine any covered work with a work licensed 550 | under version 3 of the GNU General Public License into a single 551 | combined work, and to convey the resulting work. The terms of this 552 | License will continue to apply to the part which is the covered work, 553 | but the work with which it is combined will remain governed by version 554 | 3 of the GNU General Public License. 555 | 556 | ### 14. Revised Versions of this License. 557 | 558 | The Free Software Foundation may publish revised and/or new versions 559 | of the GNU Affero General Public License from time to time. Such new 560 | versions will be similar in spirit to the present version, but may 561 | differ in detail to address new problems or concerns. 562 | 563 | Each version is given a distinguishing version number. If the Program 564 | specifies that a certain numbered version of the GNU Affero General 565 | Public License "or any later version" applies to it, you have the 566 | option of following the terms and conditions either of that numbered 567 | version or of any later version published by the Free Software 568 | Foundation. If the Program does not specify a version number of the 569 | GNU Affero General Public License, you may choose any version ever 570 | published by the Free Software Foundation. 571 | 572 | If the Program specifies that a proxy can decide which future versions 573 | of the GNU Affero General Public License can be used, that proxy's 574 | public statement of acceptance of a version permanently authorizes you 575 | to choose that version for the Program. 576 | 577 | Later license versions may give you additional or different 578 | permissions. However, no additional obligations are imposed on any 579 | author or copyright holder as a result of your choosing to follow a 580 | later version. 581 | 582 | ### 15. Disclaimer of Warranty. 583 | 584 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 585 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 586 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT 587 | WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT 588 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 589 | A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND 590 | PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE 591 | DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR 592 | CORRECTION. 593 | 594 | ### 16. Limitation of Liability. 595 | 596 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 597 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR 598 | CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 599 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES 600 | ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT 601 | NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR 602 | LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM 603 | TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER 604 | PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 605 | 606 | ### 17. Interpretation of Sections 15 and 16. 607 | 608 | If the disclaimer of warranty and limitation of liability provided 609 | above cannot be given local legal effect according to their terms, 610 | reviewing courts shall apply local law that most closely approximates 611 | an absolute waiver of all civil liability in connection with the 612 | Program, unless a warranty or assumption of liability accompanies a 613 | copy of the Program in return for a fee. 614 | 615 | END OF TERMS AND CONDITIONS 616 | 617 | ## How to Apply These Terms to Your New Programs 618 | 619 | If you develop a new program, and you want it to be of the greatest 620 | possible use to the public, the best way to achieve this is to make it 621 | free software which everyone can redistribute and change under these 622 | terms. 623 | 624 | To do so, attach the following notices to the program. It is safest to 625 | attach them to the start of each source file to most effectively state 626 | the exclusion of warranty; and each file should have at least the 627 | "copyright" line and a pointer to where the full notice is found. 628 | 629 | 630 | Copyright (C) 631 | 632 | This program is free software: you can redistribute it and/or modify 633 | it under the terms of the GNU Affero General Public License as 634 | published by the Free Software Foundation, either version 3 of the 635 | License, or (at your option) any later version. 636 | 637 | This program is distributed in the hope that it will be useful, 638 | but WITHOUT ANY WARRANTY; without even the implied warranty of 639 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 640 | GNU Affero General Public License for more details. 641 | 642 | You should have received a copy of the GNU Affero General Public License 643 | along with this program. If not, see . 644 | 645 | Also add information on how to contact you by electronic and paper 646 | mail. 647 | 648 | If your software can interact with users remotely through a computer 649 | network, you should also make sure that it provides a way for users to 650 | get its source. For example, if your program is a web application, its 651 | interface could display a "Source" link that leads users to an archive 652 | of the code. There are many ways you could offer source, and different 653 | solutions will be better for different programs; see section 13 for 654 | the specific requirements. 655 | 656 | You should also get your employer (if you work as a programmer) or 657 | school, if any, to sign a "copyright disclaimer" for the program, if 658 | necessary. For more information on this, and how to apply and follow 659 | the GNU AGPL, see . 660 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(fabric_curtail) 4 | export(fabric_drawing) 5 | export(fabric_image) 6 | export(fabric_image_add) 7 | export(fabric_shape) 8 | export(fabric_shape_add) 9 | export(fabric_text) 10 | export(fabric_text_add) 11 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # fabricerin 0.1.2 2 | 3 | * All the javascript libraries are implemented using the htmlDependency() function which means that the user can use the package without being connected to the internet. 4 | 5 | * The user has the possibility to use the package within xaringan slides. 6 | 7 | * The user can now export canvas that contain external images. 8 | -------------------------------------------------------------------------------- /R/FileSaver_dependency.R: -------------------------------------------------------------------------------- 1 | 2 | #' Create an HTML dependency for FileSaver.js 3 | #' 4 | #' @rdname html-dependencies 5 | #' @noRd 6 | #' 7 | #' 8 | 9 | 10 | 11 | filesaver_dependency <- function() { 12 | htmltools::htmlDependency( 13 | name = "FileSaver", 14 | version = "2.0.2", 15 | package = "fabricerin", 16 | src = c( 17 | file = "fabric", 18 | href = "https://cdn.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js" 19 | ), 20 | script = "FileSaver.min.js", 21 | all_files = FALSE 22 | ) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /R/fabric_curtail.R: -------------------------------------------------------------------------------- 1 | 2 | #' Add a background or an overlay image to a preexisting canvas 3 | #' 4 | #' @param cid the id of the canvas element 5 | #' @param imgsrc the URL source of the image 6 | #' @param type whether to use an image as a 'background' or as an 'overlay' 7 | #' 8 | #' @return a canvas with a background or overlay image 9 | #' @export 10 | #' 11 | #' @examples 12 | #' 13 | #' if (interactive()) { 14 | #' 15 | #' img <- "https://st.depositphotos.com/1642482/1904/i/950/depositphotos_19049237-stock-photo-leaf.jpg" 16 | #' 17 | #' ui <- fluidPage( 18 | #' 19 | #' 20 | #' 21 | #' fabric_shape(cid = "canvas123", 22 | #' shapeId = "tri1", 23 | #' shape = "Triangle", 24 | #' fill = "darkblue"), 25 | #' 26 | #'fabric_curtail(cid = "canvas123", 27 | #' imgsrc = img, 28 | #' type = "background" 29 | #' 30 | #' ) 31 | #' 32 | #') 33 | #' 34 | #'server <- function(input, output) {} 35 | #' 36 | #' 37 | #'shinyApp(ui = ui, server = server) 38 | #' 39 | #'} 40 | 41 | 42 | 43 | 44 | 45 | fabric_curtail <- function(cid, 46 | imgsrc, 47 | type = "background"){ 48 | 49 | if (!type %in% c("background", 50 | "overlay")) { 51 | stop(paste0("type accepts two values: 'background' or 'overlay'")) 52 | } 53 | 54 | 55 | type <- ifelse(type == "background", "setBackgroundImage", "setOverlayImage") 56 | 57 | 58 | 59 | 60 | htmltools::tags$script(htmltools::HTML(glue::glue(" 61 | 62 | 63 | 64 | fabric.Image.fromURL('{imgsrc}', function(img) {{ 65 | img.set({{ 66 | scaleX: {cid}.getWidth() / img.width, 67 | scaleY: {cid}.getHeight() / img.height, 68 | objectCaching: false, 69 | originX: 'left', 70 | originY: 'top' 71 | }}); 72 | {cid}.{type}(img, {cid}.renderAll.bind({cid})); 73 | }}, {{ crossOrigin: 'anonymous'}}); 74 | 75 | 76 | "))) 77 | 78 | 79 | } 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /R/fabric_dependency.R: -------------------------------------------------------------------------------- 1 | 2 | #' Create an HTML dependency for fabric.js 3 | #' 4 | #' @rdname html-dependencies 5 | #' @noRd 6 | #' 7 | #' 8 | 9 | 10 | fabric_dependency <- function() { 11 | htmltools::htmlDependency( 12 | name = "fabric", 13 | version = "3.6.3", 14 | package = "fabricerin", 15 | src = c( 16 | file = "fabric", 17 | href = "https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.3/" 18 | ), 19 | script = "fabric.min.js", 20 | all_files = FALSE 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /R/fabric_drawing.R: -------------------------------------------------------------------------------- 1 | 2 | #' Create a canvas element for drawing 3 | #' 4 | #' @param cid the id of the canvas element 5 | #' @param cwidth the width of the canvas element 6 | #' @param cheight the height of the canvas element 7 | #' @param cfill the color of the canvas element. Default to #FFFFFF (white) 8 | #' @param drawingWidth the width of the drawing output. Default to 2 9 | #' @param gumSize specify the size of the gum. Defaults to 10 10 | #' 11 | #' @return an HTML canvas element 12 | #' @export 13 | #' 14 | #' @examples 15 | #'if (interactive()) { 16 | #' 17 | #'ui <- fluidPage( 18 | #' 19 | #' 20 | #' h1("Draw some stuff here"), 21 | #' 22 | #' fabric_drawing(cid = "canvas1") 23 | #' 24 | #' ) 25 | #' 26 | #'server <- function(input, output) {} 27 | #' 28 | #'shinyApp(ui = ui, server = server) 29 | #' 30 | #'} 31 | 32 | 33 | 34 | 35 | fabric_drawing <- function(cid, 36 | cwidth = 800, 37 | cheight = 600, 38 | cfill = "#FFFFFF", 39 | drawingWidth = 2, 40 | gumSize = 10){ 41 | 42 | 43 | 44 | htmltools::tagList( 45 | 46 | 47 | htmltools::HTML(glue::glue(" 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ")), 56 | 57 | htmltools::tags$canvas(id = cid, width = cwidth, height = cheight), 58 | 59 | fabric_dependency(), 60 | 61 | filesaver_dependency(), 62 | 63 | jquery_dependency(), 64 | 65 | 66 | htmltools::tags$script(htmltools::HTML(glue::glue( 67 | " 68 | 69 | const {cid} = new fabric.Canvas('{cid}', {{ 70 | backgroundColor: '{cfill}', 71 | isDrawingMode: true 72 | 73 | }}); 74 | 75 | 76 | {cid}.freeDrawingBrush.width = {drawingWidth}; 77 | 78 | {cid}drawingColorEl = document.getElementById('{cid}drawing-color'); 79 | 80 | {cid}.freeDrawingBrush.color = {cid}drawingColorEl.value; 81 | 82 | {cid}drawingColorEl.onchange = function() {{ 83 | {cid}.freeDrawingBrush.color = {cid}drawingColorEl.value; 84 | }}; 85 | 86 | 87 | $('#erase{cid}').click(function(){{ 88 | 89 | if (this.checked) {{ 90 | 91 | {cid}.freeDrawingBrush.color = '{cfill}'; 92 | 93 | }} else {{ 94 | 95 | {cid}.freeDrawingBrush.color = {cid}drawingColorEl.value; 96 | 97 | }} 98 | 99 | 100 | 101 | }}); 102 | 103 | 104 | $('#erase{cid}').click(function(){{ 105 | 106 | if (this.checked) {{ 107 | 108 | {cid}.freeDrawingBrush.width = {gumSize}; 109 | 110 | }} else {{ 111 | 112 | {cid}.freeDrawingBrush.width = {drawingWidth}; 113 | 114 | }} 115 | 116 | 117 | }}); 118 | 119 | 120 | $('#export{cid}').click(function(){{ 121 | 122 | $('#{cid}').get(0).toBlob(function(blob){{ 123 | 124 | saveAs(blob, '{cid}-IMG.png'); 125 | 126 | 127 | }}); 128 | 129 | 130 | }}); 131 | 132 | 133 | " 134 | ))) 135 | 136 | 137 | 138 | ) 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | } 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /R/fabric_image.R: -------------------------------------------------------------------------------- 1 | 2 | #' Insert external images inside canvas element 3 | #' 4 | #' @param cid the id of the canvas element 5 | #' @param cwidth the width of the canvas element. Defaults to 800 6 | #' @param cheight the height of the canvas element. Defaults to 600 7 | #' @param cfill the color of the canvas element 8 | #' @param imgId the id of the image 9 | #' @param imgsrc the URL source of the image 10 | #' @param imgwidth the width of the image. Defaults to 500 11 | #' @param imgheight the height of the image. Defaults to 500 12 | #' @param left the image's position from the left relative to the canvas element. Defaults to 100 13 | #' @param top the image's position from the top relative to the canvas element. Defaults to 100 14 | #' @param angle the angle of rotation of the image. Defaults to 0 (no rotation) 15 | #' @param opacity the opacity of the image (from 0 to 1). Defaults to 1 16 | #' @param strokecolor the stroke color of the image. Defaults to 'darkblue' 17 | #' @param strokewidth the stroke width of the image. Defaults to 1 18 | #' @param selectable logical. If TRUE, the user can modify interactively the image's size, position and rotation. Defaults to TRUE 19 | #' @param isDrawingMode logical. If TRUE, the user can draw inside the canvas element. 20 | #' 21 | #' @return an image inside a canvas element 22 | #' @export 23 | #' 24 | #' @examples 25 | #' 26 | #' 27 | #' if (interactive()) { 28 | #' 29 | #' img <- "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/R_logo.svg/724px-R_logo.svg.png" 30 | #' 31 | #' ui <- fluidPage( 32 | #' 33 | #' 34 | #' fabric_image(cid = "cimage", 35 | #' cfill = "lightblue", 36 | #' imgId = "Rimg", 37 | #' imgsrc = img) 38 | #' 39 | #' ) 40 | #' 41 | #'server <- function(input, output) {} 42 | #' 43 | #' 44 | #'shinyApp(ui = ui, server = server) 45 | #' 46 | #'} 47 | #' 48 | 49 | fabric_image <- function( 50 | cid, 51 | cwidth = 800, 52 | cheight = 600, 53 | cfill = "#FFFFFF", 54 | imgId, 55 | imgsrc, 56 | imgwidth = 500, 57 | imgheight = 500, 58 | left = 100, 59 | top = 100, 60 | angle = 0, 61 | opacity = 1, 62 | strokecolor = "darkblue", 63 | strokewidth = 1, 64 | selectable = TRUE, 65 | isDrawingMode = FALSE 66 | 67 | ){ 68 | 69 | selectable <- ifelse(selectable == TRUE, "true", "false") 70 | 71 | isDrawingMode <- ifelse(isDrawingMode == TRUE, "true", "false") 72 | 73 | htmltools::tagList( 74 | 75 | htmltools::tags$canvas(id = cid, width = cwidth, height = cheight), 76 | 77 | fabric_dependency(), 78 | 79 | 80 | htmltools::tags$script(htmltools::HTML(glue::glue( 81 | 82 | 83 | " 84 | var {cid} = new fabric.Canvas('{cid}', {{ 85 | 86 | isDrawingMode: {isDrawingMode} 87 | 88 | }}); 89 | 90 | {cid}.backgroundColor = '{cfill}'; 91 | 92 | fabric.Image.fromURL('{imgsrc}', function({imgId}) {{ 93 | 94 | var {imgId} = {imgId}.set({{ 95 | left: {left}, 96 | top: {top}, 97 | angle: {angle}, 98 | opacity: {opacity}, 99 | stroke: '{strokecolor}', 100 | strokeWidth: {strokewidth}, 101 | selectable: {selectable} 102 | 103 | }}); 104 | 105 | {imgId}.scaleToHeight({imgheight}); 106 | {imgId}.scaleToWidth({imgwidth}); 107 | 108 | {cid}.add({imgId}); 109 | 110 | }); 111 | 112 | " 113 | ))) 114 | 115 | 116 | 117 | ) 118 | 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /R/fabric_image_add.R: -------------------------------------------------------------------------------- 1 | #' Add an image to a preexisting canvas element 2 | #' 3 | #' @param cid the id of the canvas element you want to add your image to 4 | #' @param imgId the of the image 5 | #' @param imgsrc the URL source of the image 6 | #' @param imgwidth the width of the image. Defaults to 500 7 | #' @param imgheight the height of the image. Defaults to 500 8 | #' @param left the image's position from the left relative to the canvas element. Defaults to 100 9 | #' @param top the image's position from the top relative to the canvas element. Defaults to 100 10 | #' @param angle the angle of rotation of the image. Defaults to 0 (no rotation) 11 | #' @param opacity the opacity of the image (from 0 to 1). Defaults to 1 12 | #' @param strokecolor the stroke color of the image. Defaults to 'darkblue' 13 | #' @param strokewidth the stroke width of the image. Defaults to 1 14 | #' @param selectable logical. If TRUE, the user can modify interactively the image's size, position and rotation. Defaults to TRUE 15 | #' 16 | #' @return an image inside a preexisting canvas element 17 | #' @export 18 | #' 19 | #' @examples 20 | #' if (interactive()) { 21 | #' 22 | #' img1 <- "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/R_logo.svg/724px-R_logo.svg.png" 23 | #' img2 <- "https://raw.githubusercontent.com/rstudio/hex-stickers/master/PNG/dplyr.png" 24 | #' ui <- fluidPage( 25 | #' 26 | #' 27 | #' fabric_image(cid = "cimage", 28 | #' imgId = "Rimg", 29 | #' imgsrc = img1, 30 | #' imgheight = 200, 31 | #' imgwidth = 200), 32 | #' 33 | #' fabric_image_add(cid = "cimage", 34 | #' imgId = "rstudioimg", 35 | #' imgsrc = img2, 36 | #' imgwidth = 200, 37 | #' imgheight = 200, 38 | #' left = 400) 39 | #' ) 40 | #' 41 | #'server <- function(input, output) {} 42 | #' 43 | #'shinyApp(ui = ui, server = server) 44 | #' 45 | #'} 46 | 47 | 48 | 49 | fabric_image_add <- function(cid, 50 | imgId, 51 | imgsrc, 52 | imgwidth = 500, 53 | imgheight = 500, 54 | left = 100, 55 | top = 100, 56 | angle = 0, 57 | opacity = 1, 58 | strokecolor = "darkblue", 59 | strokewidth = 1, 60 | selectable = TRUE) { 61 | 62 | 63 | selectable <- ifelse(selectable == TRUE, "true", "false") 64 | 65 | 66 | htmltools::tags$script(htmltools::HTML( 67 | glue::glue( 68 | " 69 | 70 | fabric.Image.fromURL('{imgsrc}', function(myImg) {{ 71 | 72 | var {imgId} = myImg.set({{ 73 | left: {left}, 74 | top: {top}, 75 | angle: {angle}, 76 | opacity: {opacity}, 77 | stroke: '{strokecolor}', 78 | strokeWidth: {strokewidth}, 79 | selectable: {selectable} 80 | 81 | }}); 82 | 83 | {imgId}.scaleToHeight({imgheight}); 84 | {imgId}.scaleToWidth({imgwidth}); 85 | 86 | {cid}.add({imgId}); 87 | 88 | {cid}.renderAll(); 89 | 90 | 91 | }}, {{ crossOrigin: 'anonymous'}}); 92 | 93 | 94 | " 95 | ) 96 | )) 97 | 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /R/fabric_shape.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' Create shapes inside a canvas 4 | #' 5 | #' @param cid the id of the canvas element 6 | #' @param cwidth the width of the canvas element. Defaults to 800 7 | #' @param cheight the height of the canvas element. Defaults to 600 8 | #' @param cfill the color of the canvas element 9 | #' @param shapeId the id of the shape object 10 | #' @param shape the shape of the object. Choices include 'Circle', 'Triangle' and 'Rect'. Defaults to 'Rect' 11 | #' @param left the shape's position from the left relative to the canvas element. Defaults to 100 12 | #' @param top the shape's position from the top relative to the canvas element. Defaults to 100 13 | #' @param fill the color of the shape. Defaults to 'red' 14 | #' @param width the width of the shape. Defaults to 200 15 | #' @param height the height of the shape. Defaults to 200 16 | #' @param angle the angle of rotation of the shape. Defaults to 0 (no rotation) 17 | #' @param opacity the opacity of the shape (from 0 to 1). Defaults to 1 18 | #' @param strokecolor the stroke color of the shape. Defaults to 'darkblue' 19 | #' @param strokewidth the stroke width of the shape. Defaults to 5. 20 | #' @param selectable logical. If TRUE, the user can modify interactively the shape's size, position and rotation. Defaults to TRUE 21 | #' @param isDrawingMode logical. If TRUE, the user can draw inside the canvas element. 22 | #' @param radius mandatory if the chosen shape is a 'Circle'. Defaults to NULL 23 | #' @param xPolygon a vector of the coordinate points of the polygon, from the left. 24 | #' @param yPolygon a vector of the coordinate points of the polygon, from the top 25 | #' 26 | #' @return a shape object inside a canvas 27 | #' @export 28 | #' 29 | #' @examples 30 | #' 31 | #'if(interactive()){ 32 | #' 33 | #'ui <- fluidPage( 34 | #' 35 | #' 36 | #'h2("Below you'll find a red Rectangle with a darkblue stroke"), 37 | #' 38 | #'fabric_shape(cid = "canvas", shapeId = "shape1", shape = "Rect") 39 | #' 40 | #') 41 | #' 42 | #'server <- function(input, output) { 43 | #' 44 | #' 45 | #'} 46 | #' 47 | #' 48 | #'shinyApp(ui = ui, server = server) 49 | #' 50 | #'} 51 | #' 52 | 53 | 54 | 55 | 56 | 57 | fabric_shape <- function(cid, 58 | cwidth = 800, 59 | cheight = 600, 60 | cfill = "#FFFFFF", 61 | shapeId, 62 | shape = "Rect", 63 | left = 100, 64 | top = 100, 65 | fill = "red", 66 | width = 200, 67 | height = 200, 68 | angle = 0, 69 | opacity = 1, 70 | strokecolor = "darkblue", 71 | strokewidth = 5, 72 | selectable = TRUE, 73 | isDrawingMode = FALSE, 74 | radius = NULL, 75 | xPolygon = NULL, 76 | yPolygon = NULL) { 77 | 78 | if (!shape %in% c("Rect", 79 | "Circle", 80 | "Triangle", 81 | "Polygon")) { 82 | stop(paste0(shape, " shape is not available, choices are Rect, Circle, Triangle and Polygon")) 83 | } 84 | 85 | 86 | if (shape == "Polygon" & 87 | is.null(yPolygon) & is.null(xPolygon)) { 88 | stop("If you draw a Polygon, you need to se the xPolygon and yPolygon arguments") 89 | } 90 | 91 | if (shape == "Polygon" & 92 | is.null(xPolygon)) { 93 | stop("If you draw a Circle, you need to set the xPolygon argument") 94 | } 95 | 96 | if (shape == "Polygon" & 97 | is.null(yPolygon)) { 98 | stop("If you draw a Circle, you need to set the yPolygon argument") 99 | } 100 | 101 | if (shape == "Polygon" & 102 | 103 | length(xPolygon) != length(yPolygon) 104 | 105 | ){ 106 | 107 | stop("xPolygon and yPolygon must have the same length") 108 | 109 | } 110 | 111 | if (shape == "Circle" & 112 | is.null(radius)) { 113 | stop("If you draw a Circle, you need to provide a radius") 114 | } 115 | 116 | radius <- 117 | ifelse(!is.null(radius), glue::glue("radius:{radius}"), "") 118 | 119 | 120 | selectable <- ifelse(selectable == TRUE, "true", "false") 121 | 122 | isDrawingMode <- ifelse(isDrawingMode == TRUE, "true", "false") 123 | 124 | 125 | if(shape == "Polygon"){ 126 | 127 | 128 | data <- paste("{x:", xPolygon, ", y:", yPolygon, "}", collapse = ",") 129 | 130 | 131 | htmltools::tagList( 132 | htmltools::tags$canvas( 133 | id = cid, 134 | width = cwidth, 135 | height = cheight 136 | ), 137 | 138 | fabric_dependency(), 139 | 140 | 141 | htmltools::tags$script(htmltools::HTML( 142 | glue::glue( 143 | " 144 | var {cid} = new fabric.Canvas('{cid}', {{ 145 | 146 | isDrawingMode: {isDrawingMode} 147 | 148 | }}); 149 | 150 | {cid}.backgroundColor = '{cfill}'; 151 | 152 | 153 | var {shapeId} = new fabric.{shape}( 154 | 155 | [{data}], {{ 156 | 157 | fill: '{fill}', 158 | left: {left}, 159 | top: {top}, 160 | width: {width}, 161 | height: {height}, 162 | angle: {angle}, 163 | opacity: {opacity}, 164 | stroke: '{strokecolor}', 165 | strokeWidth: {strokewidth}, 166 | selectable: {selectable} 167 | 168 | }}); 169 | 170 | {cid}.add({shapeId}); 171 | 172 | 173 | " 174 | ) 175 | )) 176 | 177 | 178 | 179 | ) 180 | 181 | 182 | 183 | 184 | } else { 185 | 186 | 187 | htmltools::tagList( 188 | htmltools::tags$canvas( 189 | id = cid, 190 | width = cwidth, 191 | height = cheight 192 | ), 193 | 194 | fabric_dependency(), 195 | 196 | 197 | htmltools::tags$script(htmltools::HTML( 198 | glue::glue( 199 | " 200 | 201 | var {cid} = new fabric.Canvas('{cid}', {{ 202 | 203 | isDrawingMode: {isDrawingMode} 204 | 205 | }}); 206 | 207 | {cid}.backgroundColor = '{cfill}'; 208 | 209 | 210 | var {shapeId} = new fabric.{shape}({{ 211 | 212 | left: {left}, 213 | top: {top}, 214 | fill: '{fill}', 215 | width: {width}, 216 | height: {height}, 217 | angle: {angle}, 218 | opacity: {opacity}, 219 | stroke: '{strokecolor}', 220 | strokeWidth: {strokewidth}, 221 | selectable: {selectable}, 222 | 223 | {radius} 224 | 225 | }}); 226 | 227 | {cid}.add({shapeId}); 228 | 229 | 230 | " 231 | ) 232 | )) 233 | 234 | 235 | 236 | ) 237 | 238 | 239 | 240 | 241 | 242 | } 243 | 244 | 245 | 246 | 247 | 248 | 249 | } 250 | -------------------------------------------------------------------------------- /R/fabric_shape_add.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #' Add a shape object to a preexisting canvas element 5 | #' 6 | #' @param cid the id of the canvas element you want to add your shape to 7 | #' @param shapeId the id of the shape object 8 | #' @param shape the shape of the object. Choices include 'Circle', 'Triangle' and 'Rect'. Defaults to 'Rect' 9 | #' @param left the shape's position from the left relative to the canvas element. Defaults to 100 10 | #' @param top the shape's position from the top relative to the canvas element. Defaults to 100 11 | #' @param fill the color of the shape. Defaults to 'red' 12 | #' @param width the width of the shape. Defaults to 200 13 | #' @param height the height of the shape. Defaults to 200 14 | #' @param angle the angle of rotation of the shape. Defaults to 0 (no rotation) 15 | #' @param opacity the opacity of the shape. Defaults to 1 16 | #' @param strokecolor the stroke color of the shape. Defaults to 'darkblue' 17 | #' @param strokewidth the stroke width of the shape. Defaults to 5. 18 | #' @param selectable logical. If TRUE, the user can modify interactively the shape. Defaults to TRUE 19 | #' @param radius Mandatory if the chosen shape is a 'Circle'. Defaults to NULL 20 | #' @param xPolygon a vector of the coordinate points of the polygon, from the left. 21 | #' @param yPolygon a vector of the coordinate points of the polygon, from the top 22 | 23 | #' 24 | #' @return a shape object inside a preexisting canvas element 25 | #' @export 26 | #' 27 | #' @examples 28 | #' 29 | #' if (interactive()) { 30 | #' 31 | #' ui <- fluidPage( 32 | #' 33 | #' 34 | #' fabric_shape(cid = "canvas", 35 | #' shapeId = "shape1", 36 | #' shape = "Rect", 37 | #' left = 130, 38 | #' top = 200), 39 | #' 40 | #' fabric_shape_add(cid = "canvas", 41 | #' shapeId = "shapo", 42 | #' shape = "Circle", 43 | #' radius = 30, 44 | #' left = 100, 45 | #' top = 100), 46 | #' 47 | #' fabric_shape_add(cid = "canvas", 48 | #' shapeId = "shapa", 49 | #' shape = "Circle", 50 | #' radius = 30, 51 | #' left = 200, 52 | #' top = 100), 53 | #' 54 | #' fabric_shape_add(cid = "canvas", 55 | #' shapeId = "shapox", 56 | #' shape = "Circle", 57 | #' radius = 30, 58 | #' left = 300, 59 | #' top = 100), 60 | #' 61 | #' fabric_shape_add(cid = "canvas", 62 | #' shapeId = "shapor", 63 | #' shape = "Circle", 64 | #' radius = 30, 65 | #' left = 300, 66 | #' top = 100) 67 | #' 68 | #' ) 69 | #' 70 | #' 71 | #' server <- function(input, output) {} 72 | #' 73 | #' shinyApp(ui = ui, server = server) 74 | #' 75 | #' } 76 | 77 | 78 | 79 | fabric_shape_add <- function(cid, 80 | shapeId, 81 | shape = "Rect", 82 | left = "100", 83 | top = "100", 84 | fill = "red", 85 | width = 200, 86 | height = 200, 87 | angle = 0, 88 | opacity = 1, 89 | strokecolor = "darkblue", 90 | strokewidth = 5, 91 | selectable = TRUE, 92 | radius = NULL, 93 | xPolygon = NULL, 94 | yPolygon = NULL){ 95 | 96 | 97 | if (!shape %in% c("Rect", 98 | "Circle", 99 | "Triangle", 100 | "Polygon")) { 101 | stop(paste0(shape, " shape is not available, choices are Rect, Circle, Triangle and Polygon")) 102 | } 103 | 104 | 105 | if (shape == "Polygon" & 106 | is.null(yPolygon) & is.null(xPolygon)) { 107 | stop("If you draw a Polygon, you need to set the xPolygon and yPolygon arguments") 108 | } 109 | 110 | if (shape == "Polygon" & 111 | is.null(xPolygon)) { 112 | stop("If you draw a Polygon, you need to set the xPolygon argument") 113 | } 114 | 115 | if (shape == "Polygon" & 116 | is.null(yPolygon)) { 117 | stop("If you draw a Polygon, you need to set the yPolygon argument") 118 | } 119 | 120 | if (shape == "Polygon" & 121 | 122 | length(xPolygon) != length(yPolygon) 123 | 124 | ){ 125 | 126 | stop("xPolygon and yPolygon must have the same length") 127 | 128 | } 129 | 130 | if (shape == "Circle" & 131 | is.null(radius)) { 132 | stop("If you draw a Circle, you need to provide a radius") 133 | } 134 | 135 | radius <- 136 | ifelse(!is.null(radius), glue::glue("radius:{radius}"), "") 137 | 138 | 139 | selectable <- ifelse(selectable == TRUE, "true", "false") 140 | 141 | 142 | 143 | if(shape == "Polygon"){ 144 | 145 | 146 | data <- paste("{x:", xPolygon, ", y:", yPolygon, "}", collapse = ",") 147 | 148 | 149 | 150 | 151 | htmltools::tags$script(htmltools::HTML( 152 | glue::glue( 153 | " 154 | 155 | var {shapeId} = new fabric.{shape}( 156 | 157 | [{data}], {{ 158 | 159 | fill: '{fill}', 160 | left: {left}, 161 | top: {top}, 162 | width: {width}, 163 | height: {height}, 164 | angle: {angle}, 165 | opacity: {opacity}, 166 | stroke: '{strokecolor}', 167 | strokeWidth: {strokewidth}, 168 | selectable: {selectable} 169 | 170 | }}); 171 | 172 | {cid}.add({shapeId}); 173 | 174 | 175 | " 176 | ) 177 | ) 178 | 179 | 180 | 181 | ) 182 | 183 | 184 | 185 | 186 | } else { 187 | 188 | 189 | 190 | htmltools::tags$script(htmltools::HTML( 191 | glue::glue( 192 | " 193 | var {shapeId} = new fabric.{shape}({{ 194 | 195 | left: {left}, 196 | top: {top}, 197 | fill: '{fill}', 198 | width: {width}, 199 | height: {height}, 200 | angle: {angle}, 201 | opacity: {opacity}, 202 | stroke: '{strokecolor}', 203 | strokeWidth: {strokewidth}, 204 | selectable: {selectable}, 205 | 206 | {radius} 207 | 208 | }}); 209 | 210 | {cid}.add({shapeId}); 211 | 212 | 213 | " 214 | ) 215 | ) 216 | 217 | 218 | 219 | ) 220 | 221 | 222 | } 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | } 251 | -------------------------------------------------------------------------------- /R/fabric_text.R: -------------------------------------------------------------------------------- 1 | 2 | #' Insert text within canvas element 3 | #' 4 | #' @param cid the id of the canvas element 5 | #' @param cwidth the width of the canvas element. Defaults to 800 6 | #' @param cheight the height of the canvas element. Defaults to 600 7 | #' @param cfill the color of the canvas element 8 | #' @param textId the id of the text 9 | #' @param text the content of the text 10 | #' @param left the text's position from the left relative to the canvas element. Defaults to 100 11 | #' @param top the text's position from the top relative to the canvas element. Defaults to 100 12 | #' @param fill the text's color. Defaults to '#2F3941' (dark shade of cyan-blue) 13 | #' @param angle the angle of rotation of the text. Defaults to 0 (no rotation) 14 | #' @param opacity text opacity (from 0 to 1). Defaults to 1 15 | #' @param fontFamily the font family of the text. Defaults to 'Comic Sans' 16 | #' @param fontSize text sizing. Defaults to 40 17 | #' @param fontStyle the font style of the text. Either 'normal' or 'italic' 18 | #' @param strokecolor the stroke color of the text Defaults to '#282A36' (Very dark grayish blue) 19 | #' @param strokewidth the stroke width of the text. Defaults to 1 20 | #' @param fontWeight allows the user to make text thicker or thinner. Keywords can be used ('normal', 'bold'), or numbers. Defaults to 'normal' 21 | #' @param underline logical. Whether to underline the text or not. Defaults to FALSE 22 | #' @param linethrough logical. Whether to insert a line through the text or not. Defaults to FALSE 23 | #' @param overline logical. Whether to put a line above the text or not. Defaults to FALSE 24 | #' @param selectable logical. If TRUE, the user can modify interactively the image's size, position and rotation. Defaults to TRUE 25 | #' @param shadow logical. If TRUE a text shadow will be inserted behind the raw text. Defaults to FALSE 26 | #' @param shadowCol the color of the text shadow. Defaults to #FFFAF0 (floral white) 27 | #' @param textAlign the alignment of text. Useful when there are line breaks. Defaults to "center" 28 | #' @param lineHeight the height of the line breaks.Defaults to 1 29 | #' @param textBackgroundColor the background color of the text, defaults to NULL 30 | #' @param isDrawingMode logical. If TRUE, the user can draw inside the canvas element. 31 | #' 32 | #' @return a text object within a canvas element 33 | #' @export 34 | #' 35 | #' @examples 36 | #' 37 | #' 38 | #' if (interactive()) { 39 | #' 40 | #' 41 | #'ui <- fluidPage( 42 | #' 43 | #' 44 | #'fabric_text(cid = "can", 45 | #' textId = "text", 46 | #' text = "But A Hero Is A Guy Who Gives Out The Meat To Everyone Else.", 47 | #' cfill = "#DD5347", 48 | #' left = 120, 49 | #' shadowCol = "blue", 50 | #' fontSize = 20, 51 | #' fontWeight = "bold", 52 | #' lineHeight = 3 53 | #' ) 54 | #' ) 55 | #'server <- function(input, output) {} 56 | #' 57 | #'shinyApp(ui = ui, server = server) 58 | #' 59 | #'} 60 | 61 | 62 | fabric_text <- function(cid, 63 | cwidth = 800, 64 | cheight = 600, 65 | cfill = "#FFFFFF", 66 | textId, 67 | text, 68 | left = 100, 69 | top = 100, 70 | fill = "#2F3941", 71 | angle = 0, 72 | opacity = 1, 73 | fontFamily = 'Comic Sans', 74 | fontSize = 40, 75 | fontStyle = 'normal', 76 | strokecolor = "#282A36", 77 | strokewidth = 1, 78 | fontWeight = "normal", 79 | underline = FALSE, 80 | linethrough = FALSE, 81 | overline = FALSE, 82 | selectable = TRUE, 83 | shadow = FALSE, 84 | shadowCol = "#FFFAF0", 85 | textAlign = "center", 86 | lineHeight = 1, 87 | textBackgroundColor = NULL, 88 | isDrawingMode = FALSE){ 89 | 90 | 91 | 92 | 93 | if (!fontStyle %in% c("normal", 94 | "italic")) { 95 | stop(paste0("fontStyle accepts two values: 'normal' or 'italic'")) 96 | } 97 | 98 | selectable <- ifelse(selectable == TRUE, "true", "false") 99 | 100 | isDrawingMode <- ifelse(isDrawingMode == TRUE, "true", "false") 101 | 102 | underline <- ifelse(underline == TRUE, "true", "false") 103 | 104 | linethrough <- ifelse(linethrough == TRUE, "true", "false") 105 | 106 | overline <- ifelse(overline == TRUE, "true", "false") 107 | 108 | shadow <- ifelse(shadow == TRUE, glue::glue("shadow:'{shadowCol} 5px 5px 5px'"), "") 109 | 110 | tBG <- ifelse(is.null(textBackgroundColor), character(0), glue::glue("textBackgroundColor: '{textBackgroundColor}',")) 111 | 112 | 113 | 114 | 115 | htmltools::tagList( 116 | 117 | htmltools::tags$canvas(id = cid, width = cwidth, height = cheight), 118 | 119 | fabric_dependency(), 120 | 121 | htmltools::tags$script(htmltools::HTML(glue::glue( 122 | 123 | 124 | " 125 | 126 | var {cid} = new fabric.Canvas('{cid}', {{ 127 | 128 | isDrawingMode: {isDrawingMode} 129 | 130 | }}); 131 | 132 | {cid}.backgroundColor = '{cfill}'; 133 | 134 | var {textId} = new fabric.Text(\"{text}\", {{ 135 | 136 | left: {left}, 137 | top: {top}, 138 | fontFamily: '{fontFamily}', 139 | fontSize: {fontSize}, 140 | fontStyle: '{fontStyle}', 141 | fontWeight: '{fontWeight}', 142 | underline: {underline}, 143 | linethrough: {linethrough}, 144 | overline: {overline}, 145 | fill: '{fill}', 146 | angle: {angle}, 147 | opacity: {opacity}, 148 | stroke: '{strokecolor}', 149 | strokeWidth: {strokewidth}, 150 | textAlign: '{textAlign}', 151 | lineHeight: {lineHeight}, 152 | {tBG} 153 | selectable: {selectable}, 154 | {shadow} 155 | 156 | }}); 157 | 158 | {cid}.add({textId}); 159 | 160 | " 161 | , .na = ""))) 162 | 163 | 164 | 165 | ) 166 | 167 | 168 | } 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /R/fabric_text_add.R: -------------------------------------------------------------------------------- 1 | #' Add text within preexisting canvas element 2 | #' 3 | #' @param cid the id of the canvas element 4 | #' @param textId the id of the text 5 | #' @param text the content of the text 6 | #' @param left the text's position from the left relative to the canvas element. Defaults to 100 7 | #' @param top the text's position from the top relative to the canvas element. Defaults to 100 8 | #' @param fill the text's color. Defaults to '#2F3941' (dark shade of cyan-blue) 9 | #' @param angle the angle of rotation of the text. Defaults to 0 (no rotation) 10 | #' @param opacity text opacity (from 0 to 1). Defaults to 1 11 | #' @param fontFamily the font family of the text. Defaults to 'Comic Sans' 12 | #' @param fontSize text sizing. Defaults to 40 13 | #' @param fontStyle the font style of the text. Either 'normal' or 'italic' 14 | #' @param strokecolor the stroke color of the text Defaults to '#282A36' (Very dark grayish blue) 15 | #' @param strokewidth the stroke width of the text. Defaults to 1 16 | #' @param fontWeight allows the user to make text thicker or thinner. Keywords can be used ('normal', 'bold'), or numbers. Defaults to 'normal' 17 | #' @param underline logical. Whether to underline the text or not. Defaults to FALSE 18 | #' @param linethrough logical. Whether to insert a line through the text or not. Defaults to FALSE 19 | #' @param overline logical. Whether to put a line above the text or not. Defaults to FALSE 20 | #' @param selectable logical. If TRUE, the user can modify interactively the image's size, position and rotation. Defaults to TRUE 21 | #' @param shadow logical. If TRUE a text shadow will be inserted behind the raw text. Defaults to FALSE 22 | #' @param shadowCol the color of the text shadow. Defaults to #FFFAF0 (floral white) 23 | #' @param textAlign the alignment of text. Useful when there are line breaks. Defaults to "center" 24 | #' @param lineHeight the height of the line breaks.Defaults to 1 25 | #' @param textBackgroundColor the background color of the text, defaults to NULL 26 | #' 27 | #' @return a text object within a preexisting canvas element 28 | #' @export 29 | #' 30 | #' @examples 31 | #' 32 | #' if (interactive()) { 33 | #' 34 | #' ui <- fluidPage( 35 | #' 36 | #' 37 | #' fabric_shape(cid = "canvas123", 38 | #' cfill = "lightblue", 39 | #' cwidth = 1000, 40 | #' shapeId = "tri1", 41 | #' shape = "Triangle", 42 | #' fill = "darkblue"), 43 | #' 44 | #' fabric_text_add(cid = "canvas123", 45 | #' textId = "txt1", 46 | #' text = "This is a darkblue Triangle !", 47 | #' left = 350 48 | #' ) 49 | #' 50 | #' ) 51 | #' 52 | #' server <- function(input, output) {} 53 | #' 54 | #' shinyApp(ui = ui, server = server) 55 | #' 56 | #' 57 | #' } 58 | 59 | 60 | 61 | 62 | fabric_text_add <- function(cid, 63 | textId, 64 | text, 65 | left = 100, 66 | top = 100, 67 | fill = "#2F3941", 68 | angle = 0, 69 | opacity = 1, 70 | fontFamily = 'Comic Sans', 71 | fontSize = 40, 72 | fontStyle = 'normal', 73 | strokecolor = "#282A36", 74 | strokewidth = 1, 75 | fontWeight = "normal", 76 | underline = FALSE, 77 | linethrough = FALSE, 78 | overline = FALSE, 79 | selectable = TRUE, 80 | shadow = FALSE, 81 | shadowCol = "#324C63", 82 | textAlign = "center", 83 | lineHeight = 1, 84 | textBackgroundColor = NULL 85 | ){ 86 | 87 | 88 | 89 | if (!fontStyle %in% c("normal", 90 | "italic")) { 91 | stop(paste0("fontStyle accepts two values: 'normal' or 'italic'")) 92 | } 93 | 94 | selectable <- ifelse(selectable == TRUE, "true", "false") 95 | 96 | 97 | underline <- ifelse(underline == TRUE, "true", "false") 98 | 99 | linethrough <- ifelse(linethrough == TRUE, "true", "false") 100 | 101 | overline <- ifelse(overline == TRUE, "true", "false") 102 | 103 | shadow <- ifelse(shadow == TRUE, glue::glue("shadow:'{shadowCol} 5px 5px 5px'"), "") 104 | 105 | tBG <- ifelse(is.null(textBackgroundColor), character(0), glue::glue("textBackgroundColor: '{textBackgroundColor}',")) 106 | 107 | 108 | 109 | htmltools::tags$script(htmltools::HTML(glue::glue( 110 | 111 | 112 | " 113 | 114 | 115 | var {textId} = new fabric.Text(\"{text}\", {{ 116 | 117 | left: {left}, 118 | top: {top}, 119 | fontFamily: '{fontFamily}', 120 | fontSize: {fontSize}, 121 | fontStyle: '{fontStyle}', 122 | fontWeight: '{fontWeight}', 123 | underline: {underline}, 124 | linethrough: {linethrough}, 125 | overline: {overline}, 126 | fill: '{fill}', 127 | angle: {angle}, 128 | opacity: {opacity}, 129 | stroke: '{strokecolor}', 130 | strokeWidth: {strokewidth}, 131 | textAlign: '{textAlign}', 132 | lineHeight: {lineHeight}, 133 | {tBG} 134 | selectable: {selectable}, 135 | {shadow} 136 | 137 | }}); 138 | 139 | {cid}.add({textId}); 140 | 141 | " 142 | , .na = "")) 143 | 144 | 145 | 146 | ) 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | } 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /R/jQuery_dependency.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #' Create an HTML dependency for jQuery 5 | #' 6 | #' @rdname html-dependencies 7 | #' @noRd 8 | #' 9 | #' 10 | 11 | jquery_dependency <- function() { 12 | htmltools::htmlDependency( 13 | name = "jQuery", 14 | version = "3.5.1", 15 | package = "fabricerin", 16 | src = c( 17 | file = "fabric", 18 | href = "https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js" 19 | ), 20 | script = "jquery-3.5.1.min.js", 21 | all_files = FALSE 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # fabricerin 17 | 18 | 19 | 20 | [![Travis build status](https://travis-ci.com/feddelegrand7/fabricerin.svg?branch=master)](https://travis-ci.com/feddelegrand7/fabricerin) 21 | [![CRAN_time_from_release](https://www.r-pkg.org/badges/ago/fabricerin)](https://cran.r-project.org/package=fabricerin) 22 | [![CRAN_latest_release_date](https://www.r-pkg.org/badges/last-release/fabricerin)](https://cran.r-project.org/package=fabricerin) 23 | [![metacran downloads](https://cranlogs.r-pkg.org/badges/fabricerin)](https://cran.r-project.org/package=fabricerin) 24 | [![metacran downloads](https://cranlogs.r-pkg.org/badges/grand-total/fabricerin)](https://cran.r-project.org/package=fabricerin) 25 | [![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) 26 | [![R badge](https://img.shields.io/badge/Build%20with-♥%20JS%20and%20R-pink)](https://github.com/feddelegrand7/fabricerin) 27 | 28 | 29 | 30 | The `fabricerin` (spelled __fabrikerine__) package allows you to create easily canvas elements within your Shiny app and RMarkdown documents. Thanks to [Garrick Aden-Buie](https://twitter.com/grrrck?lang=en), you can also use it within your [xaringan](https://github.com/yihui/xaringan) slides. You can use the canvas to render shapes, images and text. You can also create a canvas for drawing/taking notes purposes. Under the hoods, `fabricerin` relies on the [fabricjs](http://fabricjs.com/) JavaScript library. 31 | 32 | 33 | 34 | ## Installation 35 | 36 | You can install `fabricerin` from [CRAN](https://CRAN.R-project.org/package=fabricerin) with: 37 | 38 | ```{r, eval=FALSE} 39 | 40 | install.packages("fabricerin") 41 | 42 | ``` 43 | 44 | 45 | You can install the development version from [GitHub](https://github.com/) with: 46 | 47 | ```{r, eval=FALSE} 48 | # install.packages("remotes") 49 | 50 | remotes::install_github("feddelegrand7/fabricerin") 51 | 52 | ``` 53 | 54 | 55 | ## Examples: 56 | 57 | First of all, I'd like to state that all the example provided apply the same way to Shiny and Rmd documents. `fabricerin` is not an R wrapper for the fabricjs library. The package doesn't cover all the capabilities of the library. The `fabricerin` package relies only on some specified features that according to me will help Shiny/Rmd users. Of course, if you need some improvement, feel free to create a Pull Request. 58 | 59 | ### `fabric_drawing()` Create a canvas for taking notes 60 | *** 61 | 62 | 63 | `fabric_drawing()` is pretty useful when you want to teach something and write some notes at the same time, below I provide an example using the `xaringan` package. Inside a `xaringan` slide you can just (for example) run R code in the left and take notes in the right: 64 | 65 | > Important: When you change a color, make sure that the __erase__ box is not checked. 66 | 67 | 68 | ![](man/figures/t_example.gif) 69 | 70 | 71 | `fabric_drawing()` can be used the same way in Shiny: 72 | 73 | 74 | ```{r, eval=FALSE} 75 | library(shiny) 76 | library(fabricerin) 77 | 78 | 79 | ui <- fluidPage( 80 | 81 | 82 | fabric_drawing(cid = "canvas123") 83 | 84 | ) 85 | 86 | server <- function(input, output){} 87 | 88 | shinyApp(ui, server) 89 | 90 | ``` 91 | 92 | ![](man/figures/example1.gif) 93 | 94 | ## `fabric_shape()` Render shape objects in canvas 95 | *** 96 | 97 | Currently, `fabricerin` supports three types of shapes: Rectangle, Triangle, Circle and Polygon. The user can interact with the shape and modify its position, size and rotation. If you want to disable this interactivity, you can set `selectable =FALSE` 98 | 99 | 100 | ```{r, eval=FALSE} 101 | 102 | library(shiny) 103 | library(fabricerin) 104 | 105 | 106 | ui <- fluidPage( 107 | 108 | 109 | fabric_shape(cid = "canvaId", # canvas id 110 | cfill = "orange", # canvas color 111 | cwidth = 800, # the width of the canvas 112 | cheight = 600, # the height of the canvas 113 | shapeId = "shapeId", # shape id 114 | shape = "Rect", 115 | fill = "red", # shape color 116 | width = 400, 117 | height = 400, 118 | left = 100, # the position of the shape from the left relative to the canvas 119 | top = 100, # the position of the shape from the top relative to the canvas 120 | strokecolor = "darkblue", 121 | strokewidth = 5, 122 | selectable = TRUE) 123 | 124 | ) 125 | 126 | server <- function(input, output){} 127 | 128 | shinyApp(ui, server) 129 | 130 | 131 | 132 | ``` 133 | 134 | 135 | ![](man/figures/example3.gif) 136 | 137 | 138 | You can add as many shape as you want to an existing canvas using the `fabric_shape_add()` function. __Don't forget to reference the preexisting canvas with its ID:__ 139 | 140 | ```{r, eval=FALSE} 141 | library(shiny) 142 | library(fabricerin) 143 | 144 | 145 | ui <- fluidPage( 146 | 147 | 148 | fabric_shape(cid = "canvaId", 149 | shapeId = "cr1", 150 | shape = "Circle", 151 | radius = 30, 152 | left = 100), 153 | 154 | fabric_shape_add(cid = "canvaId", 155 | shapeId = "cr2", 156 | shape = "Circle", 157 | radius = 30, 158 | left = 200), 159 | 160 | fabric_shape_add(cid = "canvaId", 161 | shapeId = "cr3", 162 | shape = "Circle", 163 | radius = 30, 164 | left = 300), 165 | 166 | fabric_shape_add(cid = "canvaId", 167 | shapeId = "cr4", 168 | shape = "Circle", 169 | radius = 30, 170 | left = 400) 171 | 172 | ) 173 | 174 | server <- function(input, output){} 175 | 176 | shinyApp(ui, server) 177 | ``` 178 | 179 | ![](man/figures/example4.png) 180 | 181 | 182 | ## `fabric_image()` Render images in canvas 183 | *** 184 | 185 | You can insert an image within a canvas a play with it using the `fabric_image()` function. Note that this function accepts only URL external images. 186 | 187 | 188 | ```{r, eval=FALSE} 189 | 190 | ui <- fluidPage( 191 | 192 | fabric_image(cid = "cimage", 193 | cfill = "lightblue", 194 | imgId = "Rimg", 195 | imgsrc = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/R_logo.svg/724px-R_logo.svg.png") 196 | 197 | ) 198 | 199 | server <- function(input, output) {} 200 | 201 | 202 | shinyApp(ui = ui, server = server) 203 | 204 | 205 | 206 | ``` 207 | 208 | ![](man/figures/example5.gif) 209 | 210 | 211 | Similar to shapes, you can add images to preexisting canvas using the `fabric_image_add()` function: 212 | 213 | 214 | ```{r,eval=FALSE} 215 | library(shiny) 216 | library(fabricerin) 217 | 218 | ui <- fluidPage( 219 | 220 | fabric_image(cid = "cimage", 221 | imgId = "Rimg", 222 | imgsrc = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/R_logo.svg/724px-R_logo.svg.png", 223 | imgheight = 200, 224 | imgwidth = 200), 225 | fabric_image_add(cid = "cimage", 226 | imgId = "rstudioimg", 227 | imgsrc = "https://raw.githubusercontent.com/rstudio/hex-stickers/master/PNG/dplyr.png", 228 | imgwidth = 200, 229 | imgheight = 200, 230 | left = 400) 231 | ) 232 | 233 | server <- function(input, output) {} 234 | 235 | shinyApp(ui = ui, server = server) 236 | 237 | 238 | ``` 239 | 240 | ![](man/figures/r_dplyr_logo.png) 241 | 242 | 243 | ## `fabric_text()` Render text elements in canvas 244 | *** 245 | 246 | The `fabric_text()` function has many arguments, feel free to check them out: 247 | 248 | ```{r, eval=FALSE} 249 | 250 | ui <- fluidPage( 251 | 252 | fabric_text(cid = "cId", 253 | textId = "text", 254 | text = " 'But A Hero Is A Guy Who Gives Out The Meat To Everyone Else. \\n I Want To Eat The Damn Meat!' \\n Monkey D. Luffy", 255 | cfill = "#DD5347", 256 | left = 120, 257 | shadowCol = "blue", 258 | fontSize = 20, 259 | fontWeight = "bold", 260 | lineHeight = 3 261 | ) 262 | ) 263 | server <- function(input, output) {} 264 | 265 | shinyApp(ui = ui, server = server) 266 | 267 | ``` 268 | 269 | ![](man/figures/example6.gif) 270 | 271 | Here also, we can use the `fabric_text_add()` function to incorporate a text object within a canvas element: 272 | 273 | 274 | 275 | ```{r, eval=FALSE} 276 | 277 | library(shiny) 278 | library(fabricerin) 279 | 280 | 281 | ui <- fluidPage( 282 | 283 | fabric_shape(cid = "canvas123", 284 | cfill = "lightblue", 285 | cwidth = 1000, 286 | shapeId = "tri1", 287 | shape = "Triangle", 288 | fill = "darkblue"), 289 | 290 | fabric_text_add(cid = "canvas123", 291 | textId = "txt1", 292 | text = "This is a darkblue Triangle !", 293 | left = 350 294 | ) 295 | 296 | ) 297 | 298 | server <- function(input, output) {} 299 | 300 | shinyApp(ui = ui, server = server) 301 | 302 | 303 | ``` 304 | 305 | ![](man/figures/example7.png) 306 | 307 | 308 | 309 | ## `fabric_curtail()` Add a background or an overlay image to a canvas 310 | 311 | 312 | 313 | You can set an image as a background or as a foreground (overlay) for a canvas as follows: 314 | 315 | > Note that due to security reasons, you won't be able to replicate the following example on some images' sources. 316 | 317 | 318 | ```{r, eval=FALSE} 319 | 320 | ui <- fluidPage( 321 | 322 | fabric_shape(cid = "canvas123", 323 | shapeId = "tri1", 324 | shape = "Triangle", 325 | fill = "lightblue"), 326 | 327 | fabric_curtail(cid = "canvas123", 328 | imgsrc = "https://st.depositphotos.com/1642482/1904/i/950/depositphotos_19049237-stock-photo-leaf.jpg", 329 | type = "background" 330 | 331 | ) 332 | 333 | ) 334 | 335 | server <- function(input, output) {} 336 | 337 | 338 | shinyApp(ui = ui, server = server) 339 | 340 | ``` 341 | 342 | 343 | ![](man/figures/example8.png) 344 | 345 | 346 | 347 | 348 | ## Code of Conduct 349 | 350 | Please note that the fabricerin project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/0/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. 351 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # fabricerin 5 | 6 | 7 | 8 | [![Travis build 9 | status](https://travis-ci.com/feddelegrand7/fabricerin.svg?branch=master)](https://travis-ci.com/feddelegrand7/fabricerin) 10 | [![CRAN\_time\_from\_release](https://www.r-pkg.org/badges/ago/fabricerin)](https://cran.r-project.org/package=fabricerin) 11 | [![CRAN\_latest\_release\_date](https://www.r-pkg.org/badges/last-release/fabricerin)](https://cran.r-project.org/package=fabricerin) 12 | [![metacran 13 | downloads](https://cranlogs.r-pkg.org/badges/fabricerin)](https://cran.r-project.org/package=fabricerin) 14 | [![metacran 15 | downloads](https://cranlogs.r-pkg.org/badges/grand-total/fabricerin)](https://cran.r-project.org/package=fabricerin) 16 | [![License: AGPL 17 | v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0) 18 | [![R 19 | badge](https://img.shields.io/badge/Build%20with-♥%20JS%20and%20R-pink)](https://github.com/feddelegrand7/fabricerin) 20 | 21 | 22 | 23 | The `fabricerin` (spelled **fabrikerine**) package allows you to create 24 | easily canvas elements within your Shiny app and RMarkdown documents. 25 | Thanks to [Garrick Aden-Buie](https://twitter.com/grrrck?lang=en), you 26 | can also use it within your 27 | [xaringan](https://github.com/yihui/xaringan) slides. You can use the 28 | canvas to render shapes, images and text. You can also create a canvas 29 | for drawing/taking notes purposes. Under the hoods, `fabricerin` relies 30 | on the [fabricjs](http://fabricjs.com/) JavaScript library. 31 | 32 | ## Installation 33 | 34 | You can install `fabricerin` from 35 | [CRAN](https://CRAN.R-project.org/package=fabricerin) with: 36 | 37 | ``` r 38 | install.packages("fabricerin") 39 | ``` 40 | 41 | You can install the development version from 42 | [GitHub](https://github.com/) with: 43 | 44 | ``` r 45 | # install.packages("remotes") 46 | 47 | remotes::install_github("feddelegrand7/fabricerin") 48 | ``` 49 | 50 | ## Examples: 51 | 52 | First of all, I’d like to state that all the example provided apply the 53 | same way to Shiny and Rmd documents. `fabricerin` is not an R wrapper 54 | for the fabricjs library. The package doesn’t cover all the capabilities 55 | of the library. The `fabricerin` package relies only on some specified 56 | features that according to me will help Shiny/Rmd users. Of course, if 57 | you need some improvement, feel free to create a Pull Request. 58 | 59 | ### `fabric_drawing()` Create a canvas for taking notes 60 | 61 | ------------------------------------------------------------------------ 62 | 63 | `fabric_drawing()` is pretty useful when you want to teach something and 64 | write some notes at the same time, below I provide an example using the 65 | `xaringan` package. Inside a `xaringan` slide you can just (for example) 66 | run R code in the left and take notes in the right: 67 | 68 | > Important: When you change a color, make sure that the **erase** box 69 | > is not checked. 70 | 71 | ![](man/figures/t_example.gif) 72 | 73 | `fabric_drawing()` can be used the same way in Shiny: 74 | 75 | ``` r 76 | library(shiny) 77 | library(fabricerin) 78 | 79 | 80 | ui <- fluidPage( 81 | 82 | 83 | fabric_drawing(cid = "canvas123") 84 | 85 | ) 86 | 87 | server <- function(input, output){} 88 | 89 | shinyApp(ui, server) 90 | ``` 91 | 92 | ![](man/figures/example1.gif) 93 | 94 | ## `fabric_shape()` Render shape objects in canvas 95 | 96 | ------------------------------------------------------------------------ 97 | 98 | Currently, `fabricerin` supports three types of shapes: Rectangle, 99 | Triangle, Circle and Polygon. The user can interact with the shape and 100 | modify its position, size and rotation. If you want to disable this 101 | interactivity, you can set `selectable =FALSE` 102 | 103 | ``` r 104 | library(shiny) 105 | library(fabricerin) 106 | 107 | 108 | ui <- fluidPage( 109 | 110 | 111 | fabric_shape(cid = "canvaId", # canvas id 112 | cfill = "orange", # canvas color 113 | cwidth = 800, # the width of the canvas 114 | cheight = 600, # the height of the canvas 115 | shapeId = "shapeId", # shape id 116 | shape = "Rect", 117 | fill = "red", # shape color 118 | width = 400, 119 | height = 400, 120 | left = 100, # the position of the shape from the left relative to the canvas 121 | top = 100, # the position of the shape from the top relative to the canvas 122 | strokecolor = "darkblue", 123 | strokewidth = 5, 124 | selectable = TRUE) 125 | 126 | ) 127 | 128 | server <- function(input, output){} 129 | 130 | shinyApp(ui, server) 131 | ``` 132 | 133 | ![](man/figures/example3.gif) 134 | 135 | You can add as many shape as you want to an existing canvas using the 136 | `fabric_shape_add()` function. **Don’t forget to reference the 137 | preexisting canvas with its ID:** 138 | 139 | ``` r 140 | library(shiny) 141 | library(fabricerin) 142 | 143 | 144 | ui <- fluidPage( 145 | 146 | 147 | fabric_shape(cid = "canvaId", 148 | shapeId = "cr1", 149 | shape = "Circle", 150 | radius = 30, 151 | left = 100), 152 | 153 | fabric_shape_add(cid = "canvaId", 154 | shapeId = "cr2", 155 | shape = "Circle", 156 | radius = 30, 157 | left = 200), 158 | 159 | fabric_shape_add(cid = "canvaId", 160 | shapeId = "cr3", 161 | shape = "Circle", 162 | radius = 30, 163 | left = 300), 164 | 165 | fabric_shape_add(cid = "canvaId", 166 | shapeId = "cr4", 167 | shape = "Circle", 168 | radius = 30, 169 | left = 400) 170 | 171 | ) 172 | 173 | server <- function(input, output){} 174 | 175 | shinyApp(ui, server) 176 | ``` 177 | 178 | ![](man/figures/example4.png) 179 | 180 | ## `fabric_image()` Render images in canvas 181 | 182 | ------------------------------------------------------------------------ 183 | 184 | You can insert an image within a canvas a play with it using the 185 | `fabric_image()` function. Note that this function accepts only URL 186 | external images. 187 | 188 | ``` r 189 | ui <- fluidPage( 190 | 191 | fabric_image(cid = "cimage", 192 | cfill = "lightblue", 193 | imgId = "Rimg", 194 | imgsrc = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/R_logo.svg/724px-R_logo.svg.png") 195 | 196 | ) 197 | 198 | server <- function(input, output) {} 199 | 200 | 201 | shinyApp(ui = ui, server = server) 202 | ``` 203 | 204 | ![](man/figures/example5.gif) 205 | 206 | Similar to shapes, you can add images to preexisting canvas using the 207 | `fabric_image_add()` function: 208 | 209 | ``` r 210 | library(shiny) 211 | library(fabricerin) 212 | 213 | ui <- fluidPage( 214 | 215 | fabric_image(cid = "cimage", 216 | imgId = "Rimg", 217 | imgsrc = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/R_logo.svg/724px-R_logo.svg.png", 218 | imgheight = 200, 219 | imgwidth = 200), 220 | fabric_image_add(cid = "cimage", 221 | imgId = "rstudioimg", 222 | imgsrc = "https://raw.githubusercontent.com/rstudio/hex-stickers/master/PNG/dplyr.png", 223 | imgwidth = 200, 224 | imgheight = 200, 225 | left = 400) 226 | ) 227 | 228 | server <- function(input, output) {} 229 | 230 | shinyApp(ui = ui, server = server) 231 | ``` 232 | 233 | ![](man/figures/r_dplyr_logo.png) 234 | 235 | ## `fabric_text()` Render text elements in canvas 236 | 237 | ------------------------------------------------------------------------ 238 | 239 | The `fabric_text()` function has many arguments, feel free to check them 240 | out: 241 | 242 | ``` r 243 | ui <- fluidPage( 244 | 245 | fabric_text(cid = "cId", 246 | textId = "text", 247 | text = " 'But A Hero Is A Guy Who Gives Out The Meat To Everyone Else. \\n I Want To Eat The Damn Meat!' \\n Monkey D. Luffy", 248 | cfill = "#DD5347", 249 | left = 120, 250 | shadowCol = "blue", 251 | fontSize = 20, 252 | fontWeight = "bold", 253 | lineHeight = 3 254 | ) 255 | ) 256 | server <- function(input, output) {} 257 | 258 | shinyApp(ui = ui, server = server) 259 | ``` 260 | 261 | ![](man/figures/example6.gif) 262 | 263 | Here also, we can use the `fabric_text_add()` function to incorporate a 264 | text object within a canvas element: 265 | 266 | ``` r 267 | library(shiny) 268 | library(fabricerin) 269 | 270 | 271 | ui <- fluidPage( 272 | 273 | fabric_shape(cid = "canvas123", 274 | cfill = "lightblue", 275 | cwidth = 1000, 276 | shapeId = "tri1", 277 | shape = "Triangle", 278 | fill = "darkblue"), 279 | 280 | fabric_text_add(cid = "canvas123", 281 | textId = "txt1", 282 | text = "This is a darkblue Triangle !", 283 | left = 350 284 | ) 285 | 286 | ) 287 | 288 | server <- function(input, output) {} 289 | 290 | shinyApp(ui = ui, server = server) 291 | ``` 292 | 293 | ![](man/figures/example7.png) 294 | 295 | ## `fabric_curtail()` Add a background or an overlay image to a canvas 296 | 297 | You can set an image as a background or as a foreground (overlay) for a 298 | canvas as follows: 299 | 300 | > Note that due to security reasons, you won’t be able to replicate the 301 | > following example on some images’ sources. 302 | 303 | ``` r 304 | ui <- fluidPage( 305 | 306 | fabric_shape(cid = "canvas123", 307 | shapeId = "tri1", 308 | shape = "Triangle", 309 | fill = "lightblue"), 310 | 311 | fabric_curtail(cid = "canvas123", 312 | imgsrc = "https://st.depositphotos.com/1642482/1904/i/950/depositphotos_19049237-stock-photo-leaf.jpg", 313 | type = "background" 314 | 315 | ) 316 | 317 | ) 318 | 319 | server <- function(input, output) {} 320 | 321 | 322 | shinyApp(ui = ui, server = server) 323 | ``` 324 | 325 | ![](man/figures/example8.png) 326 | 327 | ## Code of Conduct 328 | 329 | Please note that the fabricerin project is released with a [Contributor 330 | Code of 331 | Conduct](https://contributor-covenant.org/version/2/0/CODE_OF_CONDUCT.html). 332 | By contributing to this project, you agree to abide by its terms. 333 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | * local R installation, R 4.0.2 3 | * ubuntu 16.04 (on travis-ci), R 4.0.2 4 | * win-builder (devel) 5 | 6 | ## R CMD check results 7 | 8 | 9 | -- R CMD check results fabricerin 0.1.1-- 10 | Duration: 1m 10.1s 11 | 12 | 0 errors √ | 0 warnings √ | 0 notes √ 13 | 14 | 15 | - fabric, jQuery and FileSaver dependencies set using the htmlDependency() function. The user doesn't need an internet connexion to run the JS dependencies. 16 | - Possibility to use the packge smoothly with xaringan and other slides RMarkdown format 17 | - Bugs fixed. 18 | - Possibility to choose a color for the drawing. 19 | - Possibility to export a canvas that contains an image. 20 | -------------------------------------------------------------------------------- /fabricerin.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | -------------------------------------------------------------------------------- /inst/fabric/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | (function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)}); 2 | 3 | //# sourceMappingURL=FileSaver.min.js.map -------------------------------------------------------------------------------- /inst/fabric/jquery-3.5.1.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0