| 1 | # always use LF line endings | |
| 2 | ||
| 3 | # ALL FILES: | |
| 4 | # Normalize line endings to LF on checkin and | |
| 5 | # prevent conversion to CRLF when the file is checked out. | |
| 6 | ||
| 7 | * text eol=lf | |
| 8 | ||
| 9 | ||
| 10 | # BINARY FILES: | |
| 11 | # Disable line ending normalize on checkin. | |
| 12 | ||
| 13 | *.blend binary | |
| 14 | ||
| 15 | *.bin binary | |
| 16 | *.bmp binary | |
| 17 | *.eps binary | |
| 18 | *.exe binary | |
| 19 | *.gif binary | |
| 20 | *.jar binary | |
| 21 | *.jpg binary | |
| 22 | *.mng binary | |
| 23 | *.png binary | |
| 24 | *.zip binary | |
| 25 | *.otf binary | |
| 26 | *.ttf binary | |
| 27 | ||
| 1 | 28 |
| 1 | --- | |
| 2 | name: Bug report | |
| 3 | about: Create a report to help us improve | |
| 4 | title: '' | |
| 5 | labels: bug | |
| 6 | assignees: '' | |
| 7 | ||
| 8 | --- | |
| 9 | ||
| 10 | **Description** | |
| 11 | A concise problem description. | |
| 12 | ||
| 13 | **Replicate** | |
| 14 | Exact and complete steps to reproduce the problem 100% of the time: | |
| 15 | ||
| 16 | 1. Open '...' | |
| 17 | 1. Click '....' | |
| 18 | 1. Click '....' | |
| 19 | ||
| 20 | **Expected** | |
| 21 | Describe the expected behaviour. | |
| 22 | ||
| 23 | **Actual** | |
| 24 | Describe the actual behaviour. | |
| 25 | ||
| 26 | **Screenshots** | |
| 27 | Add screenshots to show the problem, if applicable. | |
| 28 | ||
| 29 | **Environment** | |
| 30 | - Operating System: (Windows, Linux, Mac) | |
| 31 | - Application: e.g., 1.7.16 | |
| 32 | ||
| 33 | **Details** | |
| 34 | Add additional information, if applicable. | |
| 1 | 35 |
| 1 | dist | |
| 2 | *.bin | |
| 3 | *.exe | |
| 4 | /*.jar | |
| 5 | build | |
| 6 | .gradle | |
| 7 | contacted.csv | |
| 8 | video | |
| 9 | .settings | |
| 10 | .classpath | |
| 11 | .idea | |
| 12 | themes | |
| 1 | 13 |
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <projectDescription> | |
| 3 | <name>Markdown Writer FX</name> | |
| 4 | <comment></comment> | |
| 5 | <projects> | |
| 6 | </projects> | |
| 7 | <buildSpec> | |
| 8 | <buildCommand> | |
| 9 | <name>org.eclipse.jdt.core.javabuilder</name> | |
| 10 | <arguments> | |
| 11 | </arguments> | |
| 12 | </buildCommand> | |
| 13 | <buildCommand> | |
| 14 | <name>org.eclipse.buildship.core.gradleprojectbuilder</name> | |
| 15 | <arguments> | |
| 16 | </arguments> | |
| 17 | </buildCommand> | |
| 18 | </buildSpec> | |
| 19 | <natures> | |
| 20 | <nature>org.eclipse.jdt.core.javanature</nature> | |
| 21 | <nature>org.eclipse.buildship.core.gradleprojectnature</nature> | |
| 22 | </natures> | |
| 23 | <filteredResources> | |
| 24 | <filter> | |
| 25 | <id>1438449113801</id> | |
| 26 | <name></name> | |
| 27 | <type>26</type> | |
| 28 | <matcher> | |
| 29 | <id>org.eclipse.ui.ide.multiFilter</id> | |
| 30 | <arguments>1.0-projectRelativePath-matches-false-false-build</arguments> | |
| 31 | </matcher> | |
| 32 | </filter> | |
| 33 | <filter> | |
| 34 | <id>1438449113801</id> | |
| 35 | <name></name> | |
| 36 | <type>26</type> | |
| 37 | <matcher> | |
| 38 | <id>org.eclipse.ui.ide.multiFilter</id> | |
| 39 | <arguments>1.0-projectRelativePath-matches-false-false-.gradle</arguments> | |
| 40 | </matcher> | |
| 41 | </filter> | |
| 42 | </filteredResources> | |
| 43 | </projectDescription> | |
| 1 | 44 |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to build the application and platform binaries. | |
| 4 | ||
| 5 | # Requirements | |
| 6 | ||
| 7 | Download and install the following software packages: | |
| 8 | ||
| 9 | * [JDK 16](https://bell-sw.com/pages/downloads/?version=java-16) (Full JDK + JavaFX) | |
| 10 | * [Gradle 7.0](https://services.gradle.org/distributions) | |
| 11 | * [Git 2.28.0](https://git-scm.com/downloads) | |
| 12 | ||
| 13 | ## Repository | |
| 14 | ||
| 15 | Clone the repository as follows: | |
| 16 | ||
| 17 | git clone https://github.com/DaveJarvis/keenwrite.git | |
| 18 | ||
| 19 | The repository is cloned. | |
| 20 | ||
| 21 | # Build | |
| 22 | ||
| 23 | Build the application überjar as follows: | |
| 24 | ||
| 25 | cd keenwrite | |
| 26 | gradle clean jar | |
| 27 | ||
| 28 | The application is built. | |
| 29 | ||
| 30 | # Run | |
| 31 | ||
| 32 | After the application is compiled, run it as follows: | |
| 33 | ||
| 34 | java --illegal-access=permit -jar build/libs/keenwrite.jar | |
| 35 | ||
| 36 | On Windows: | |
| 37 | ||
| 38 | java --illegal-access=permit -jar build\libs\keenwrite.jar | |
| 39 | ||
| 40 | # Integrated development environments | |
| 41 | ||
| 42 | This section describes setup instructions to import and run the application using an integrated development environment (IDE). Running the application should trigger a build. | |
| 43 | ||
| 44 | ## IntelliJ IDEA | |
| 45 | ||
| 46 | This section describes how to build and run the application using IntellIJ's IDEA. | |
| 47 | ||
| 48 | ### Import | |
| 49 | ||
| 50 | Complete the following steps to import the application: | |
| 51 | ||
| 52 | 1. Start the IDE. | |
| 53 | 1. Click **File → New → Project from Existing Sources**. | |
| 54 | 1. Browse to the directory containing `keenwrite`. | |
| 55 | 1. Click **OK**. | |
| 56 | 1. Select **Gradle** as the external model. | |
| 57 | 1. Click **Finish**. | |
| 58 | ||
| 59 | The project is imported into the IDE. | |
| 60 | ||
| 61 | ### Configure | |
| 62 | ||
| 63 | Configure the IDE to run the application as follows: | |
| 64 | ||
| 65 | 1. Click **Run → Edit Configurations**. | |
| 66 | 1. Click **+** to add a new configuration. | |
| 67 | 1. Set **Name** to: KeenWrite | |
| 68 | 1. Click **Modify Options → Add VM options**. | |
| 69 | 1. Set **VM options** field to: `--illegal-access=permit` | |
| 70 | 1. Click **OK** close the dialog. | |
| 71 | ||
| 72 | The changes should resemble: | |
| 73 | ||
| 74 |  | |
| 75 | ||
| 76 | ### Run | |
| 77 | ||
| 78 | Click **Run → KeenWrite** to launch the application. | |
| 79 | ||
| 80 | # Installers | |
| 81 | ||
| 82 | This section describes how to set up the development environment and build native executables for supported operating systems. | |
| 83 | ||
| 84 | ## Setup | |
| 85 | ||
| 86 | Follow these one-time setup instructions to begin: | |
| 87 | ||
| 88 | 1. Ensure `$HOME/bin` is set in the `PATH` environment variable. | |
| 89 | 1. Copy `build-template` into `$HOME/bin`. | |
| 90 | ||
| 91 | Setup is complete. | |
| 92 | ||
| 93 | ## Binaries | |
| 94 | ||
| 95 | Run the `installer` script to build platform-specific binaries, such as: | |
| 96 | ||
| 97 | ./installer -V -o linux | |
| 98 | ||
| 99 | The `installer` script: | |
| 100 | ||
| 101 | * downloads a JDK; | |
| 102 | * generates a run script; | |
| 103 | * bundles the JDK, run script, and JAR file; and | |
| 104 | * creates a standalone binary, so no installation required. | |
| 105 | ||
| 106 | Run `./installer -h` to see all command-line options. | |
| 107 | ||
| 108 | # Releases | |
| 109 | ||
| 110 | After installing `scripts/build-template`, build release binaries as follows: | |
| 111 | ||
| 112 | git tag -a 2.0.0 -m "Release name" | |
| 113 | git push origin --tags | |
| 114 | ./release.sh | |
| 115 | ||
| 116 | When finished, browse to the project releases page to draft a new release. | |
| 117 | ||
| 118 | # Versioning | |
| 119 | ||
| 120 | Version numbers are read directly from Git using a plugin. The version number is written to `app.properties` in the `resources` directory. The application reads that file to display version information upon start. | |
| 121 | ||
| 1 | 122 |
| 1 | # Contributor Covenant Code of Conduct | |
| 2 | ||
| 3 | ## Our Pledge | |
| 4 | ||
| 5 | In the interest of fostering an open and welcoming environment, we as | |
| 6 | contributors and maintainers pledge to making participation in our project and | |
| 7 | our community a harassment-free experience for everyone, regardless of age, body | |
| 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, | |
| 9 | level of experience, education, socio-economic status, nationality, personal | |
| 10 | appearance, race, religion, or sexual identity and orientation. | |
| 11 | ||
| 12 | ## Our Standards | |
| 13 | ||
| 14 | Examples of behavior that contributes to creating a positive environment | |
| 15 | include: | |
| 16 | ||
| 17 | * Using welcoming and inclusive language | |
| 18 | * Being respectful of differing viewpoints and experiences | |
| 19 | * Gracefully accepting constructive criticism | |
| 20 | * Focusing on what is best for the community | |
| 21 | * Showing empathy towards other community members | |
| 22 | ||
| 23 | Examples of unacceptable behavior by participants include: | |
| 24 | ||
| 25 | * The use of sexualized language or imagery and unwelcome sexual attention or | |
| 26 | advances | |
| 27 | * Trolling, insulting/derogatory comments, and personal or political attacks | |
| 28 | * Public or private harassment | |
| 29 | * Publishing others' private information, such as a physical or electronic | |
| 30 | address, without explicit permission | |
| 31 | * Other conduct which could reasonably be considered inappropriate in a | |
| 32 | professional setting | |
| 33 | ||
| 34 | ## Our Responsibilities | |
| 35 | ||
| 36 | Project maintainers are responsible for clarifying the standards of acceptable | |
| 37 | behavior and are expected to take appropriate and fair corrective action in | |
| 38 | response to any instances of unacceptable behavior. | |
| 39 | ||
| 40 | Project maintainers have the right and responsibility to remove, edit, or | |
| 41 | reject comments, commits, code, wiki edits, issues, and other contributions | |
| 42 | that are not aligned to this Code of Conduct, or to ban temporarily or | |
| 43 | permanently any contributor for other behaviors that they deem inappropriate, | |
| 44 | threatening, offensive, or harmful. | |
| 45 | ||
| 46 | ## Scope | |
| 47 | ||
| 48 | This Code of Conduct applies both within project spaces and in public spaces | |
| 49 | when an individual is representing the project or its community. Examples of | |
| 50 | representing a project or community include using an official project e-mail | |
| 51 | address, posting via an official social media account, or acting as an appointed | |
| 52 | representative at an online or offline event. Representation of a project may be | |
| 53 | further defined and clarified by project maintainers. | |
| 54 | ||
| 55 | ## Enforcement | |
| 56 | ||
| 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be | |
| 58 | reported by contacting the project team at Dave.Jarvis@gmail.com. All | |
| 59 | complaints will be reviewed and investigated and will result in a response that | |
| 60 | is deemed necessary and appropriate to the circumstances. The project team is | |
| 61 | obligated to maintain confidentiality with regard to the reporter of an incident. | |
| 62 | Further details of specific enforcement policies may be posted separately. | |
| 63 | ||
| 64 | Project maintainers who do not follow or enforce the Code of Conduct in good | |
| 65 | faith may face temporary or permanent repercussions as determined by other | |
| 66 | members of the project's leadership. | |
| 67 | ||
| 68 | ## Attribution | |
| 69 | ||
| 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, | |
| 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html | |
| 72 | ||
| 73 | [homepage]: https://www.contributor-covenant.org | |
| 74 | ||
| 75 | For answers to common questions about this code of conduct, see | |
| 76 | https://www.contributor-covenant.org/faq | |
| 1 | 77 |
| 1 | # License | |
| 2 | ||
| 3 | Copyright 2020 White Magic Software, Ltd. | |
| 4 | ||
| 5 | Copyright 2015 Karl Tauber | |
| 6 | ||
| 7 | All rights reserved. | |
| 8 | ||
| 9 | Redistribution and use in source and binary forms, with or without | |
| 10 | modification, are permitted provided that the following conditions are met: | |
| 11 | ||
| 12 | * Redistributions of source code must retain the above copyright | |
| 13 | notice, this list of conditions and the following disclaimer. | |
| 14 | ||
| 15 | * Redistributions in binary form must reproduce the above copyright | |
| 16 | notice, this list of conditions and the following disclaimer in the | |
| 17 | documentation and/or other materials provided with the distribution. | |
| 18 | ||
| 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 23 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 30 |
| 1 | # R Functions | |
| 2 | ||
| 3 | Import the files in this directory into the application, which include: | |
| 4 | ||
| 5 | * pluralize.R | |
| 6 | * possessive.R | |
| 7 | * conversion.R | |
| 8 | * csv.R | |
| 9 | ||
| 10 | # pluralize.R | |
| 11 | ||
| 12 | This file defines a function that implements most of Damian Conway's [An Algorithmic Approach to English Pluralization](http://blob.perl.org/tpc/1998/User_Applications/Algorithmic%20Approach%20Plurals/Algorithmic_Plurals.html). | |
| 13 | ||
| 14 | ## Usage | |
| 15 | ||
| 16 | Example usages of the pluralize function include: | |
| 17 | ||
| 18 | `r#pluralize( 'mouse' )` - mice | |
| 19 | `r#pluralize( 'buzz' )` - buzzes | |
| 20 | `r#pluralize( 'bus' )` - busses | |
| 21 | ||
| 22 | # possessive.R | |
| 23 | ||
| 24 | This file defines a function that applies possessives to English words. | |
| 25 | ||
| 26 | ## Usage | |
| 27 | ||
| 28 | Example usages of the possessive function include: | |
| 29 | ||
| 30 | `r#pos( 'Ross' )` - Ross' | |
| 31 | `r#pos( 'Ruby' )` - Ruby's | |
| 32 | `r#pos( 'Lois' )` - Lois' | |
| 33 | `r#pos( 'my' )` - mine | |
| 34 | `r#pos( 'Your' )` - Yours | |
| 35 | ||
| 1 | 36 |
| 1 | setwd( '{{application.r.working.directory}}' ) | |
| 2 | assign( "anchor", '{{date.anchor}}', envir = .GlobalEnv ) | |
| 3 | ||
| 4 | source( 'pluralize.R' ) | |
| 5 | source( 'possessive.R' ) | |
| 6 | source( 'conversion.R' ) | |
| 7 | source( 'csv.R' ) | |
| 8 | ||
| 1 | 9 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Substitute R expressions in a document with their evaluated value. The | |
| 26 | # anchor variable must be set for functions that use relative dates. | |
| 27 | # ----------------------------------------------------------------------------- | |
| 28 | ||
| 29 | # ----------------------------------------------------------------------------- | |
| 30 | # Evaluates an expression; writes s if there is no expression. | |
| 31 | # ----------------------------------------------------------------------------- | |
| 32 | x <- function( s ) { | |
| 33 | tryCatch( { | |
| 34 | r = eval( parse( text = s ) ) | |
| 35 | ||
| 36 | # If the result isn't primitive, then it was probably parsed into | |
| 37 | # an unprintable object (e.g., "gray" becomes a colour). In those | |
| 38 | # cases, return the original text string. Otherwise, an atomic | |
| 39 | # value means a primitive type (string, integer, etc.) that can be | |
| 40 | # written directly into the document. | |
| 41 | ifelse( is.atomic( r ), r, s ); | |
| 42 | }, | |
| 43 | warning = function( w ) { s }, | |
| 44 | error = function( e ) { s } ) | |
| 45 | } | |
| 46 | ||
| 47 | # ----------------------------------------------------------------------------- | |
| 48 | # Returns a date offset by a given number of days, relative to the given | |
| 49 | # date (d). This does not use the anchor, but is used to get the anchor's | |
| 50 | # value as a date. | |
| 51 | # ----------------------------------------------------------------------------- | |
| 52 | when <- function( d, n = 0, format = "%Y-%m-%d" ) { | |
| 53 | as.Date( d, format = format ) + x( n ) | |
| 54 | } | |
| 55 | ||
| 56 | # ----------------------------------------------------------------------------- | |
| 57 | # Full date (s) offset by an optional number of days before or after. | |
| 58 | # This will remove leading zeros (applying leading spaces instead, which | |
| 59 | # are ignored by any worthwhile typesetting engine). | |
| 60 | # ----------------------------------------------------------------------------- | |
| 61 | annal <- function( days = 0, format = "%Y-%m-%d", oformat = "%B %d, %Y" ) { | |
| 62 | format( when( anchor, days ), format = oformat ) | |
| 63 | } | |
| 64 | ||
| 65 | # ----------------------------------------------------------------------------- | |
| 66 | # Extracts the year from a date string. | |
| 67 | # ----------------------------------------------------------------------------- | |
| 68 | year <- function( days = 0, format = "%Y-%m-%d" ) { | |
| 69 | annal( days, format, "%Y" ) | |
| 70 | } | |
| 71 | ||
| 72 | # ----------------------------------------------------------------------------- | |
| 73 | # Day of the week (in days since the anchor date). | |
| 74 | # ----------------------------------------------------------------------------- | |
| 75 | weekday <- function( n ) { | |
| 76 | weekdays( when( anchor, n ) ) | |
| 77 | } | |
| 78 | ||
| 79 | # ----------------------------------------------------------------------------- | |
| 80 | # String concatenate function alias because paste0 is a terrible name. | |
| 81 | # ----------------------------------------------------------------------------- | |
| 82 | concat <- paste0 | |
| 83 | ||
| 84 | # ----------------------------------------------------------------------------- | |
| 85 | # Translates a number from digits to words using Chicago Manual of Style. | |
| 86 | # This does not translate numbers greater than one hundred. If ordinal | |
| 87 | # is TRUE, this will return the ordinal name. This will not produce ordinals | |
| 88 | # for numbers greater than 100. | |
| 89 | # ----------------------------------------------------------------------------- | |
| 90 | cms <- function( n, ordinal = FALSE ) { | |
| 91 | n <- x( n ) | |
| 92 | ||
| 93 | if( n == 0 ) { | |
| 94 | if( ordinal ) { | |
| 95 | return( "zeroth" ) | |
| 96 | } | |
| 97 | ||
| 98 | return( "zero" ) | |
| 99 | } | |
| 100 | ||
| 101 | # Concatenate this a little later. | |
| 102 | if( n < 0 ) { | |
| 103 | result = "negative " | |
| 104 | n = abs( n ) | |
| 105 | } | |
| 106 | ||
| 107 | # Do not spell out numbers greater than one hundred. | |
| 108 | if( n > 100 ) { | |
| 109 | # Comma-separated numbers. | |
| 110 | return( commas( n ) ) | |
| 111 | } | |
| 112 | ||
| 113 | # Don't go beyond 100. | |
| 114 | if( n == 100 ) { | |
| 115 | if( ordinal ) { | |
| 116 | return( "one hundredth" ) | |
| 117 | } | |
| 118 | ||
| 119 | return( "one hundred" ) | |
| 120 | } | |
| 121 | ||
| 122 | # Samuel Langhorne Clemens noted English has too many exceptions. | |
| 123 | small = c( | |
| 124 | "one", "two", "three", "four", "five", | |
| 125 | "six", "seven", "eight", "nine", "ten", | |
| 126 | "eleven", "twelve", "thirteen", "fourteen", "fifteen", | |
| 127 | "sixteen", "seventeen", "eighteen", "nineteen" | |
| 128 | ) | |
| 129 | ||
| 130 | ord_small = c( | |
| 131 | "first", "second", "third", "fourth", "fifth", | |
| 132 | "sixth", "seventh", "eighth", "ninth", "tenth", | |
| 133 | "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", | |
| 134 | "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth" | |
| 135 | ) | |
| 136 | ||
| 137 | # After this, the number (n) is between 20 and 99. | |
| 138 | if( n < 20 ) { | |
| 139 | if( ordinal ) { | |
| 140 | return( .subset( ord_small, n %% 100 ) ) | |
| 141 | } | |
| 142 | ||
| 143 | return( .subset( small, n %% 100 ) ) | |
| 144 | } | |
| 145 | ||
| 146 | tens = c( "", | |
| 147 | "twenty", "thirty", "forty", "fifty", | |
| 148 | "sixty", "seventy", "eighty", "ninety" | |
| 149 | ) | |
| 150 | ||
| 151 | ord_tens = c( "", | |
| 152 | "twentieth", "thirtieth", "fortieth", "fiftieth", | |
| 153 | "sixtieth", "seventieth", "eightieth", "ninetieth" | |
| 154 | ) | |
| 155 | ||
| 156 | ones_index = n %% 10 | |
| 157 | n = n %/% 10 | |
| 158 | ||
| 159 | # No number in the ones column, so the number must be a multiple of ten. | |
| 160 | if( ones_index == 0 ) { | |
| 161 | if( ordinal ) { | |
| 162 | return( .subset( ord_tens, n ) ) | |
| 163 | } | |
| 164 | ||
| 165 | return( .subset( tens, n ) ) | |
| 166 | } | |
| 167 | ||
| 168 | # Find the value from the ones column. | |
| 169 | if( ordinal ) { | |
| 170 | unit_1 = .subset( ord_small, ones_index ) | |
| 171 | } | |
| 172 | else { | |
| 173 | unit_1 = .subset( small, ones_index ) | |
| 174 | } | |
| 175 | ||
| 176 | # Find the tens column. | |
| 177 | unit_10 = .subset( tens, n ) | |
| 178 | ||
| 179 | # Hyphenate the tens and the ones together. | |
| 180 | concat( unit_10, concat( "-", unit_1 ) ) | |
| 181 | } | |
| 182 | ||
| 183 | # ----------------------------------------------------------------------------- | |
| 184 | # Returns a number as a comma-delimited string. This is a work-around | |
| 185 | # until Renjin fixes https://github.com/bedatadriven/renjin/issues/338 | |
| 186 | # ----------------------------------------------------------------------------- | |
| 187 | commas <- function( n ) { | |
| 188 | n <- x( n ) | |
| 189 | ||
| 190 | s <- sprintf( "%03.0f", n %% 1000 ) | |
| 191 | n <- n %/% 1000 | |
| 192 | ||
| 193 | while( n > 0 ) { | |
| 194 | s <- concat( sprintf( "%03.0f", n %% 1000 ), ',', s ) | |
| 195 | n <- n %/% 1000 | |
| 196 | } | |
| 197 | ||
| 198 | gsub( '^0*', '', s ) | |
| 199 | } | |
| 200 | ||
| 201 | # ----------------------------------------------------------------------------- | |
| 202 | # Returns a human-readable string that provides the elapsed time between | |
| 203 | # two numbers in terms of years, months, and days. If any unit value is zero, | |
| 204 | # the unit is not included. The words (year, month, day) are pluralized | |
| 205 | # according to English grammar. The numbers are written out according to | |
| 206 | # Chicago Manual of Style. This applies the serial comma. | |
| 207 | # | |
| 208 | # Both numbers are offsets relative to the anchor date. | |
| 209 | # | |
| 210 | # If all unit values are zero, this returns s ("same day" by default). | |
| 211 | # | |
| 212 | # If the start date (began) is greater than end date (ended), the dates are | |
| 213 | # swapped before calculations are performed. This allows any two dates | |
| 214 | # to be compared and positive unit values are always returned. | |
| 215 | # ----------------------------------------------------------------------------- | |
| 216 | elapsed <- function( began, ended, s = "same day" ) { | |
| 217 | began = when( anchor, began ) | |
| 218 | ended = when( anchor, ended ) | |
| 219 | ||
| 220 | # Swap the dates if the end date comes before the start date. | |
| 221 | if( as.integer( ended - began ) < 0 ) { | |
| 222 | tempd = began | |
| 223 | began = ended | |
| 224 | ended = tempd | |
| 225 | } | |
| 226 | ||
| 227 | # Calculate number of elapsed years. | |
| 228 | years = length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 229 | ||
| 230 | # Move the start date up by the number of elapsed years. | |
| 231 | if( years > 0 ) { | |
| 232 | began = seq( began, length = 2, by = concat( years, " years" ) )[2] | |
| 233 | years = pl.numeric( "year", years ) | |
| 234 | } | |
| 235 | else { | |
| 236 | # Zero years. | |
| 237 | years = "" | |
| 238 | } | |
| 239 | ||
| 240 | # Calculate number of elapsed months, excluding years. | |
| 241 | months = length( seq( from = began, to = ended, by = 'month' ) ) - 1 | |
| 242 | ||
| 243 | # Move the start date up by the number of elapsed months | |
| 244 | if( months > 0 ) { | |
| 245 | began = seq( began, length = 2, by = concat( months, " months" ) )[2] | |
| 246 | months = pl.numeric( "month", months ) | |
| 247 | } | |
| 248 | else { | |
| 249 | # Zero months | |
| 250 | months = "" | |
| 251 | } | |
| 252 | ||
| 253 | # Calculate number of elapsed days, excluding months and years. | |
| 254 | days = length( seq( from = began, to = ended, by = 'day' ) ) - 1 | |
| 255 | ||
| 256 | if( days > 0 ) { | |
| 257 | days = pl.numeric( "day", days ) | |
| 258 | } | |
| 259 | else { | |
| 260 | # Zero days | |
| 261 | days = "" | |
| 262 | } | |
| 263 | ||
| 264 | if( years <= 0 && months <= 0 && days <= 0 ) { | |
| 265 | return( s ) | |
| 266 | } | |
| 267 | ||
| 268 | # Put them all in a vector, then remove the empty values. | |
| 269 | s <- c( years, months, days ) | |
| 270 | s <- s[ s != "" ] | |
| 271 | ||
| 272 | r <- paste( s, collapse = ", " ) | |
| 273 | ||
| 274 | # If all three items are present, replace the last comma with ", and". | |
| 275 | if( length( s ) > 2 ) { | |
| 276 | return( gsub( "(.*),", "\\1, and", r ) ) | |
| 277 | } | |
| 278 | ||
| 279 | # Does nothing if no commas are present. | |
| 280 | gsub( "(.*),", "\\1 and", r ) | |
| 281 | } | |
| 282 | ||
| 283 | # ----------------------------------------------------------------------------- | |
| 284 | # Returns the number (n) in English followed by the plural or singular | |
| 285 | # form of the given string (s; resumably a noun), if applicable, according | |
| 286 | # to English grammar. That is, pl.numeric( "wolf", 5 ) will return | |
| 287 | # "five wolves". | |
| 288 | # ----------------------------------------------------------------------------- | |
| 289 | pl.numeric <- function( s, n ) { | |
| 290 | concat( cms( n ), concat( " ", pluralize( s, n ) ) ) | |
| 291 | } | |
| 292 | ||
| 293 | # ----------------------------------------------------------------------------- | |
| 294 | # Pluralize s if n is not equal to 1. | |
| 295 | # ----------------------------------------------------------------------------- | |
| 296 | pl <- function( s, n=2 ) { | |
| 297 | pluralize( s, x( n ) ) | |
| 298 | } | |
| 299 | ||
| 300 | # ----------------------------------------------------------------------------- | |
| 301 | # Name of the season, starting with an capital letter. | |
| 302 | # ----------------------------------------------------------------------------- | |
| 303 | season <- function( n, format = "%Y-%m-%d" ) { | |
| 304 | WS <- as.Date("2016-12-15", "%Y-%m-%d") # Winter Solstice | |
| 305 | SE <- as.Date("2016-03-15", "%Y-%m-%d") # Spring Equinox | |
| 306 | SS <- as.Date("2016-06-15", "%Y-%m-%d") # Summer Solstice | |
| 307 | AE <- as.Date("2016-09-15", "%Y-%m-%d") # Autumn Equinox | |
| 308 | ||
| 309 | d <- when( anchor, n ) | |
| 310 | d <- as.Date( strftime( d, format="2016-%m-%d" ) ) | |
| 311 | ||
| 312 | ifelse( d >= WS | d < SE, "Winter", | |
| 313 | ifelse( d >= SE & d < SS, "Spring", | |
| 314 | ifelse( d >= SS & d < AE, "Summer", "Autumn" ) | |
| 315 | ) | |
| 316 | ) | |
| 317 | } | |
| 318 | ||
| 319 | # ----------------------------------------------------------------------------- | |
| 320 | # Converts the first letter in a string to lowercase | |
| 321 | # ----------------------------------------------------------------------------- | |
| 322 | lc <- function( s ) { | |
| 323 | concat( tolower( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 324 | } | |
| 325 | ||
| 326 | # ----------------------------------------------------------------------------- | |
| 327 | # Converts the entire string to lowercase | |
| 328 | # ----------------------------------------------------------------------------- | |
| 329 | lower <- tolower | |
| 330 | ||
| 331 | # ----------------------------------------------------------------------------- | |
| 332 | # Converts the first letter in a string to uppercase | |
| 333 | # ----------------------------------------------------------------------------- | |
| 334 | uc <- function( s ) { | |
| 335 | concat( toupper( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 336 | } | |
| 337 | ||
| 338 | # ----------------------------------------------------------------------------- | |
| 339 | # Returns the number of days between the given dates. | |
| 340 | # ----------------------------------------------------------------------------- | |
| 341 | days <- function( d1, d2, format = "%Y-%m-%d" ) { | |
| 342 | dates = c( d1, d2 ) | |
| 343 | dt = strptime( dates, format = format ) | |
| 344 | as.integer( difftime( dates[2], dates[1], units = "days" ) ) | |
| 345 | } | |
| 346 | ||
| 347 | # ----------------------------------------------------------------------------- | |
| 348 | # Returns the number of years elapsed. | |
| 349 | # ----------------------------------------------------------------------------- | |
| 350 | years <- function( began, ended ) { | |
| 351 | began = when( anchor, began ) | |
| 352 | ended = when( anchor, ended ) | |
| 353 | ||
| 354 | # Swap the dates if the end date comes before the start date. | |
| 355 | if( as.integer( ended - began ) < 0 ) { | |
| 356 | tempd = began | |
| 357 | began = ended | |
| 358 | ended = tempd | |
| 359 | } | |
| 360 | ||
| 361 | # Calculate number of elapsed years. | |
| 362 | length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 363 | } | |
| 364 | ||
| 365 | # ----------------------------------------------------------------------------- | |
| 366 | # Full name of the month, starting with a capital letter. | |
| 367 | # ----------------------------------------------------------------------------- | |
| 368 | month <- function( n ) { | |
| 369 | # Faster than month.name[ x( n ) ] | |
| 370 | .subset( month.name, x( n ) ) | |
| 371 | } | |
| 372 | ||
| 373 | # ----------------------------------------------------------------------------- | |
| 374 | # ----------------------------------------------------------------------------- | |
| 375 | money <- function( n ) { | |
| 376 | commas( x( n ) ) | |
| 377 | } | |
| 378 | ||
| 379 | # ----------------------------------------------------------------------------- | |
| 380 | # ----------------------------------------------------------------------------- | |
| 381 | timeline <- function( n ) { | |
| 382 | concat( weekday( n ), ", ", annal( n ), " (", season( n ), ")" ) | |
| 383 | } | |
| 384 | ||
| 385 | # ----------------------------------------------------------------------------- | |
| 386 | # Rounds to the nearest base value (e.g., round to nearest 10). | |
| 387 | # | |
| 388 | # @param base The nearest value to round to. | |
| 389 | # ----------------------------------------------------------------------------- | |
| 390 | round.up <- function( n, base = 5 ) { | |
| 391 | base * round( x( n ) / base ) | |
| 392 | } | |
| 393 | ||
| 394 | # ----------------------------------------------------------------------------- | |
| 395 | # Computes linear distance between two points using Haversine formula. | |
| 396 | # Although Earth is an oblate spheroid, this will produce results close | |
| 397 | # enough for most purposes. | |
| 398 | # | |
| 399 | # @param lat1/lon1 The source latitude and longitude. | |
| 400 | # @param lat2/lon2 The destination latitude and longitude. | |
| 401 | # @param radius The radius of the sphere. | |
| 402 | # | |
| 403 | # @return The distance between the two coordinates in meters. | |
| 404 | # ----------------------------------------------------------------------------- | |
| 405 | haversine <- function( lat1, lon1, lat2, lon2, radius = 6371 ) { | |
| 406 | # Convert decimal degrees to radians | |
| 407 | lon1 = lon1 * pi / 180 | |
| 408 | lon2 = lon2 * pi / 180 | |
| 409 | lat1 = lat1 * pi / 180 | |
| 410 | lat2 = lat2 * pi / 180 | |
| 411 | ||
| 412 | # Haversine formula | |
| 413 | dlon = lon2 - lon1 | |
| 414 | dlat = lat2 - lat1 | |
| 415 | a = sin( dlat / 2 ) ** 2 + cos( lat1 ) * cos( lat2 ) * sin( dlon / 2 ) ** 2 | |
| 416 | c = 2 * atan2( sqrt( a ), sqrt( 1-a ) ) | |
| 417 | ||
| 418 | return( radius * c * 1000 ) | |
| 419 | } | |
| 420 | ||
| 1 | 421 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Converts CSV to Markdown. | |
| 26 | # | |
| 27 | # Reads a CSV file and converts the contents to a Markdown table. The | |
| 28 | # file must be in the working directory as specified by setwd. | |
| 29 | # | |
| 30 | # @param f The filename to convert. | |
| 31 | # @param decimals Rounded decimal places (default 1). | |
| 32 | # @param totals Include total sums (default TRUE). | |
| 33 | # @param align Right-align numbers (default TRUE). | |
| 34 | # ----------------------------------------------------------------------------- | |
| 35 | csv2md <- function( f, decimals = 2, totals = T, align = T ) { | |
| 36 | # Read the CVS data from the file; ensure strings become characters. | |
| 37 | df <- read.table( f, sep=',', header=T, stringsAsFactors=F ) | |
| 38 | ||
| 39 | if( totals ) { | |
| 40 | # Determine what columns can be summed. | |
| 41 | number <- which( unlist( lapply( df, is.numeric ) ) ) | |
| 42 | ||
| 43 | # Use colSums when more than one summable column exists. | |
| 44 | if( length( number ) > 1 ) { | |
| 45 | f.sum <- colSums | |
| 46 | } | |
| 47 | else { | |
| 48 | f.sum <- sum | |
| 49 | } | |
| 50 | ||
| 51 | # Calculate the sum of all the summable columns and insert the | |
| 52 | # results back into the data frame. | |
| 53 | df[ (nrow( df ) + 1), number ] <- f.sum( df[, number], na.rm=TRUE ) | |
| 54 | ||
| 55 | # pluralise would be heavyweight here. | |
| 56 | if( length( number ) > 1 ) { | |
| 57 | t <- "**Totals**" | |
| 58 | } | |
| 59 | else { | |
| 60 | t <- "**Total**" | |
| 61 | } | |
| 62 | ||
| 63 | # Change the first column of the last line to "Total(s)". | |
| 64 | df[ nrow( df ), 1 ] <- t | |
| 65 | ||
| 66 | # Don't clutter the output with "NA" text. | |
| 67 | df[ is.na( df ) ] <- "" | |
| 68 | } | |
| 69 | ||
| 70 | if( align ) { | |
| 71 | is.char <- vapply( df, is.character, logical( 1 ) ) | |
| 72 | dashes <- paste( ifelse( is.char, ':---', '---:' ), collapse='|' ) | |
| 73 | } | |
| 74 | else { | |
| 75 | dashes <- paste( rep( '---', length( df ) ), collapse = '|' ) | |
| 76 | } | |
| 77 | ||
| 78 | # Create a Markdown version of the data frame. | |
| 79 | paste( | |
| 80 | paste( names( df ), collapse = '|'), '\n', | |
| 81 | dashes, '\n', | |
| 82 | paste( | |
| 83 | Reduce( function( x, y ) { | |
| 84 | paste( x, format( y, digits = decimals ), sep = '|' ) | |
| 85 | }, df | |
| 86 | ), | |
| 87 | collapse = '|\n', sep='' | |
| 88 | ) | |
| 89 | ) | |
| 90 | } | |
| 91 | ||
| 1 | 92 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # See Damian Conway's "An Algorithmic Approach to English Pluralization": | |
| 26 | # http://goo.gl/oRL4MP | |
| 27 | # See Oliver Glerke's Evo Inflector: https://github.com/atteo/evo-inflector/ | |
| 28 | # See Shevek's Pluralizer: https://github.com/shevek/linguistics/ | |
| 29 | # See also: http://www.freevectors.net/assets/files/plural.txt | |
| 30 | # ----------------------------------------------------------------------------- | |
| 31 | pluralize <- function( s, n ) { | |
| 32 | result <- s | |
| 33 | ||
| 34 | # Partial implementation of Conway's algorithm for nouns. | |
| 35 | if( n != 1 ) { | |
| 36 | if( pl.noninflective( s ) || | |
| 37 | pl.suffix( "es", s ) || | |
| 38 | pl.suffix( "fish", s ) || | |
| 39 | pl.suffix( "ois", s ) || | |
| 40 | pl.suffix( "sheep", s ) || | |
| 41 | pl.suffix( "deer", s ) || | |
| 42 | pl.suffix( "pox", s ) || | |
| 43 | pl.suffix( "[A-Z].*ese", s ) || | |
| 44 | pl.suffix( "itis", s ) ) { | |
| 45 | # 1. Retain non-inflective user-mapped noun as is. | |
| 46 | # 2. Retain non-inflective plural as is. | |
| 47 | result <- s | |
| 48 | } | |
| 49 | else if( pl.is.irregular.pl( s ) ) { | |
| 50 | # 4. Change irregular plurals based on mapping. | |
| 51 | result <- pl.irregular.pl( s ) | |
| 52 | } | |
| 53 | else if( pl.is.irregular.es( s ) ) { | |
| 54 | # x. From Shevek's | |
| 55 | result <- pl.inflect( s, "", "es" ) | |
| 56 | } | |
| 57 | else if( pl.suffix( "man", s ) ) { | |
| 58 | # 5. For -man, change -an to -en | |
| 59 | result <- pl.inflect( s, "an", "en" ) | |
| 60 | } | |
| 61 | else if( pl.suffix( "[lm]ouse", s ) ) { | |
| 62 | # 5. For [lm]ouse, change -ouse to -ice | |
| 63 | result <- pl.inflect( s, "ouse", "ice" ) | |
| 64 | } | |
| 65 | else if( pl.suffix( "tooth", s ) ) { | |
| 66 | # 5. For -tooth, change -ooth to -eeth | |
| 67 | result <- pl.inflect( s, "ooth", "eeth" ) | |
| 68 | } | |
| 69 | else if( pl.suffix( "goose", s ) ) { | |
| 70 | # 5. For -goose, change -oose to -eese | |
| 71 | result <- pl.inflect( s, "oose", "eese" ) | |
| 72 | } | |
| 73 | else if( pl.suffix( "foot", s ) ) { | |
| 74 | # 5. For -foot, change -oot to -eet | |
| 75 | result <- pl.inflect( s, "oot", "eet" ) | |
| 76 | } | |
| 77 | else if( pl.suffix( "zoon", s ) ) { | |
| 78 | # 5. For -zoon, change -on to -a | |
| 79 | result <- pl.inflect( s, "on", "a" ) | |
| 80 | } | |
| 81 | else if( pl.suffix( "[csx]is", s ) ) { | |
| 82 | # 5. Change -cis, -sis, -xis to -es | |
| 83 | result <- pl.inflect( s, "is", "es" ) | |
| 84 | } | |
| 85 | else if( pl.suffix( "([cs]h|ss|zz|x|s)", s ) ) { | |
| 86 | # 8. Change -ch, -sh, -ss, -zz, -x, -s to -es | |
| 87 | result <- pl.inflect( s, "", "es" ) | |
| 88 | } | |
| 89 | else if( pl.suffix( "([aeo]lf|[^d]eaf|arf)", s ) ) { | |
| 90 | # 9. Change -f to -ves | |
| 91 | result <- pl.inflect( s, "f", "ves" ) | |
| 92 | } | |
| 93 | else if( pl.suffix( "[nlw]ife", s ) ) { | |
| 94 | # 10. Change -fe to -ves | |
| 95 | result <- pl.inflect( s, "fe", "ves" ) | |
| 96 | } | |
| 97 | else if( pl.suffix( "[aeiou]y", s ) ) { | |
| 98 | # 11. Change -[aeiou]y to -ys | |
| 99 | result <- pl.inflect( s, "", "s" ) | |
| 100 | } | |
| 101 | else if( pl.suffix( "y", s ) ) { | |
| 102 | # 12. Change -y to -ies | |
| 103 | result <- pl.inflect( s, "y", "ies" ) | |
| 104 | } | |
| 105 | else if( pl.suffix( "z", s ) ) { | |
| 106 | # x. Change -z to -zzes | |
| 107 | result <- pl.inflect( s, "", "zes" ) | |
| 108 | } | |
| 109 | else { | |
| 110 | # 13. Default plural: add -s | |
| 111 | result <- pl.inflect( s, "", "s" ) | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | result | |
| 116 | } | |
| 117 | ||
| 118 | # ----------------------------------------------------------------------------- | |
| 119 | # Returns the given string (s) with its suffix replaced by r. | |
| 120 | # ----------------------------------------------------------------------------- | |
| 121 | pl.inflect <- function( s, suffix, r ) { | |
| 122 | gsub( paste( suffix, "$", sep="" ), r, s ) | |
| 123 | } | |
| 124 | ||
| 125 | # ----------------------------------------------------------------------------- | |
| 126 | # Answers whether the given string (s) has the given ending. | |
| 127 | # ----------------------------------------------------------------------------- | |
| 128 | pl.suffix <- function( ending, s ) { | |
| 129 | grepl( paste( ending, "$", sep="" ), s ) | |
| 130 | } | |
| 131 | ||
| 132 | # ----------------------------------------------------------------------------- | |
| 133 | # Answers whether the given string (s) is a noninflective noun. | |
| 134 | # ----------------------------------------------------------------------------- | |
| 135 | pl.noninflective <- function( s ) { | |
| 136 | v <- c( | |
| 137 | "aircraft", | |
| 138 | "Bhutanese", | |
| 139 | "bison", | |
| 140 | "bream", | |
| 141 | "Burmese", | |
| 142 | "carp", | |
| 143 | "chassis", | |
| 144 | "Chinese", | |
| 145 | "clippers", | |
| 146 | "cod", | |
| 147 | "contretemps", | |
| 148 | "corps", | |
| 149 | "debris", | |
| 150 | "djinn", | |
| 151 | "eland", | |
| 152 | "elk", | |
| 153 | "flounder", | |
| 154 | "fracas", | |
| 155 | "gallows", | |
| 156 | "graffiti", | |
| 157 | "headquarters", | |
| 158 | "high-jinks", | |
| 159 | "homework", | |
| 160 | "hovercraft", | |
| 161 | "innings", | |
| 162 | "Japanese", | |
| 163 | "Lebanese", | |
| 164 | "mackerel", | |
| 165 | "means", | |
| 166 | "mews", | |
| 167 | "mice", | |
| 168 | "mumps", | |
| 169 | "news", | |
| 170 | "pincers", | |
| 171 | "pliers", | |
| 172 | "Portuguese", | |
| 173 | "proceedings", | |
| 174 | "salmon", | |
| 175 | "scissors", | |
| 176 | "sea-bass", | |
| 177 | "Senegalese", | |
| 178 | "shears", | |
| 179 | "Siamese", | |
| 180 | "Sinhalese", | |
| 181 | "spacecraft", | |
| 182 | "swine", | |
| 183 | "trout", | |
| 184 | "tuna", | |
| 185 | "Vietnamese", | |
| 186 | "watercraft", | |
| 187 | "whiting", | |
| 188 | "wildebeest" | |
| 189 | ) | |
| 190 | ||
| 191 | is.element( s, v ) | |
| 192 | } | |
| 193 | ||
| 194 | # ----------------------------------------------------------------------------- | |
| 195 | # Answers whether the given string (s) is an irregular plural. | |
| 196 | # ----------------------------------------------------------------------------- | |
| 197 | pl.is.irregular.pl <- function( s ) { | |
| 198 | # Could be refactored with pl.irregular.pl... | |
| 199 | v <- c( | |
| 200 | "beef", "brother", "child", "cow", "ephemeris", "genie", "money", | |
| 201 | "mongoose", "mythos", "octopus", "ox", "soliloquy", "trilby" | |
| 202 | ) | |
| 203 | ||
| 204 | is.element( s, v ) | |
| 205 | } | |
| 206 | ||
| 207 | # ----------------------------------------------------------------------------- | |
| 208 | # Call to pluralize an irregular noun. Only call after confirming | |
| 209 | # the noun is irregular via pl.is.irregular.pl. | |
| 210 | # ----------------------------------------------------------------------------- | |
| 211 | pl.irregular.pl <- function( s ) { | |
| 212 | v <- list( | |
| 213 | "beef" = "beefs", | |
| 214 | "brother" = "brothers", | |
| 215 | "child" = "children", | |
| 216 | "cow" = "cows", | |
| 217 | "ephemeris" = "ephemerides", | |
| 218 | "genie" = "genies", | |
| 219 | "money" = "moneys", | |
| 220 | "mongoose" = "mongooses", | |
| 221 | "mythos" = "mythoi", | |
| 222 | "octopus" = "octopuses", | |
| 223 | "ox" = "oxen", | |
| 224 | "soliloquy" = "soliloquies", | |
| 225 | "trilby" = "trilbys" | |
| 226 | ) | |
| 227 | ||
| 228 | # Faster version of v[[ s ]] | |
| 229 | .subset2( v, s ) | |
| 230 | } | |
| 231 | ||
| 232 | # ----------------------------------------------------------------------------- | |
| 233 | # Answers whether the given string (s) pluralizes with -es. | |
| 234 | # ----------------------------------------------------------------------------- | |
| 235 | pl.is.irregular.es <- function( s ) { | |
| 236 | v <- c( | |
| 237 | "acropolis", "aegis", "alias", "asbestos", "bathos", "bias", "bronchitis", | |
| 238 | "bursitis", "caddis", "cannabis", "canvas", "chaos", "cosmos", "dais", | |
| 239 | "digitalis", "epidermis", "ethos", "eyas", "gas", "glottis", "hubris", | |
| 240 | "ibis", "lens", "mantis", "marquis", "metropolis", "pathos", "pelvis", | |
| 241 | "polis", "rhinoceros", "sassafrass", "trellis" | |
| 242 | ) | |
| 243 | ||
| 244 | is.element( s, v ) | |
| 245 | } | |
| 246 | ||
| 1 | 247 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020, White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 5 | # a copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be | |
| 13 | # included in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 16 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 18 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 19 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 20 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 21 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Returns leftmost n characters of s. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | lstr <- function( s, n = 1 ) { | |
| 28 | substr( s, 0, n ) | |
| 29 | } | |
| 30 | ||
| 31 | # ----------------------------------------------------------------------------- | |
| 32 | # Returns rightmost n characters of s. | |
| 33 | # ----------------------------------------------------------------------------- | |
| 34 | rstr <- function( s, n = 1 ) { | |
| 35 | l <- nchar( s ) | |
| 36 | substr( s, l - n + 1, l ) | |
| 37 | } | |
| 38 | ||
| 39 | # ----------------------------------------------------------------------------- | |
| 40 | # Returns the possessive form of the given word, s. | |
| 41 | # ----------------------------------------------------------------------------- | |
| 42 | pos <- function( s ) { | |
| 43 | lcs <- tolower( s ) | |
| 44 | pronouns <- c( 'your', 'our', 'her', 'it', 'their' ) | |
| 45 | ||
| 46 | if( lcs == 'my' ) { | |
| 47 | # Change "[Mm]y" to "[Mm]ine". | |
| 48 | s <- paste0( lstr( s, 1 ), "ine" ) | |
| 49 | } | |
| 50 | else if( lcs %in% pronouns ) { | |
| 51 | # Append an s to most pronouns. | |
| 52 | s <- paste0( s, 's' ) | |
| 53 | } | |
| 54 | else if( lcs != 'his' ) { | |
| 55 | # Possessive for all other words except 'his'. | |
| 56 | s <- paste0( s, ifelse( rstr( s, 1 ) == 's', "'" ,"'s" ) ) | |
| 57 | } | |
| 58 | ||
| 59 | s | |
| 60 | } | |
| 61 | ||
| 1 | 62 |
| 1 |     | |
| 2 | ||
| 3 | #  | |
| 4 | ||
| 5 | A text editor that uses [interpolated strings](https://en.wikipedia.org/wiki/String_interpolation) to reference values defined externally. | |
| 6 | ||
| 7 | ## Download | |
| 8 | ||
| 9 | Download one of the following editions: | |
| 10 | ||
| 11 | * [Windows](https://gitreleases.dev/gh/DaveJarvis/keenwrite/latest/keenwrite.exe) | |
| 12 | * [Linux](https://gitreleases.dev/gh/DaveJarvis/keenwrite/latest/keenwrite.bin) | |
| 13 | * [Java Archive](https://gitreleases.dev/gh/DaveJarvis/keenwrite/latest/keenwrite.jar) | |
| 14 | ||
| 15 | ## Run | |
| 16 | ||
| 17 | Note that the first time the application runs, it will unpack itself into a local directory. Subsequent starts will be faster. | |
| 18 | ||
| 19 | ### Windows | |
| 20 | ||
| 21 | When upgrading to a new version, delete the following directory: | |
| 22 | ||
| 23 | C:\Users\%USERNAME%\AppData\Local\warp\packages\keenwrite.exe | |
| 24 | ||
| 25 | Double-click the application to start; give the application permission to run. | |
| 26 | ||
| 27 | ### Linux | |
| 28 | ||
| 29 | Execute the following commands in a terminal: | |
| 30 | ||
| 31 | ``` bash | |
| 32 | chmod +x keenwrite.bin | |
| 33 | ./keenwrite.bin | |
| 34 | ``` | |
| 35 | ||
| 36 | ### Other | |
| 37 | ||
| 38 | Download and install a full version of [JRE 16](https://bell-sw.com/pages/downloads/?version=java-16&package=jre-full) that includes JavaFX module support, then run: | |
| 39 | ||
| 40 | ``` bash | |
| 41 | java --illegal-access=permit -jar build/libs/keenwrite.jar 2> /dev/null | |
| 42 | ``` | |
| 43 | ||
| 44 | The `--illegal-access=permit` is a temporary option until third-party libraries used by the text editor are updated or replaced. | |
| 45 | ||
| 46 | ## Features | |
| 47 | ||
| 48 | The application offers: | |
| 49 | ||
| 50 | * User-defined interpolated strings | |
| 51 | * Auto-complete variable names based on variable values | |
| 52 | * High-quality PDF exports | |
| 53 | * Real-time spell check | |
| 54 | * Real-time rendering of math using TeX notation | |
| 55 | * Real-time document statistics (with CJK word separation) | |
| 56 | * Diagrams: Mermaid, GraphViz, UML, sequence, timing, and more | |
| 57 | * Dark, custom, and responsive user interface skins | |
| 58 | * Integrated file manager | |
| 59 | * Interactive document outline | |
| 60 | * Internationalized font support (e.g., Chinese, Japanese, Korean, etc.) | |
| 61 | * Support for Pandoc's fenced div extended attribute syntax | |
| 62 | * R integration | |
| 63 | * Customizable user interface having detachable tabs | |
| 64 | * Platform-independent (Windows, Linux, MacOS) | |
| 65 | ||
| 66 | ## Usage | |
| 67 | ||
| 68 | Read the [detailed documentation](docs/README.md) for using the application. | |
| 69 | ||
| 70 | ### Skins | |
| 71 | ||
| 72 | Read the [skins documentation](docs/skins.md) to learn about how to change | |
| 73 | the user interface appearance. | |
| 74 | ||
| 75 | ## Screenshots | |
| 76 | ||
| 77 | See [screenshots](docs/screenshots.md) for visuals. | |
| 78 | ||
| 79 | ## License | |
| 80 | ||
| 81 | This software is licensed under the [BSD 2-Clause License](LICENSE.md) and | |
| 82 | based on [Markdown-Writer-FX](licenses/MARKDOWN-WRITER-FX.md). | |
| 83 | ||
| 1 | 84 |
| 1 | #  | |
| 2 | ||
| 3 | 智能写入是一个文本编辑器,它使用插值字符串引用外部定义的值。 | |
| 4 | ||
| 5 | ## 下载 | |
| 6 | ||
| 7 | 下载以下版本之一: | |
| 8 | ||
| 9 | * [Windows](https://gitreleases.dev/gh/DaveJarvis/keenwrite/latest/keenwrite.exe) | |
| 10 | * [Linux](https://gitreleases.dev/gh/DaveJarvis/keenwrite/latest/keenwrite.bin) | |
| 11 | * [Java Archive](https://gitreleases.dev/gh/DaveJarvis/keenwrite/latest/keenwrite.jar) | |
| 12 | ||
| 13 | ## 跑 | |
| 14 | ||
| 15 | 在第一次运行期间,应用程序将自身解压到本地目录中。随后的启动会更快。 | |
| 16 | ||
| 17 | ### Windows | |
| 18 | ||
| 19 | 双击应用程序以启动。您必须授予应用程序运行权限。 | |
| 20 | ||
| 21 | 升级时,删除以下目录: | |
| 22 | ||
| 23 | C:\Users\%USERNAME%\AppData\Local\warp\packages\keenwrite.exe | |
| 24 | ||
| 25 | ### Linux | |
| 26 | ||
| 27 | 执行以下命令: | |
| 28 | ||
| 29 | ``` bash | |
| 30 | chmod +x keenwrite.bin | |
| 31 | ./keenwrite.bin | |
| 32 | ``` | |
| 33 | ||
| 34 | ### Other | |
| 35 | ||
| 36 | Download and install a full version of [OpenJDK 15](https://bell-sw.com/pages/downloads/?version=java-15#mn) that includes JavaFX module support, then run: | |
| 37 | ||
| 38 | ``` bash | |
| 39 | java -jar keenwrite.jar | |
| 40 | ``` | |
| 41 | ||
| 42 | ## 特征 | |
| 43 | ||
| 44 | * 用户定义的插值字符串 | |
| 45 | * 带变量替换的实时预览 | |
| 46 | * 基于变量值自动完成变量名 | |
| 47 | * 独立于操作系统 | |
| 48 | * 打字时拼写检查 | |
| 49 | * 使用TeX的子集编写数学公式 | |
| 50 | * 嵌入R语句 | |
| 51 | ||
| 52 | ## 软件使用 | |
| 53 | ||
| 54 | See the [detailed documentation](docs/README.md) for information about | |
| 55 | using the application. | |
| 56 | ||
| 57 | ## 截图 | |
| 58 | ||
| 59 |  | |
| 60 | ||
| 61 |  | |
| 62 | ||
| 63 |  | |
| 64 | ||
| 65 | ||
| 66 | ## 软件许可证 | |
| 67 | ||
| 68 | This software is licensed under the [BSD 2-Clause License](LICENSE.md) and | |
| 69 | based on [Markdown-Writer-FX](licenses/MARKDOWN-WRITER-FX.md). | |
| 70 | ||
| 1 | 71 |
| 1 | plugins { | |
| 2 | id 'application' | |
| 3 | id 'org.openjfx.javafxplugin' version '0.0.10' | |
| 4 | id 'com.palantir.git-version' version '0.12.3' | |
| 5 | } | |
| 6 | ||
| 7 | repositories { | |
| 8 | mavenCentral() | |
| 9 | jcenter() | |
| 10 | ||
| 11 | maven { | |
| 12 | url 'https://oss.sonatype.org/content/repositories/snapshots/' | |
| 13 | } | |
| 14 | ||
| 15 | maven { | |
| 16 | url "https://nexus.bedatadriven.com/content/groups/public" | |
| 17 | } | |
| 18 | } | |
| 19 | ||
| 20 | // Assume a cross-platform überjar unless targetOs is set. | |
| 21 | String[] os = ["win", "mac", "linux"] | |
| 22 | ||
| 23 | if (project.hasProperty('targetOs')) { | |
| 24 | if ("windows" == targetOs) { | |
| 25 | os = ["win"] | |
| 26 | } else { | |
| 27 | os = [targetOs] | |
| 28 | } | |
| 29 | } | |
| 30 | ||
| 31 | def moduleSecurity = [ | |
| 32 | "--add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED", | |
| 33 | "--add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED", | |
| 34 | "--add-opens=javafx.graphics/javafx.scene.text=ALL-UNNAMED", | |
| 35 | "--add-opens=javafx.graphics/com.sun.javafx.css=ALL-UNNAMED", | |
| 36 | "--add-opens=javafx.graphics/com.sun.javafx.text=ALL-UNNAMED", | |
| 37 | "--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED", | |
| 38 | "--add-exports=javafx.graphics/com.sun.javafx.application=ALL-UNNAMED", | |
| 39 | "--add-exports=javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED", | |
| 40 | "--add-exports=javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED", | |
| 41 | "--add-exports=javafx.graphics/com.sun.javafx.scene.traversal=ALL-UNNAMED", | |
| 42 | "--add-exports=javafx.graphics/com.sun.javafx.text=ALL-UNNAMED", | |
| 43 | ] | |
| 44 | ||
| 45 | javafx { | |
| 46 | version = "16" | |
| 47 | modules = ['javafx.controls', 'javafx.swing'] | |
| 48 | configuration = 'compileOnly' | |
| 49 | } | |
| 50 | ||
| 51 | dependencies { | |
| 52 | def v_junit = '5.7.2' | |
| 53 | def v_flexmark = '0.62.2' | |
| 54 | def v_jackson = '2.12.3' | |
| 55 | def v_batik = '1.14' | |
| 56 | def v_wheatsheaf = '2.0.1' | |
| 57 | ||
| 58 | // JavaFX | |
| 59 | implementation 'org.controlsfx:controlsfx:11.1.0' | |
| 60 | implementation 'org.fxmisc.richtext:richtextfx:0.10.6' | |
| 61 | implementation 'org.fxmisc.wellbehaved:wellbehavedfx:0.3.3' | |
| 62 | implementation 'com.miglayout:miglayout-javafx:11.0' | |
| 63 | implementation 'com.dlsc.preferencesfx:preferencesfx-core:11.8.0' | |
| 64 | ||
| 65 | // Pure JavaFX File Chooser | |
| 66 | implementation "com.io7m.jwheatsheaf:com.io7m.jwheatsheaf:${v_wheatsheaf}" | |
| 67 | implementation "com.io7m.jwheatsheaf:com.io7m.jwheatsheaf.api:${v_wheatsheaf}" | |
| 68 | implementation "com.io7m.jwheatsheaf:com.io7m.jwheatsheaf.ui:${v_wheatsheaf}" | |
| 69 | ||
| 70 | // Markdown | |
| 71 | implementation "com.vladsch.flexmark:flexmark:${v_flexmark}" | |
| 72 | implementation "com.vladsch.flexmark:flexmark-ext-definition:${v_flexmark}" | |
| 73 | implementation "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:${v_flexmark}" | |
| 74 | implementation "com.vladsch.flexmark:flexmark-ext-superscript:${v_flexmark}" | |
| 75 | implementation "com.vladsch.flexmark:flexmark-ext-tables:${v_flexmark}" | |
| 76 | implementation "com.vladsch.flexmark:flexmark-ext-typographic:${v_flexmark}" | |
| 77 | ||
| 78 | // YAML | |
| 79 | implementation "com.fasterxml.jackson.core:jackson-core:${v_jackson}" | |
| 80 | implementation "com.fasterxml.jackson.core:jackson-databind:${v_jackson}" | |
| 81 | implementation "com.fasterxml.jackson.core:jackson-annotations:${v_jackson}" | |
| 82 | implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${v_jackson}" | |
| 83 | implementation 'org.yaml:snakeyaml:1.29' | |
| 84 | ||
| 85 | // XML | |
| 86 | implementation 'com.ximpleware:vtd-xml:2.13.4' | |
| 87 | ||
| 88 | // HTML parsing and rendering | |
| 89 | implementation 'org.jsoup:jsoup:1.13.1' | |
| 90 | implementation 'org.xhtmlrenderer:flying-saucer-core:9.1.20' | |
| 91 | ||
| 92 | // R | |
| 93 | implementation 'org.renjin:renjin-script-engine:3.5-beta76' | |
| 94 | ||
| 95 | // SVG | |
| 96 | implementation "org.apache.xmlgraphics:batik-anim:${v_batik}" | |
| 97 | implementation "org.apache.xmlgraphics:batik-awt-util:${v_batik}" | |
| 98 | implementation "org.apache.xmlgraphics:batik-bridge:${v_batik}" | |
| 99 | implementation "org.apache.xmlgraphics:batik-css:${v_batik}" | |
| 100 | implementation "org.apache.xmlgraphics:batik-dom:${v_batik}" | |
| 101 | implementation "org.apache.xmlgraphics:batik-ext:${v_batik}" | |
| 102 | implementation "org.apache.xmlgraphics:batik-gvt:${v_batik}" | |
| 103 | implementation "org.apache.xmlgraphics:batik-parser:${v_batik}" | |
| 104 | implementation "org.apache.xmlgraphics:batik-script:${v_batik}" | |
| 105 | implementation "org.apache.xmlgraphics:batik-svg-dom:${v_batik}" | |
| 106 | implementation "org.apache.xmlgraphics:batik-svggen:${v_batik}" | |
| 107 | implementation "org.apache.xmlgraphics:batik-transcoder:${v_batik}" | |
| 108 | implementation "org.apache.xmlgraphics:batik-rasterizer:${v_batik}" | |
| 109 | implementation "org.apache.xmlgraphics:batik-util:${v_batik}" | |
| 110 | implementation "org.apache.xmlgraphics:batik-xml:${v_batik}" | |
| 111 | ||
| 112 | // Misc. | |
| 113 | implementation 'org.ahocorasick:ahocorasick:0.6.3' | |
| 114 | implementation 'org.apache.commons:commons-configuration2:2.7' | |
| 115 | implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3' | |
| 116 | implementation 'javax.validation:validation-api:2.0.1.Final' | |
| 117 | implementation 'org.greenrobot:eventbus:3.2.0' | |
| 118 | ||
| 119 | // TODO: Update Workspace config to use Jackson to shave ~800kb | |
| 120 | implementation 'org.apache.commons:commons-configuration2:2.7' | |
| 121 | implementation 'commons-beanutils:commons-beanutils:1.9.4' | |
| 122 | ||
| 123 | // Spelling, TeX, Docking, KeenQuotes | |
| 124 | implementation fileTree(include: ['**/*.jar'], dir: 'libs') | |
| 125 | ||
| 126 | def fx = ['controls', 'graphics', 'fxml', 'swing'] | |
| 127 | ||
| 128 | fx.each { fxitem -> | |
| 129 | os.each { ositem -> | |
| 130 | runtimeOnly "org.openjfx:javafx-${fxitem}:${javafx.version}:${ositem}" | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | testImplementation "org.junit.jupiter:junit-jupiter-api:${v_junit}" | |
| 135 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' | |
| 136 | ||
| 137 | testImplementation "org.testfx:testfx-junit5:4.0.16-alpha" | |
| 138 | } | |
| 139 | ||
| 140 | compileJava { | |
| 141 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" | |
| 142 | } | |
| 143 | ||
| 144 | def resourceDir = sourceSets.main.resources.srcDirs[0] | |
| 145 | ||
| 146 | final Properties config = new Properties() | |
| 147 | final File configFile = file("${resourceDir}/bootstrap.properties") | |
| 148 | final FileInputStream configStream = new FileInputStream(configFile) | |
| 149 | config.load(configStream) | |
| 150 | configStream.close() | |
| 151 | ||
| 152 | final String applicationName = config.get("application.title").toString().toLowerCase() | |
| 153 | final String applicationClass = "com.${applicationName}.Launcher" | |
| 154 | ||
| 155 | application { | |
| 156 | mainClass.set(applicationClass) | |
| 157 | applicationDefaultJvmArgs = moduleSecurity | |
| 158 | } | |
| 159 | ||
| 160 | version = gitVersion() | |
| 161 | ||
| 162 | final File propertiesFile = new File("${resourceDir}/com/${applicationName}/app.properties") | |
| 163 | propertiesFile.write("application.version=${version}") | |
| 164 | ||
| 165 | jar { | |
| 166 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE | |
| 167 | ||
| 168 | doFirst { | |
| 169 | manifest { | |
| 170 | attributes 'Main-Class': applicationClass | |
| 171 | } | |
| 172 | } | |
| 173 | ||
| 174 | from { | |
| 175 | (configurations.runtimeClasspath.findAll { !it.path.endsWith(".pom") }).collect { | |
| 176 | it.isDirectory() ? it : zipTree(it) | |
| 177 | } | |
| 178 | } | |
| 179 | ||
| 180 | archiveFileName = "${applicationName}.jar" | |
| 181 | ||
| 182 | exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' | |
| 183 | } | |
| 184 | ||
| 185 | distributions { | |
| 186 | main { | |
| 187 | distributionBaseName = applicationName | |
| 188 | contents { | |
| 189 | from { ['LICENSE.md', 'README.md'] } | |
| 190 | into('images') { | |
| 191 | from { 'images' } | |
| 192 | } | |
| 193 | } | |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | test { | |
| 198 | useJUnitPlatform() | |
| 199 | ||
| 200 | doFirst { | |
| 201 | jvmArgs = moduleSecurity | |
| 202 | } | |
| 203 | ||
| 204 | testLogging { | |
| 205 | exceptionFormat = 'full' | |
| 206 | } | |
| 207 | } | |
| 1 | 208 |
| 1 | # Documentation | |
| 2 | ||
| 3 | The following documents have additional details about using the editor: | |
| 4 | ||
| 5 | * [div.md](div.md) -- Syntax for annotated text (fenced divs) | |
| 6 | * [i18n.md](i18n.md) -- Internationalization features | |
| 7 | * [r.md](r.md) -- R functions within R Markdown documents | |
| 8 | * [samples](samples) -- Example documents | |
| 9 | * [skins.md](skins.md) -- User interface customization | |
| 10 | * [svg.md](svg.md) -- Resolve issues with some SVG files | |
| 11 | * [typesetting.md](typesetting.md) -- Document typesetting | |
| 12 | * [variables.md](variables.md) -- Variable definitions and interpolation | |
| 13 | ||
| 14 | # Contributions | |
| 15 | ||
| 16 | * [credits.md](credits.md) -- Thanks to authors of contributing projects | |
| 17 | * [licenses](licenses) -- Third-party licenses | |
| 18 | ||
| 1 | 19 |
| 1 | # Credits | |
| 2 | ||
| 3 | * Karl Tauber: [Markdown Writer FX](https://github.com/JFormDesigner/markdown-writer-fx) | |
| 4 | * Tomas Mikula: [RichTextFX](https://github.com/TomasMikula/RichTextFX), [ReactFX](https://github.com/TomasMikula/ReactFX), [WellBehavedFX](https://github.com/TomasMikula/WellBehavedFX), [Flowless](https://github.com/TomasMikula/Flowless), and [UndoFX](https://github.com/TomasMikula/UndoFX) | |
| 5 | * Mikael Grev: [MigLayout](http://www.miglayout.com/) | |
| 6 | * Tom Eugelink: [MigPane](https://github.com/mikaelgrev/miglayout/blob/master/javafx/src/main/java/org/tbee/javafx/scene/layout/fxml/MigPane.java) | |
| 7 | * Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx) | |
| 8 | * Dieter Holz, [PreferencesFX](https://github.com/dlsc-software-consulting-gmbh/PreferencesFX) | |
| 9 | * David Croft, [File Preferences](http://www.davidc.net/programming/java/java-preferences-using-file-backing-store) | |
| 10 | * Alex Bertram, [Renjin](https://www.renjin.org/) | |
| 11 | * Vladimir Schneider: [flexmark](https://github.com/vsch/flexmark-java) | |
| 12 | * Shy Shalom, Kohei Taketa: [juniversalchardet](https://github.com/takscape/juniversalchardet) | |
| 13 | ||
| 1 | 14 |
| 1 | From https://github.com/greenrobot/EventBus#r8-proguard | |
| 2 | ||
| 3 | -keepattributes *Annotation* | |
| 4 | -keepclassmembers class * { | |
| 5 | @org.greenrobot.eventbus.Subscribe <methods>; | |
| 6 | } | |
| 7 | -keep enum org.greenrobot.eventbus.ThreadMode { *; } | |
| 8 | ||
| 1 | 9 |
| 1 | # Introduction | |
| 2 | ||
| 3 | From a high level, the application architecture for converting Markdown documents is captured in the following figure: | |
| 4 | ||
| 5 | ``` diagram-graphviz | |
| 6 | digraph { | |
| 7 | node [fontname = "Noto Sans" fontsize=6 height=.25 penwidth=.5]; | |
| 8 | edge [fontname = "Noto Sans" fontsize=6 penwidth=.5 arrowsize=.5]; | |
| 9 | node [shape=box color="{{keenwrite.palette.primary.light}}" fontcolor="{{keenwrite.palette.primary.dark}}"] | |
| 10 | edge [color="{{keenwrite.palette.grayscale.light}}" fontcolor="{{keenwrite.palette.grayscale.dark}}"] | |
| 11 | ||
| 12 | {{keenwrite.classes.processors.variable.definition}} -> {{keenwrite.classes.processors.markdown}} [xlabel="{{keenwrite.graph.label.chain.next}} "] | |
| 13 | {{keenwrite.classes.processors.markdown}} -> {{keenwrite.classes.processors.preview}} [xlabel="{{keenwrite.graph.label.chain.next}} "] | |
| 14 | {{keenwrite.classes.processors.markdown}} -> Extensions [label=" contains"] | |
| 15 | ||
| 16 | Extensions -> FencedBlockExtension | |
| 17 | Extensions -> CaretExtension | |
| 18 | Extensions -> ImageLinkExtension | |
| 19 | Extensions -> TeXExtension | |
| 20 | } | |
| 21 | ``` | |
| 22 | ||
| 23 | An extension is an addition to the Markdown parser, flexmark-java, that is used when converting the document's abstract syntax tree into an HTML document. The {{keenwrite.classes.processors.markdown}} contains both prepackaged and custom extensions. | |
| 1 | 24 |
| 1 | --- | |
| 2 | keenwrite: | |
| 3 | classes: | |
| 4 | processors: | |
| 5 | markdown: MarkdownProcessor | |
| 6 | variable: | |
| 7 | definition: DefinitionProcessor | |
| 8 | preview: PreviewProcessor | |
| 9 | palette: | |
| 10 | primary: | |
| 11 | light: '#51a9cf' | |
| 12 | dark: '#126d95' | |
| 13 | secondary: | |
| 14 | light: '#ec706a' | |
| 15 | dark: '#7e252f' | |
| 16 | accent: | |
| 17 | light: '#76A786' | |
| 18 | dark: '#385742' | |
| 19 | grayscale: | |
| 20 | light: '#bac2c5' | |
| 21 | dark: '#394343' | |
| 22 | graph: | |
| 23 | label: | |
| 24 | chain: | |
| 25 | next: successor | |
| 1 | 26 |
| 1 | # Fenced divs | |
| 2 | ||
| 3 | This section describes the syntax to generate HTML `div` elements. The | |
| 4 | syntax is known as a _fenced div_. | |
| 5 | ||
| 6 | # Basic syntax | |
| 7 | ||
| 8 | A fenced div has the following basic syntax: | |
| 9 | ||
| 10 | ``` markdown | |
| 11 | ::: name | |
| 12 | Content | |
| 13 | ::: | |
| 14 | ``` | |
| 15 | ||
| 16 | To start a fenced div, begin a line with at least three colons (`:::`), | |
| 17 | followed by at least one space, followed by any word. Content may follow | |
| 18 | immediately on the next line. Terminate the fenced div with at least | |
| 19 | three colons. The terminating colons needn't match in number to the starting | |
| 20 | colons, but it's a good idea to maintain symmetry. | |
| 21 | ||
| 22 | The HTML that is generated from the above fenced div will resemble: | |
| 23 | ||
| 24 | ``` html | |
| 25 | <div class="name"> | |
| 26 | <p>Content</p> | |
| 27 | </div> | |
| 28 | ``` | |
| 29 | ||
| 30 | # Extended syntax | |
| 31 | ||
| 32 | A fenced div may use an extended syntax. The extended syntax can provide | |
| 33 | a unique identifier, multiple class names, and key/value data pairs. For | |
| 34 | example: | |
| 35 | ||
| 36 | ``` markdown | |
| 37 | ::: {#poem-01 .stanza author="Emily Dickinson" year=1890} | |
| 38 | Because I could not stop for Death — | |
| 39 | He kindly stopped for me — | |
| 40 | The Carriage held but just Ourselves — | |
| 41 | And Immortality. | |
| 42 | ::: | |
| 43 | ``` | |
| 44 | ||
| 45 | The above snippet produces: | |
| 46 | ||
| 47 | ``` html | |
| 48 | <div id="poem-01" class="stanza" data-author="Emily Dickinson" data-year="1890"> | |
| 49 | <p>Because I could not stop for Death — | |
| 50 | He kindly stopped for me — | |
| 51 | The Carriage held but just Ourselves — | |
| 52 | And Immortality.</p> | |
| 53 | </div> | |
| 54 | ``` | |
| 55 | ||
| 56 | Note that when using the extended syntax, class styles must be prefixed with | |
| 57 | a period (e.g., `.stanza` in the example). | |
| 58 | ||
| 59 | # Nested syntax | |
| 60 | ||
| 61 | Fenced divs may be nested, such as in the following example: | |
| 62 | ||
| 63 | ``` markdown | |
| 64 | ::: poem | |
| 65 | :::::: stanza | |
| 66 | Because I could not stop for Death — | |
| 67 | He kindly stopped for me — | |
| 68 | The Carriage held but just Ourselves — | |
| 69 | And Immortality. | |
| 70 | :::::: | |
| 71 | ::: | |
| 72 | ``` | |
| 73 | ||
| 74 | The above example produces: | |
| 75 | ||
| 76 | ``` html | |
| 77 | <div class="poem"><div class="stanza"> | |
| 78 | <p>Because I could not stop for Death — | |
| 79 | He kindly stopped for me — | |
| 80 | The Carriage held but just Ourselves — | |
| 81 | And Immortality.</p> | |
| 82 | </div></div> | |
| 83 | ``` | |
| 84 | ||
| 1 | 85 |
| 1 | # Internationalization | |
| 2 | ||
| 3 | The application supports internationalization (I18N). There are multiple | |
| 4 | components to editing and previewing internationalized text documents. | |
| 5 | These include: | |
| 6 | ||
| 7 | * Fonts | |
| 8 | * Language | |
| 9 | ||
| 10 | Both fonts and language must be set for non-Latin-based text. | |
| 11 | ||
| 12 | # Fonts | |
| 13 | ||
| 14 | The text editors and preview panel have independent font settings. For | |
| 15 | all Chinese, Japanese, and Korean (CJK) fonts, you may have to type in | |
| 16 | the font family name directly. | |
| 17 | ||
| 18 | For example, CJK font families for the editor have the following names: | |
| 19 | ||
| 20 | * **Noto Sans CJK KR** --- Korean font | |
| 21 | * **Noto Sans CJK JP** --- Japanese font | |
| 22 | * **Noto Sans CJK HN** --- Chinese font | |
| 23 | * **Noto Sans CJK SC** --- Simplified Chinese font | |
| 24 | ||
| 25 | While CJK font familes for the preview have the following names: | |
| 26 | ||
| 27 | * **Noto Serif CJK KR** --- Korean font | |
| 28 | * **Noto Serif CJK JP** --- Japanese font | |
| 29 | * **Noto Serif CJK HN** --- Chinese font | |
| 30 | * **Noto Serif CJK SC** --- Simplified Chinese font | |
| 31 | ||
| 32 | ## Editor | |
| 33 | ||
| 34 | Complete the following steps to change the editor font: | |
| 35 | ||
| 36 | 1. Click **Edit → Preferences**. | |
| 37 | 1. Click **Fonts**. | |
| 38 | 1. Click **Change** under **Editor Font**. | |
| 39 | 1. Find the font name by typing or scrolling. | |
| 40 | 1. Click the desired font family. | |
| 41 | 1. Click **OK**. | |
| 42 | 1. Click **Apply**. | |
| 43 | ||
| 44 | The text editor font is changed. | |
| 45 | ||
| 46 | Note the following: | |
| 47 | ||
| 48 | * The font must be installed in the system for this to work. | |
| 49 | * You may have to edit the font name if it cannot be selected from the list. | |
| 50 | * Setting the editor font also sets the statistics panel font. | |
| 51 | ||
| 52 | ## Preview | |
| 53 | ||
| 54 | The preview panel uses regular and monospace fonts. | |
| 55 | ||
| 56 | ### Regular | |
| 57 | ||
| 58 | Complete the following steps to change the regular preview font: | |
| 59 | ||
| 60 | 1. Click **Edit → Preferences**. | |
| 61 | 1. Click **Fonts**. | |
| 62 | 1. Click **Change** under **Preview Font** for the **Preview pane font name**. | |
| 63 | 1. Find the font name by typing or scrolling. | |
| 64 | 1. Click the desired font family. | |
| 65 | 1. Click **OK**. | |
| 66 | 1. Click **Apply**. | |
| 67 | ||
| 68 | The regular preview font is changed. | |
| 69 | ||
| 70 | ### Monospace | |
| 71 | ||
| 72 | Complete the following steps to change the monospace preview font: | |
| 73 | ||
| 74 | 1. Click **Edit → Preferences**. | |
| 75 | 1. Click **Fonts**. | |
| 76 | 1. Click **Change** under **Preview Font** for the **Monospace font name**. | |
| 77 | 1. Find the font name by typing or scrolling. | |
| 78 | 1. Click the desired font family. | |
| 79 | 1. Click **OK**. | |
| 80 | 1. Click **Apply**. | |
| 81 | ||
| 82 | The monospace font is changed. | |
| 83 | ||
| 84 | # Language | |
| 85 | ||
| 86 | Language settings control the locale that the application uses. When using | |
| 87 | a CJK font, for example, the application must also be instructed to use | |
| 88 | a particular locale. Change the locale as follows: | |
| 89 | ||
| 90 | 1. Click **Edit → Preferences**. | |
| 91 | 1. Click **Language**. | |
| 92 | 1. Select a value for **Locale**. | |
| 93 | 1. Click **Apply**. | |
| 94 | ||
| 95 | The language is set. | |
| 96 | ||
| 1 | 97 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 4 | xmlns:cc="http://creativecommons.org/ns#" | |
| 5 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 6 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 7 | xmlns="http://www.w3.org/2000/svg" | |
| 8 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 9 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 10 | inkscape:export-ydpi="150.0097" | |
| 11 | inkscape:export-xdpi="150.0097" | |
| 12 | sodipodi:docname="architecture.svg" | |
| 13 | viewBox="0 0 764.4414 811.46748" | |
| 14 | height="811.46747" | |
| 15 | width="764.44141" | |
| 16 | id="svg4610" | |
| 17 | version="1.2" | |
| 18 | inkscape:version="1.0 (4035a4fb49, 2020-05-01)"> | |
| 19 | <metadata | |
| 20 | id="metadata4616"> | |
| 21 | <rdf:RDF> | |
| 22 | <cc:Work | |
| 23 | rdf:about=""> | |
| 24 | <dc:format>image/svg+xml</dc:format> | |
| 25 | <dc:type | |
| 26 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |
| 27 | <dc:title /> | |
| 28 | </cc:Work> | |
| 29 | </rdf:RDF> | |
| 30 | </metadata> | |
| 31 | <defs | |
| 32 | id="defs4614"> | |
| 33 | <marker | |
| 34 | inkscape:stockid="Arrow1Mend" | |
| 35 | orient="auto" | |
| 36 | refY="0" | |
| 37 | refX="0" | |
| 38 | id="marker10933" | |
| 39 | style="overflow:visible" | |
| 40 | inkscape:isstock="true"> | |
| 41 | <path | |
| 42 | id="path10931" | |
| 43 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 44 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 45 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 46 | inkscape:connector-curvature="0" /> | |
| 47 | </marker> | |
| 48 | <marker | |
| 49 | inkscape:stockid="Arrow1Mend" | |
| 50 | orient="auto" | |
| 51 | refY="0" | |
| 52 | refX="0" | |
| 53 | id="marker9893" | |
| 54 | style="overflow:visible" | |
| 55 | inkscape:isstock="true"> | |
| 56 | <path | |
| 57 | id="path9891" | |
| 58 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 59 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 60 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 61 | inkscape:connector-curvature="0" /> | |
| 62 | </marker> | |
| 63 | <marker | |
| 64 | inkscape:collect="always" | |
| 65 | inkscape:isstock="true" | |
| 66 | style="overflow:visible" | |
| 67 | id="marker9767" | |
| 68 | refX="0" | |
| 69 | refY="0" | |
| 70 | orient="auto" | |
| 71 | inkscape:stockid="Arrow1Mend"> | |
| 72 | <path | |
| 73 | inkscape:connector-curvature="0" | |
| 74 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 75 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 76 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 77 | id="path9765" /> | |
| 78 | </marker> | |
| 79 | <marker | |
| 80 | inkscape:collect="always" | |
| 81 | inkscape:stockid="Arrow1Mend" | |
| 82 | orient="auto" | |
| 83 | refY="0" | |
| 84 | refX="0" | |
| 85 | id="marker9761" | |
| 86 | style="overflow:visible" | |
| 87 | inkscape:isstock="true"> | |
| 88 | <path | |
| 89 | id="path9759" | |
| 90 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 91 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 92 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 93 | inkscape:connector-curvature="0" /> | |
| 94 | </marker> | |
| 95 | <marker | |
| 96 | inkscape:isstock="true" | |
| 97 | style="overflow:visible" | |
| 98 | id="marker9750" | |
| 99 | refX="0" | |
| 100 | refY="0" | |
| 101 | orient="auto" | |
| 102 | inkscape:stockid="Arrow1Mend"> | |
| 103 | <path | |
| 104 | inkscape:connector-curvature="0" | |
| 105 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 106 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 107 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 108 | id="path9748" /> | |
| 109 | </marker> | |
| 110 | <marker | |
| 111 | inkscape:isstock="true" | |
| 112 | style="overflow:visible" | |
| 113 | id="marker9715" | |
| 114 | refX="0" | |
| 115 | refY="0" | |
| 116 | orient="auto" | |
| 117 | inkscape:stockid="Arrow1Mend"> | |
| 118 | <path | |
| 119 | inkscape:connector-curvature="0" | |
| 120 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 121 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 122 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 123 | id="path9713" /> | |
| 124 | </marker> | |
| 125 | <marker | |
| 126 | inkscape:collect="always" | |
| 127 | inkscape:stockid="Arrow1Mend" | |
| 128 | orient="auto" | |
| 129 | refY="0" | |
| 130 | refX="0" | |
| 131 | id="marker9685" | |
| 132 | style="overflow:visible" | |
| 133 | inkscape:isstock="true"> | |
| 134 | <path | |
| 135 | id="path9683" | |
| 136 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 137 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 138 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 139 | inkscape:connector-curvature="0" /> | |
| 140 | </marker> | |
| 141 | <marker | |
| 142 | inkscape:collect="always" | |
| 143 | inkscape:stockid="Arrow1Mend" | |
| 144 | orient="auto" | |
| 145 | refY="0" | |
| 146 | refX="0" | |
| 147 | id="marker9679" | |
| 148 | style="overflow:visible" | |
| 149 | inkscape:isstock="true"> | |
| 150 | <path | |
| 151 | id="path9677" | |
| 152 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 153 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 154 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 155 | inkscape:connector-curvature="0" /> | |
| 156 | </marker> | |
| 157 | <marker | |
| 158 | inkscape:collect="always" | |
| 159 | inkscape:isstock="true" | |
| 160 | style="overflow:visible" | |
| 161 | id="marker9640" | |
| 162 | refX="0" | |
| 163 | refY="0" | |
| 164 | orient="auto" | |
| 165 | inkscape:stockid="Arrow1Mend"> | |
| 166 | <path | |
| 167 | inkscape:connector-curvature="0" | |
| 168 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 169 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 170 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 171 | id="path9638" /> | |
| 172 | </marker> | |
| 173 | <marker | |
| 174 | inkscape:collect="always" | |
| 175 | inkscape:isstock="true" | |
| 176 | style="overflow:visible" | |
| 177 | id="marker9513" | |
| 178 | refX="0" | |
| 179 | refY="0" | |
| 180 | orient="auto" | |
| 181 | inkscape:stockid="Arrow1Mend"> | |
| 182 | <path | |
| 183 | inkscape:connector-curvature="0" | |
| 184 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 185 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 186 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 187 | id="path9511" /> | |
| 188 | </marker> | |
| 189 | <marker | |
| 190 | inkscape:stockid="Arrow1Mend" | |
| 191 | orient="auto" | |
| 192 | refY="0" | |
| 193 | refX="0" | |
| 194 | id="marker9509" | |
| 195 | style="overflow:visible" | |
| 196 | inkscape:isstock="true"> | |
| 197 | <path | |
| 198 | id="path9507" | |
| 199 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 200 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 201 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 202 | inkscape:connector-curvature="0" /> | |
| 203 | </marker> | |
| 204 | <marker | |
| 205 | inkscape:isstock="true" | |
| 206 | style="overflow:visible" | |
| 207 | id="marker9505" | |
| 208 | refX="0" | |
| 209 | refY="0" | |
| 210 | orient="auto" | |
| 211 | inkscape:stockid="Arrow1Mend"> | |
| 212 | <path | |
| 213 | inkscape:connector-curvature="0" | |
| 214 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 215 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 216 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 217 | id="path9503" /> | |
| 218 | </marker> | |
| 219 | <marker | |
| 220 | inkscape:collect="always" | |
| 221 | inkscape:stockid="Arrow1Mend" | |
| 222 | orient="auto" | |
| 223 | refY="0" | |
| 224 | refX="0" | |
| 225 | id="marker9479" | |
| 226 | style="overflow:visible" | |
| 227 | inkscape:isstock="true"> | |
| 228 | <path | |
| 229 | id="path9477" | |
| 230 | d="M 0,0 5,-5 -12.5,0 5,5 Z" | |
| 231 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1" | |
| 232 | transform="matrix(-0.4,0,0,-0.4,-4,0)" | |
| 233 | inkscape:connector-curvature="0" /> | |
| 234 | </marker> | |
| 235 | <clipPath | |
| 236 | id="ID000001"> | |
| 237 | <rect | |
| 238 | id="rect6" | |
| 239 | height="961.125" | |
| 240 | width="1381.6169" | |
| 241 | y="-43.688" | |
| 242 | x="-62.683998" /> | |
| 243 | </clipPath> | |
| 244 | <filter | |
| 245 | id="filter2842" | |
| 246 | inkscape:label="Drop Shadow" | |
| 247 | style="color-interpolation-filters:sRGB;"> | |
| 248 | <feFlood | |
| 249 | id="feFlood2832" | |
| 250 | result="flood" | |
| 251 | flood-color="rgb(0,0,0)" | |
| 252 | flood-opacity="0.498039" /> | |
| 253 | <feComposite | |
| 254 | id="feComposite2834" | |
| 255 | result="composite1" | |
| 256 | operator="in" | |
| 257 | in2="SourceGraphic" | |
| 258 | in="flood" /> | |
| 259 | <feGaussianBlur | |
| 260 | id="feGaussianBlur2836" | |
| 261 | result="blur" | |
| 262 | stdDeviation="2" | |
| 263 | in="composite1" /> | |
| 264 | <feOffset | |
| 265 | id="feOffset2838" | |
| 266 | result="offset" | |
| 267 | dy="3" | |
| 268 | dx="3" /> | |
| 269 | <feComposite | |
| 270 | id="feComposite2840" | |
| 271 | result="composite2" | |
| 272 | operator="over" | |
| 273 | in2="offset" | |
| 274 | in="SourceGraphic" /> | |
| 275 | </filter> | |
| 276 | <filter | |
| 277 | id="filter2854" | |
| 278 | inkscape:label="Drop Shadow" | |
| 279 | style="color-interpolation-filters:sRGB;"> | |
| 280 | <feFlood | |
| 281 | id="feFlood2844" | |
| 282 | result="flood" | |
| 283 | flood-color="rgb(0,0,0)" | |
| 284 | flood-opacity="0.498039" /> | |
| 285 | <feComposite | |
| 286 | id="feComposite2846" | |
| 287 | result="composite1" | |
| 288 | operator="in" | |
| 289 | in2="SourceGraphic" | |
| 290 | in="flood" /> | |
| 291 | <feGaussianBlur | |
| 292 | id="feGaussianBlur2848" | |
| 293 | result="blur" | |
| 294 | stdDeviation="2" | |
| 295 | in="composite1" /> | |
| 296 | <feOffset | |
| 297 | id="feOffset2850" | |
| 298 | result="offset" | |
| 299 | dy="3" | |
| 300 | dx="3" /> | |
| 301 | <feComposite | |
| 302 | id="feComposite2852" | |
| 303 | result="composite2" | |
| 304 | operator="over" | |
| 305 | in2="offset" | |
| 306 | in="SourceGraphic" /> | |
| 307 | </filter> | |
| 308 | <filter | |
| 309 | id="filter2866" | |
| 310 | inkscape:label="Drop Shadow" | |
| 311 | style="color-interpolation-filters:sRGB;"> | |
| 312 | <feFlood | |
| 313 | id="feFlood2856" | |
| 314 | result="flood" | |
| 315 | flood-color="rgb(0,0,0)" | |
| 316 | flood-opacity="0.498039" /> | |
| 317 | <feComposite | |
| 318 | id="feComposite2858" | |
| 319 | result="composite1" | |
| 320 | operator="in" | |
| 321 | in2="SourceGraphic" | |
| 322 | in="flood" /> | |
| 323 | <feGaussianBlur | |
| 324 | id="feGaussianBlur2860" | |
| 325 | result="blur" | |
| 326 | stdDeviation="2" | |
| 327 | in="composite1" /> | |
| 328 | <feOffset | |
| 329 | id="feOffset2862" | |
| 330 | result="offset" | |
| 331 | dy="3" | |
| 332 | dx="3" /> | |
| 333 | <feComposite | |
| 334 | id="feComposite2864" | |
| 335 | result="composite2" | |
| 336 | operator="over" | |
| 337 | in2="offset" | |
| 338 | in="SourceGraphic" /> | |
| 339 | </filter> | |
| 340 | <filter | |
| 341 | id="filter2878" | |
| 342 | inkscape:label="Drop Shadow" | |
| 343 | style="color-interpolation-filters:sRGB;"> | |
| 344 | <feFlood | |
| 345 | id="feFlood2868" | |
| 346 | result="flood" | |
| 347 | flood-color="rgb(0,0,0)" | |
| 348 | flood-opacity="0.498039" /> | |
| 349 | <feComposite | |
| 350 | id="feComposite2870" | |
| 351 | result="composite1" | |
| 352 | operator="in" | |
| 353 | in2="SourceGraphic" | |
| 354 | in="flood" /> | |
| 355 | <feGaussianBlur | |
| 356 | id="feGaussianBlur2872" | |
| 357 | result="blur" | |
| 358 | stdDeviation="2" | |
| 359 | in="composite1" /> | |
| 360 | <feOffset | |
| 361 | id="feOffset2874" | |
| 362 | result="offset" | |
| 363 | dy="3" | |
| 364 | dx="3" /> | |
| 365 | <feComposite | |
| 366 | id="feComposite2876" | |
| 367 | result="composite2" | |
| 368 | operator="over" | |
| 369 | in2="offset" | |
| 370 | in="SourceGraphic" /> | |
| 371 | </filter> | |
| 372 | <filter | |
| 373 | id="filter2890" | |
| 374 | inkscape:label="Drop Shadow" | |
| 375 | style="color-interpolation-filters:sRGB;"> | |
| 376 | <feFlood | |
| 377 | id="feFlood2880" | |
| 378 | result="flood" | |
| 379 | flood-color="rgb(0,0,0)" | |
| 380 | flood-opacity="0.498039" /> | |
| 381 | <feComposite | |
| 382 | id="feComposite2882" | |
| 383 | result="composite1" | |
| 384 | operator="in" | |
| 385 | in2="SourceGraphic" | |
| 386 | in="flood" /> | |
| 387 | <feGaussianBlur | |
| 388 | id="feGaussianBlur2884" | |
| 389 | result="blur" | |
| 390 | stdDeviation="2" | |
| 391 | in="composite1" /> | |
| 392 | <feOffset | |
| 393 | id="feOffset2886" | |
| 394 | result="offset" | |
| 395 | dy="3" | |
| 396 | dx="3" /> | |
| 397 | <feComposite | |
| 398 | id="feComposite2888" | |
| 399 | result="composite2" | |
| 400 | operator="over" | |
| 401 | in2="offset" | |
| 402 | in="SourceGraphic" /> | |
| 403 | </filter> | |
| 404 | <filter | |
| 405 | id="filter2902" | |
| 406 | inkscape:label="Drop Shadow" | |
| 407 | style="color-interpolation-filters:sRGB;"> | |
| 408 | <feFlood | |
| 409 | id="feFlood2892" | |
| 410 | result="flood" | |
| 411 | flood-color="rgb(0,0,0)" | |
| 412 | flood-opacity="0.498039" /> | |
| 413 | <feComposite | |
| 414 | id="feComposite2894" | |
| 415 | result="composite1" | |
| 416 | operator="in" | |
| 417 | in2="SourceGraphic" | |
| 418 | in="flood" /> | |
| 419 | <feGaussianBlur | |
| 420 | id="feGaussianBlur2896" | |
| 421 | result="blur" | |
| 422 | stdDeviation="2" | |
| 423 | in="composite1" /> | |
| 424 | <feOffset | |
| 425 | id="feOffset2898" | |
| 426 | result="offset" | |
| 427 | dy="3" | |
| 428 | dx="3" /> | |
| 429 | <feComposite | |
| 430 | id="feComposite2900" | |
| 431 | result="composite2" | |
| 432 | operator="over" | |
| 433 | in2="offset" | |
| 434 | in="SourceGraphic" /> | |
| 435 | </filter> | |
| 436 | <filter | |
| 437 | id="filter2914" | |
| 438 | inkscape:label="Drop Shadow" | |
| 439 | style="color-interpolation-filters:sRGB;"> | |
| 440 | <feFlood | |
| 441 | id="feFlood2904" | |
| 442 | result="flood" | |
| 443 | flood-color="rgb(0,0,0)" | |
| 444 | flood-opacity="0.498039" /> | |
| 445 | <feComposite | |
| 446 | id="feComposite2906" | |
| 447 | result="composite1" | |
| 448 | operator="in" | |
| 449 | in2="SourceGraphic" | |
| 450 | in="flood" /> | |
| 451 | <feGaussianBlur | |
| 452 | id="feGaussianBlur2908" | |
| 453 | result="blur" | |
| 454 | stdDeviation="2" | |
| 455 | in="composite1" /> | |
| 456 | <feOffset | |
| 457 | id="feOffset2910" | |
| 458 | result="offset" | |
| 459 | dy="3" | |
| 460 | dx="3" /> | |
| 461 | <feComposite | |
| 462 | id="feComposite2912" | |
| 463 | result="composite2" | |
| 464 | operator="over" | |
| 465 | in2="offset" | |
| 466 | in="SourceGraphic" /> | |
| 467 | </filter> | |
| 468 | <filter | |
| 469 | id="filter2926" | |
| 470 | inkscape:label="Drop Shadow" | |
| 471 | style="color-interpolation-filters:sRGB;"> | |
| 472 | <feFlood | |
| 473 | id="feFlood2916" | |
| 474 | result="flood" | |
| 475 | flood-color="rgb(0,0,0)" | |
| 476 | flood-opacity="0.498039" /> | |
| 477 | <feComposite | |
| 478 | id="feComposite2918" | |
| 479 | result="composite1" | |
| 480 | operator="in" | |
| 481 | in2="SourceGraphic" | |
| 482 | in="flood" /> | |
| 483 | <feGaussianBlur | |
| 484 | id="feGaussianBlur2920" | |
| 485 | result="blur" | |
| 486 | stdDeviation="2" | |
| 487 | in="composite1" /> | |
| 488 | <feOffset | |
| 489 | id="feOffset2922" | |
| 490 | result="offset" | |
| 491 | dy="3" | |
| 492 | dx="3" /> | |
| 493 | <feComposite | |
| 494 | id="feComposite2924" | |
| 495 | result="composite2" | |
| 496 | operator="over" | |
| 497 | in2="offset" | |
| 498 | in="SourceGraphic" /> | |
| 499 | </filter> | |
| 500 | <filter | |
| 501 | id="filter2938" | |
| 502 | inkscape:label="Drop Shadow" | |
| 503 | style="color-interpolation-filters:sRGB;"> | |
| 504 | <feFlood | |
| 505 | id="feFlood2928" | |
| 506 | result="flood" | |
| 507 | flood-color="rgb(0,0,0)" | |
| 508 | flood-opacity="0.498039" /> | |
| 509 | <feComposite | |
| 510 | id="feComposite2930" | |
| 511 | result="composite1" | |
| 512 | operator="in" | |
| 513 | in2="SourceGraphic" | |
| 514 | in="flood" /> | |
| 515 | <feGaussianBlur | |
| 516 | id="feGaussianBlur2932" | |
| 517 | result="blur" | |
| 518 | stdDeviation="2" | |
| 519 | in="composite1" /> | |
| 520 | <feOffset | |
| 521 | id="feOffset2934" | |
| 522 | result="offset" | |
| 523 | dy="3" | |
| 524 | dx="3" /> | |
| 525 | <feComposite | |
| 526 | id="feComposite2936" | |
| 527 | result="composite2" | |
| 528 | operator="over" | |
| 529 | in2="offset" | |
| 530 | in="SourceGraphic" /> | |
| 531 | </filter> | |
| 532 | <filter | |
| 533 | id="filter2950" | |
| 534 | inkscape:label="Drop Shadow" | |
| 535 | style="color-interpolation-filters:sRGB;"> | |
| 536 | <feFlood | |
| 537 | id="feFlood2940" | |
| 538 | result="flood" | |
| 539 | flood-color="rgb(0,0,0)" | |
| 540 | flood-opacity="0.498039" /> | |
| 541 | <feComposite | |
| 542 | id="feComposite2942" | |
| 543 | result="composite1" | |
| 544 | operator="in" | |
| 545 | in2="SourceGraphic" | |
| 546 | in="flood" /> | |
| 547 | <feGaussianBlur | |
| 548 | id="feGaussianBlur2944" | |
| 549 | result="blur" | |
| 550 | stdDeviation="2" | |
| 551 | in="composite1" /> | |
| 552 | <feOffset | |
| 553 | id="feOffset2946" | |
| 554 | result="offset" | |
| 555 | dy="3" | |
| 556 | dx="3" /> | |
| 557 | <feComposite | |
| 558 | id="feComposite2948" | |
| 559 | result="composite2" | |
| 560 | operator="over" | |
| 561 | in2="offset" | |
| 562 | in="SourceGraphic" /> | |
| 563 | </filter> | |
| 564 | <filter | |
| 565 | id="filter2962" | |
| 566 | inkscape:label="Drop Shadow" | |
| 567 | style="color-interpolation-filters:sRGB;"> | |
| 568 | <feFlood | |
| 569 | id="feFlood2952" | |
| 570 | result="flood" | |
| 571 | flood-color="rgb(0,0,0)" | |
| 572 | flood-opacity="0.498039" /> | |
| 573 | <feComposite | |
| 574 | id="feComposite2954" | |
| 575 | result="composite1" | |
| 576 | operator="in" | |
| 577 | in2="SourceGraphic" | |
| 578 | in="flood" /> | |
| 579 | <feGaussianBlur | |
| 580 | id="feGaussianBlur2956" | |
| 581 | result="blur" | |
| 582 | stdDeviation="2" | |
| 583 | in="composite1" /> | |
| 584 | <feOffset | |
| 585 | id="feOffset2958" | |
| 586 | result="offset" | |
| 587 | dy="3" | |
| 588 | dx="3" /> | |
| 589 | <feComposite | |
| 590 | id="feComposite2960" | |
| 591 | result="composite2" | |
| 592 | operator="over" | |
| 593 | in2="offset" | |
| 594 | in="SourceGraphic" /> | |
| 595 | </filter> | |
| 596 | <filter | |
| 597 | id="filter2974" | |
| 598 | inkscape:label="Drop Shadow" | |
| 599 | style="color-interpolation-filters:sRGB;"> | |
| 600 | <feFlood | |
| 601 | id="feFlood2964" | |
| 602 | result="flood" | |
| 603 | flood-color="rgb(0,0,0)" | |
| 604 | flood-opacity="0.498039" /> | |
| 605 | <feComposite | |
| 606 | id="feComposite2966" | |
| 607 | result="composite1" | |
| 608 | operator="in" | |
| 609 | in2="SourceGraphic" | |
| 610 | in="flood" /> | |
| 611 | <feGaussianBlur | |
| 612 | id="feGaussianBlur2968" | |
| 613 | result="blur" | |
| 614 | stdDeviation="2" | |
| 615 | in="composite1" /> | |
| 616 | <feOffset | |
| 617 | id="feOffset2970" | |
| 618 | result="offset" | |
| 619 | dy="3" | |
| 620 | dx="3" /> | |
| 621 | <feComposite | |
| 622 | id="feComposite2972" | |
| 623 | result="composite2" | |
| 624 | operator="over" | |
| 625 | in2="offset" | |
| 626 | in="SourceGraphic" /> | |
| 627 | </filter> | |
| 628 | <filter | |
| 629 | id="filter2986" | |
| 630 | inkscape:label="Drop Shadow" | |
| 631 | style="color-interpolation-filters:sRGB;"> | |
| 632 | <feFlood | |
| 633 | id="feFlood2976" | |
| 634 | result="flood" | |
| 635 | flood-color="rgb(0,0,0)" | |
| 636 | flood-opacity="0.498039" /> | |
| 637 | <feComposite | |
| 638 | id="feComposite2978" | |
| 639 | result="composite1" | |
| 640 | operator="in" | |
| 641 | in2="SourceGraphic" | |
| 642 | in="flood" /> | |
| 643 | <feGaussianBlur | |
| 644 | id="feGaussianBlur2980" | |
| 645 | result="blur" | |
| 646 | stdDeviation="2" | |
| 647 | in="composite1" /> | |
| 648 | <feOffset | |
| 649 | id="feOffset2982" | |
| 650 | result="offset" | |
| 651 | dy="3" | |
| 652 | dx="3" /> | |
| 653 | <feComposite | |
| 654 | id="feComposite2984" | |
| 655 | result="composite2" | |
| 656 | operator="over" | |
| 657 | in2="offset" | |
| 658 | in="SourceGraphic" /> | |
| 659 | </filter> | |
| 660 | </defs> | |
| 661 | <sodipodi:namedview | |
| 662 | inkscape:snap-text-baseline="false" | |
| 663 | inkscape:document-rotation="0" | |
| 664 | fit-margin-bottom="20" | |
| 665 | fit-margin-right="20" | |
| 666 | fit-margin-left="20" | |
| 667 | fit-margin-top="20" | |
| 668 | inkscape:current-layer="svg4610" | |
| 669 | inkscape:cy="370.55742" | |
| 670 | inkscape:cx="398.61418" | |
| 671 | inkscape:zoom="1.3753763" | |
| 672 | showgrid="false" | |
| 673 | id="namedview4612" | |
| 674 | inkscape:window-height="1280" | |
| 675 | inkscape:window-width="2055" | |
| 676 | inkscape:pageshadow="2" | |
| 677 | inkscape:pageopacity="1" | |
| 678 | guidetolerance="10" | |
| 679 | gridtolerance="10" | |
| 680 | objecttolerance="10" | |
| 681 | borderopacity="1" | |
| 682 | bordercolor="#666666" | |
| 683 | pagecolor="#ffffff" | |
| 684 | inkscape:window-x="215" | |
| 685 | inkscape:window-y="26" | |
| 686 | inkscape:window-maximized="0" /> | |
| 687 | <path | |
| 688 | sodipodi:nodetypes="ccssssc" | |
| 689 | inkscape:connector-curvature="0" | |
| 690 | style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#df4d65;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 691 | d="M 53.35547,445.11522 V 790.96744 H 741.0332 c 1.6112,0 2.90821,-1.29701 2.90821,-2.9082 V 448.02342 c 0,-1.6112 -1.297,-2.9082 -2.90821,-2.9082 z" | |
| 692 | id="path9961" /> | |
| 693 | <path | |
| 694 | sodipodi:nodetypes="sssccssss" | |
| 695 | id="path9940" | |
| 696 | d="m 20.5,787.82486 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.35253,0.91797 2.22265,0.91797 H 53.35547 V 445.11522 H 23.64062 c -0.87012,0 -1.65487,0.35019 -2.22265,0.91797 -0.56778,0.56778 -0.91797,1.35254 -0.91797,2.22266 z" | |
| 697 | style="fill:#df4d65;fill-opacity:1;fill-rule:nonzero;stroke:#df4d65;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 698 | inkscape:connector-curvature="0" /> | |
| 699 | <path | |
| 700 | sodipodi:nodetypes="sssccssss" | |
| 701 | id="path11125" | |
| 702 | d="m 20.5,423.31014 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.354494,0.9764 2.22265,0.91797 H 53.35547 V 210.6005 H 23.64062 c -0.87012,0 -1.65487,0.3502 -2.22265,0.918 C 20.85019,212.08629 20.5,212.871 20.5,213.74109 Z" | |
| 703 | style="fill:#3e3e3e;fill-opacity:1;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 704 | inkscape:connector-curvature="0" /> | |
| 705 | <path | |
| 706 | sodipodi:nodetypes="ccssssc" | |
| 707 | inkscape:connector-curvature="0" | |
| 708 | style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 709 | d="m 53.35547,210.6005 v 215.85222 h 687.67774 c 1.6112,0 2.9082,-1.29701 2.9082,-2.9082 V 213.5087 c 0,-1.6112 -1.29701,-2.90352 -2.9082,-2.9082 z" | |
| 710 | id="path11123" /> | |
| 711 | <path | |
| 712 | id="path6150" | |
| 713 | d="m 557.756,222.53493 c -0.87012,0 -1.65683,0.35019 -2.22461,0.91797 -0.56778,0.56778 -0.91797,1.35253 -0.91797,2.22265 v 29.71485 h 165.6211 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 714 | style="fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2926)" | |
| 715 | inkscape:connector-curvature="0" | |
| 716 | sodipodi:nodetypes="sssccssss" /> | |
| 717 | <path | |
| 718 | sodipodi:nodetypes="ccssssc" | |
| 719 | id="path6134" | |
| 720 | d="m 720.75716,255.39041 h -165.6211 v 152.63392 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z" | |
| 721 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2926)" | |
| 722 | inkscape:connector-curvature="0" /> | |
| 723 | <path | |
| 724 | id="path6082" | |
| 725 | d="m 317.13559,222.53494 c -0.87011,0 -1.65683,0.35019 -2.2246,0.91797 -0.56779,0.56778 -0.91798,1.35253 -0.91798,2.22265 v 29.71485 h 165.62111 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91798,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 726 | style="fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2938)" | |
| 727 | inkscape:connector-curvature="0" | |
| 728 | sodipodi:nodetypes="sssccssss" /> | |
| 729 | <path | |
| 730 | sodipodi:nodetypes="ccssssc" | |
| 731 | id="path6080" | |
| 732 | d="M 479.61412,255.39041 H 313.99301 v 152.63392 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.90821,-1.29701 2.90821,-2.90821 z" | |
| 733 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2938)" | |
| 734 | inkscape:connector-curvature="0" /> | |
| 735 | <path | |
| 736 | id="path10980" | |
| 737 | d="M 53.35547,20.500012 V 188.35224 h 687.67774 c 1.6112,0 2.9082,-1.29701 2.9082,-2.9082 V 23.408212 c 0,-1.6112 -1.29701,-2.912886 -2.9082,-2.9082 z" | |
| 738 | style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 739 | inkscape:connector-curvature="0" | |
| 740 | sodipodi:nodetypes="ccssssc" /> | |
| 741 | <path | |
| 742 | inkscape:connector-curvature="0" | |
| 743 | style="fill:#3e3e3e;fill-opacity:1;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 744 | d="m 20.5,185.20966 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.35253,0.91797 2.22265,0.91797 H 53.35547 V 20.500012 H 23.64062 c -0.87012,0 -1.65487,0.350201 -2.22265,0.918 -0.56778,0.5678 -0.91797,1.3525 -0.91797,2.2226 z" | |
| 745 | id="path10982" | |
| 746 | sodipodi:nodetypes="sssccssss" /> | |
| 747 | <path | |
| 748 | sodipodi:nodetypes="sssccssss" | |
| 749 | inkscape:connector-curvature="0" | |
| 750 | style="fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2962)" | |
| 751 | d="m 557.75599,36.704447 c -0.87012,0 -1.65683,0.35019 -2.22461,0.91797 -0.56778,0.56778 -0.91797,1.35253 -0.91797,2.22265 v 29.71485 h 165.6211 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 752 | id="path4857" /> | |
| 753 | <path | |
| 754 | inkscape:connector-curvature="0" | |
| 755 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2962)" | |
| 756 | d="M 720.23451,69.559917 H 554.61341 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z" | |
| 757 | id="path4853" /> | |
| 758 | <path | |
| 759 | sodipodi:nodetypes="sssccssss" | |
| 760 | inkscape:connector-curvature="0" | |
| 761 | style="fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2974)" | |
| 762 | d="m 317.13558,36.704447 c -0.87011,0 -1.65683,0.35019 -2.2246,0.91797 -0.56779,0.56778 -0.91798,1.35253 -0.91798,2.22265 v 29.71485 h 165.62111 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91798,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 763 | id="path5726" /> | |
| 764 | <path | |
| 765 | inkscape:connector-curvature="0" | |
| 766 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2974)" | |
| 767 | d="M 479.61411,69.559917 H 313.993 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 H 476.7059 c 1.6112,0 2.90821,-1.29701 2.90821,-2.90821 z" | |
| 768 | id="path5724" /> | |
| 769 | <path | |
| 770 | id="path4721" | |
| 771 | d="m 235.85308,44.704447 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z" | |
| 772 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 773 | inkscape:connector-curvature="0" /> | |
| 774 | <path | |
| 775 | sodipodi:nodetypes="sssccssss" | |
| 776 | id="path4719" | |
| 777 | d="m 76.515197,36.704447 c -0.870125,0 -1.656831,0.35019 -2.22461,0.91797 -0.567778,0.56778 -0.917968,1.35253 -0.917968,2.22265 v 29.71485 H 238.99371 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 778 | style="fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2986)" | |
| 779 | inkscape:connector-curvature="0" /> | |
| 780 | <path | |
| 781 | id="path4723" | |
| 782 | d="M 238.99372,69.559917 H 73.372613 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 H 236.08552 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z" | |
| 783 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2986)" | |
| 784 | inkscape:connector-curvature="0" /> | |
| 785 | <path | |
| 786 | id="rect4622" | |
| 787 | d="m 76.280822,44.704447 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z" | |
| 788 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 789 | inkscape:connector-curvature="0" /> | |
| 790 | <path | |
| 791 | sodipodi:nodetypes="cc" | |
| 792 | inkscape:connector-curvature="0" | |
| 793 | id="path9889" | |
| 794 | d="m 397.61301,500.62068 -0.50618,32.59418" | |
| 795 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:1.9694221;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9893)" /> | |
| 796 | <path | |
| 797 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9715)" | |
| 798 | d="m 554.61351,648.83688 -69.6817,47.69253" | |
| 799 | id="path9711" | |
| 800 | inkscape:connector-curvature="0" | |
| 801 | sodipodi:nodetypes="cc" /> | |
| 802 | <path | |
| 803 | sodipodi:nodetypes="cc" | |
| 804 | inkscape:connector-curvature="0" | |
| 805 | id="path9675" | |
| 806 | d="M 554.61351,567.95047 484.93181,615.643" | |
| 807 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9679)" /> | |
| 808 | <rect | |
| 809 | ry="3.9839513" | |
| 810 | rx="3.9205718" | |
| 811 | y="537.09552" | |
| 812 | x="554.61353" | |
| 813 | height="32.855" | |
| 814 | width="165.621" | |
| 815 | id="rect9618" | |
| 816 | style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2842)" /> | |
| 817 | <rect | |
| 818 | ry="3.9839513" | |
| 819 | rx="3.9205718" | |
| 820 | y="537.09552" | |
| 821 | x="73.372665" | |
| 822 | height="32.855" | |
| 823 | width="165.621" | |
| 824 | id="rect9614" | |
| 825 | style="opacity:1;fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2914)" /> | |
| 826 | <path | |
| 827 | inkscape:connector-curvature="0" | |
| 828 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 829 | d="m 235.85308,545.09525 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z" | |
| 830 | id="path9323" /> | |
| 831 | <path | |
| 832 | inkscape:connector-curvature="0" | |
| 833 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 834 | d="m 76.280823,545.09525 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z" | |
| 835 | id="path9327" /> | |
| 836 | <rect | |
| 837 | style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2902)" | |
| 838 | id="rect9616" | |
| 839 | width="165.621" | |
| 840 | height="32.855" | |
| 841 | x="313.99307" | |
| 842 | y="537.09552" | |
| 843 | rx="3.9205718" | |
| 844 | ry="3.9839513" /> | |
| 845 | <path | |
| 846 | sodipodi:nodetypes="cc" | |
| 847 | inkscape:connector-curvature="0" | |
| 848 | id="path9491" | |
| 849 | d="m 240.99257,554.11276 65.23376,-1.01307" | |
| 850 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9513)" /> | |
| 851 | <path | |
| 852 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9640)" | |
| 853 | d="m 481.61298,554.11276 65.23376,-1.01307" | |
| 854 | id="path9501" | |
| 855 | inkscape:connector-curvature="0" | |
| 856 | sodipodi:nodetypes="cc" /> | |
| 857 | <rect | |
| 858 | ry="3.9839513" | |
| 859 | rx="3.9205718" | |
| 860 | y="617.79578" | |
| 861 | x="313.99307" | |
| 862 | height="32.855" | |
| 863 | width="165.621" | |
| 864 | id="rect9620" | |
| 865 | style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2890)" /> | |
| 866 | <path | |
| 867 | sodipodi:nodetypes="cc" | |
| 868 | inkscape:connector-curvature="0" | |
| 869 | id="path9681" | |
| 870 | d="m 481.61298,634.81299 65.23376,-1.01307" | |
| 871 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9685)" /> | |
| 872 | <rect | |
| 873 | style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2854)" | |
| 874 | id="rect9687" | |
| 875 | width="165.621" | |
| 876 | height="32.855" | |
| 877 | x="554.61353" | |
| 878 | y="617.79578" | |
| 879 | rx="3.9205718" | |
| 880 | ry="3.9839513" /> | |
| 881 | <path | |
| 882 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9750)" | |
| 883 | d="m 481.61298,715.51321 65.23376,-1.01307" | |
| 884 | id="path9734" | |
| 885 | inkscape:connector-curvature="0" | |
| 886 | sodipodi:nodetypes="cc" /> | |
| 887 | <rect | |
| 888 | ry="3.9839513" | |
| 889 | rx="3.9205718" | |
| 890 | y="698.49591" | |
| 891 | x="554.61353" | |
| 892 | height="32.855" | |
| 893 | width="165.621" | |
| 894 | id="rect9736" | |
| 895 | style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2866)" /> | |
| 896 | <path | |
| 897 | id="path9830" | |
| 898 | d="m 356.40451,489.45323 c -0.80426,0 -1.45167,0.64741 -1.45167,1.45166 v 0.11602 c 0,-0.43433 0.1748,-0.82605 0.45822,-1.10946 0.28341,-0.28342 0.6761,-0.45822 1.11043,-0.45822 z" | |
| 899 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10902636;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 900 | inkscape:connector-curvature="0" /> | |
| 901 | <rect | |
| 902 | style="opacity:1;fill:#ffb73a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.9391377;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 903 | id="rect9826" | |
| 904 | width="120.98324" | |
| 905 | height="24" | |
| 906 | x="336.82672" | |
| 907 | y="477.86002" | |
| 908 | rx="2.8639088" | |
| 909 | ry="2.9102066" /> | |
| 910 | <path | |
| 911 | id="path10514" | |
| 912 | d="m 235.85301,637.23875 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z" | |
| 913 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 914 | inkscape:connector-curvature="0" /> | |
| 915 | <rect | |
| 916 | style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2878)" | |
| 917 | id="rect9717" | |
| 918 | width="165.621" | |
| 919 | height="32.855" | |
| 920 | x="313.99307" | |
| 921 | y="698.49591" | |
| 922 | rx="3.9205718" | |
| 923 | ry="3.9839513" /> | |
| 924 | <path | |
| 925 | id="path10537" | |
| 926 | d="M 238.99366,636.97465 H 73.372671 V 729.175 c 0,1.2055 0.970418,2.17592 2.175911,2.17592 H 236.81776 c 1.20549,0 2.1759,-0.97042 2.1759,-2.17592 z" | |
| 927 | style="opacity:1;fill:#333333;fill-opacity:0.93333333;fill-rule:nonzero;stroke:none;stroke-width:0.16342013;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 928 | inkscape:connector-curvature="0" | |
| 929 | sodipodi:nodetypes="ccssssc" /> | |
| 930 | <path | |
| 931 | sodipodi:nodetypes="sssccssss" | |
| 932 | id="path10516" | |
| 933 | d="m 75.723937,612.39226 c -0.651025,0 -1.239637,0.26201 -1.664447,0.68682 -0.424811,0.42482 -0.686822,1.01196 -0.686822,1.66299 v 22.23258 H 238.99366 v -22.23258 c 0,-0.65103 -0.26201,-1.23817 -0.68682,-1.66299 -0.42481,-0.42481 -1.01197,-0.68682 -1.66299,-0.68682 z" | |
| 934 | style="opacity:1;fill:#ffb73a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.16342013;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 935 | inkscape:connector-curvature="0" /> | |
| 936 | <path | |
| 937 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker10933)" | |
| 938 | d="m 240.99257,715.51318 65.23376,-1.01307" | |
| 939 | id="path10929" | |
| 940 | inkscape:connector-curvature="0" | |
| 941 | sodipodi:nodetypes="cc" /> | |
| 942 | <path | |
| 943 | style="fill:#df4d65;fill-opacity:1;fill-rule:evenodd;stroke-width:0.05250736" | |
| 944 | d="m 399.47357,99.155037 -0.12716,0.0127 -0.12716,0.0141 -0.12574,0.0141 -0.12716,0.0155 -0.61883,0.967813 -0.29106,0.9325 -0.17943,0.91977 -0.1215,1.09073 -0.30943,0.0636 -0.308,0.0678 -0.308,0.0735 -0.30801,0.0763 -0.58069,-0.93108 -0.5609,-0.75023 -0.66687,-0.71349 -0.97912,-0.6033 -0.12009,0.0409 -0.1201,0.0424 -0.12009,0.0424 -0.12009,0.0424 -0.38854,1.08085 -0.0763,0.97487 0.0297,0.93673 0.12575,1.08932 -0.28823,0.1314 -0.28681,0.13563 -0.28398,0.13987 -0.28117,0.14271 -0.77425,-0.77849 -0.7149,-0.60612 -0.80816,-0.54819 -1.0879,-0.37017 -0.10879,0.0678 -0.10738,0.0679 -0.10738,0.0678 -0.10738,0.0678 -0.13846,1.14018 0.14129,0.9664 0.23735,0.90706 0.36594,1.03563 -0.25149,0.19215 -0.24865,0.19497 -0.24585,0.19922 -0.24442,0.20204 -0.92825,-0.58634 -0.83077,-0.43234 -0.90988,-0.35463 -1.14301,-0.11868 -0.0904,0.0904 -0.0904,0.089 -0.089,0.0904 -0.0904,0.0904 0.11868,1.14301 0.35462,0.90988 0.43234,0.83218 0.58634,0.92684 -0.20204,0.24442 -0.19921,0.24584 -0.19498,0.24867 -0.19215,0.25148 -1.03563,-0.36593 -0.90705,-0.23736 -0.96641,-0.14128 -1.14018,0.13845 -0.0678,0.10738 -0.0678,0.10738 -0.0678,0.10738 -0.0678,0.10879 0.37016,1.0879 0.5482,0.80816 0.60612,0.71491 0.77848,0.77425 -0.1427,0.28117 -0.13987,0.28398 -0.13564,0.28681 -0.13139,0.28823 -1.09073,-0.12575 -0.93532,-0.0297 -0.97487,0.0763 -1.08084,0.38854 -0.0424,0.12009 -0.0424,0.12009 -0.0424,0.1201 -0.0409,0.12009 0.6033,0.97912 0.7135,0.66686 0.75023,0.56091 0.93107,0.58069 -0.0763,0.30801 -0.0735,0.308 -0.0678,0.308 -0.0636,0.30942 -1.09073,0.1215 -0.91977,0.17944 -0.9325,0.29105 -0.96781,0.61883 -0.0156,0.12717 -0.0141,0.12574 -0.0141,0.12716 -0.0127,0.12716 0.80533,0.81804 0.84348,0.49168 0.85619,0.38006 1.03704,0.36028 -0.006,0.31648 -0.003,0.31648 0.003,0.31648 0.006,0.31648 -1.03704,0.35887 -0.85619,0.38006 -0.84348,0.49168 -0.80533,0.81946 0.0127,0.12716 0.0141,0.12574 0.0141,0.12716 0.0156,0.12574 0.96781,0.62026 0.9325,0.29104 0.91977,0.17944 1.09073,0.12009 0.0636,0.30942 0.0678,0.30941 0.0735,0.30801 0.0763,0.30659 -0.93107,0.5821 -0.75023,0.56091 -0.7135,0.66687 -0.6033,0.97771 0.0409,0.12008 0.0424,0.12151 0.0424,0.1201 0.0424,0.11868 1.08084,0.38995 0.97487,0.0763 0.93532,-0.0297 1.09073,-0.12574 0.13139,0.28822 0.13564,0.2854 0.13987,0.28399 0.1427,0.28257 -0.77848,0.77425 -0.60612,0.7135 -0.5482,0.80957 -0.37016,1.08791 0.0678,0.10737 0.0678,0.10879 0.0678,0.10738 0.0678,0.10738 1.14018,0.13846 0.96641,-0.1427 0.90705,-0.23736 1.03563,-0.36452 0.19215,0.25149 0.19498,0.24866 0.19921,0.24584 0.20204,0.24302 -0.58634,0.92825 -0.43234,0.83076 -0.35462,0.9113 -0.11868,1.14159 0.0904,0.0918 0.089,0.089 0.0904,0.0904 0.0904,0.089 1.14301,-0.11868 0.90988,-0.35321 0.83077,-0.43375 0.92825,-0.58493 0.24442,0.20204 0.24585,0.19921 0.24865,0.19497 0.25149,0.19216 -0.36594,1.03563 -0.23735,0.90564 -0.14129,0.9664 0.13846,1.14018 0.10738,0.0692 0.10738,0.0678 0.10738,0.0678 0.10879,0.0664 1.0879,-0.37017 0.80816,-0.54677 0.7149,-0.60754 0.77425,-0.77708 0.28117,0.14271 0.28398,0.13987 0.28681,0.13422 0.28823,0.13139 -0.12575,1.09074 -0.0297,0.93673 0.0763,0.97346 0.38854,1.08084 0.12009,0.0438 0.12009,0.0424 0.1201,0.041 0.12009,0.0409 0.97912,-0.60188 0.66687,-0.71349 0.5609,-0.75165 0.58069,-0.93108 0.30801,0.0777 0.308,0.072 0.308,0.0692 0.30943,0.0636 0.1215,1.09073 0.17943,0.91978 0.29106,0.93249 0.61883,0.9664 0.12716,0.0156 0.12574,0.0141 0.12716,0.0141 0.12716,0.0141 0.81806,-0.80533 0.49167,-0.8449 0.38006,-0.85619 0.36028,-1.03704 0.31648,0.007 0.31648,0.003 0.31649,-0.003 0.31648,-0.007 0.36028,1.03704 0.37865,0.85619 0.49167,0.8449 0.81947,0.80533 0.12715,-0.0141 0.12574,-0.0141 0.12717,-0.0141 0.12574,-0.0156 0.62025,-0.9664 0.29104,-0.93249 0.17944,-0.91978 0.12009,-1.09073 0.30942,-0.0636 0.30942,-0.0692 0.30799,-0.072 0.3066,-0.0777 0.58211,0.93108 0.5609,0.75165 0.66687,0.71349 0.97771,0.60188 0.12009,-0.0409 0.1215,-0.041 0.1201,-0.0424 0.11868,-0.0438 0.38995,-1.08084 0.0763,-0.97346 -0.0297,-0.93673 -0.12574,-1.09074 0.28822,-0.13139 0.2854,-0.13422 0.28398,-0.13987 0.28258,-0.14271 0.77424,0.77708 0.7135,0.60754 0.80957,0.54677 1.08791,0.37017 0.10737,-0.0664 0.10879,-0.0678 0.10738,-0.0678 0.10738,-0.0692 0.13847,-1.14018 -0.14271,-0.9664 -0.23737,-0.90564 -0.36452,-1.03563 0.25149,-0.19216 0.24866,-0.19497 0.24585,-0.19921 0.24301,-0.20204 0.92825,0.58493 0.83077,0.43375 0.91129,0.35321 1.1416,0.11868 0.0904,-0.089 0.0904,-0.0904 0.0904,-0.089 0.089,-0.0918 -0.11868,-1.14159 -0.35321,-0.9113 -0.43375,-0.83076 -0.58492,-0.92825 0.20203,-0.24302 0.19921,-0.24584 0.19498,-0.24866 0.19215,-0.25149 1.03563,0.36452 0.90564,0.23736 0.9664,0.1427 1.14018,-0.13846 0.0692,-0.10738 0.0678,-0.10738 0.0678,-0.10879 0.0664,-0.10737 -0.37017,-1.08791 -0.54677,-0.80957 -0.60754,-0.7135 -0.77706,-0.77425 0.1427,-0.28257 0.13986,-0.28399 0.13423,-0.2854 0.13139,-0.28822 1.09073,0.12574 0.93674,0.0297 0.97345,-0.0763 1.08085,-0.38995 0.0438,-0.11868 0.0424,-0.1201 0.0409,-0.12151 0.041,-0.12008 -0.6019,-0.97771 -0.71349,-0.66687 -0.75164,-0.56091 -0.93108,-0.5821 0.0777,-0.30659 0.072,-0.30801 0.0692,-0.30941 0.0636,-0.30942 1.09073,-0.12009 0.91978,-0.17944 0.93249,-0.29104 0.9664,-0.62026 0.0155,-0.12574 0.0141,-0.12716 0.0141,-0.12574 0.0141,-0.12716 -0.80533,-0.81946 -0.8449,-0.49168 -0.85619,-0.38006 -1.03704,-0.35887 0.007,-0.31648 0.003,-0.31648 -0.003,-0.31648 -0.007,-0.31648 1.03704,-0.36028 0.85619,-0.38006 0.8449,-0.49168 0.80533,-0.81804 -0.0141,-0.12716 -0.0141,-0.12716 -0.0141,-0.12574 -0.0155,-0.12717 -0.9664,-0.61883 -0.93249,-0.29105 -0.91978,-0.17944 -1.09073,-0.1215 -0.0636,-0.30942 -0.0692,-0.308 -0.072,-0.308 -0.0777,-0.30801 0.93108,-0.58069 0.75164,-0.56091 0.71349,-0.66686 0.6019,-0.97912 -0.041,-0.12009 -0.0409,-0.1201 -0.0424,-0.12009 -0.0438,-0.12009 -1.08085,-0.38854 -0.97345,-0.0763 -0.93674,0.0297 -1.09073,0.12575 -0.13139,-0.28823 -0.13423,-0.28681 -0.13986,-0.28398 -0.1427,-0.28117 0.77706,-0.77425 0.60754,-0.71491 0.54677,-0.80816 0.37017,-1.0879 -0.0664,-0.10879 -0.0678,-0.10738 -0.0678,-0.10738 -0.0692,-0.10738 -1.14018,-0.13845 -0.9664,0.14128 -0.90564,0.23736 -1.03563,0.36593 -0.19215,-0.25148 -0.19498,-0.24867 -0.19921,-0.24584 -0.20203,-0.24442 0.58492,-0.92684 0.43375,-0.83218 0.35321,-0.90988 0.11868,-1.14301 -0.089,-0.0904 -0.0904,-0.0904 -0.0904,-0.089 -0.0904,-0.0904 -1.1416,0.11868 -0.91129,0.35463 -0.83077,0.43234 -0.92825,0.58634 -0.24301,-0.20204 -0.24585,-0.19922 -0.24866,-0.19497 -0.25149,-0.19215 0.36452,-1.03563 0.23737,-0.90706 0.14271,-0.9664 -0.13847,-1.14018 -0.10738,-0.0678 -0.10738,-0.0678 -0.10879,-0.0679 -0.10737,-0.0678 -1.08791,0.37017 -0.80957,0.54819 -0.7135,0.60612 -0.77424,0.77849 -0.28258,-0.14271 -0.28398,-0.13987 -0.2854,-0.13563 -0.28822,-0.1314 0.12574,-1.08932 0.0297,-0.93673 -0.0763,-0.97487 -0.38995,-1.08085 -0.11868,-0.0424 -0.1201,-0.0424 -0.1215,-0.0424 -0.12009,-0.0409 -0.97771,0.6033 -0.66687,0.71349 -0.5609,0.75023 -0.58211,0.93108 -0.3066,-0.0763 -0.30799,-0.0735 -0.30942,-0.0678 -0.30942,-0.0636 -0.12009,-1.09073 -0.17944,-0.91977 -0.29104,-0.9325 -0.62025,-0.967813 -0.12574,-0.0155 -0.12717,-0.0141 -0.12574,-0.0141 -0.12715,-0.0127 -0.81947,0.80533 -0.49167,0.843483 -0.37865,0.8562 -0.36028,1.03704 -0.31648,-0.006 -0.31649,-0.003 -0.31648,0.003 -0.31648,0.006 -0.36028,-1.03704 -0.38006,-0.8562 -0.49167,-0.843483 z m 2.68302,20.688573 a 5.3990039,5.3990039 0 0 1 5.39856,5.39997 5.3990039,5.3990039 0 0 1 -5.39856,5.39855 5.3990039,5.3990039 0 0 1 -5.39996,-5.39855 5.3990039,5.3990039 0 0 1 5.39996,-5.39997 z" | |
| 945 | id="path5693" | |
| 946 | inkscape:connector-curvature="0" /> | |
| 947 | <path | |
| 948 | inkscape:connector-curvature="0" | |
| 949 | d="m 380.9529,101.31918 a 4.37599,4.37599 0 0 1 -4.37599,4.37599 4.37599,4.37599 0 0 1 -4.37599,-4.37599 4.37599,4.37599 0 0 1 4.37599,-4.375983 4.37599,4.37599 0 0 1 4.37599,4.375983 z m 4.63493,-1.27213 c -0.32212,-0.118873 -0.95326,0.0926 -0.92258,-0.401293 -0.13877,-0.39635 -0.21401,-0.74537 0.27363,-0.88946 0.78055,-0.47633 1.45123,-1.16128 1.74461,-2.04171 0.15411,-0.39145 -0.3432,-0.48754 -0.63657,-0.53536 -0.91614,-0.25589 -1.86519,0.0578 -2.73328,0.35995 -0.11023,-0.31345 -0.69059,-0.56868 -0.47901,-0.88657 0.56405,-0.84324 0.99162,-1.8335 0.85012,-2.86709 -0.031,-0.41955 -0.52073,-0.29038 -0.8058,-0.20618 -0.93646,0.16692 -1.65537,0.86143 -2.30642,1.51029 -0.23528,-0.23464 -0.86896,-0.21274 -0.81624,-0.59097 0.14241,-1.00446 0.0978,-2.08217 -0.47806,-2.95201 -0.20995,-0.36459 -0.59514,-0.0357 -0.81544,0.16385 -0.7713,0.55671 -1.11768,1.49434 -1.42274,2.36143 -0.3138,-0.10927 -0.87519,0.18536 -0.99181,-0.17826 -0.30757,-0.96675 -0.81528,-1.91841 -1.71153,-2.45229 -0.34734,-0.23737 -0.55176,0.22606 -0.66364,0.50149 -0.45331,0.83622 -0.35865,1.83127 -0.25723,2.74482 -0.33012,0.0378 -0.70817,0.54672 -0.97098,0.26977 -0.69651,-0.73763 -1.56687,-1.37476 -2.60601,-1.46682 -0.41593,-0.0631 -0.39904,0.443 -0.38034,0.73971 -0.0456,0.95009 0.47143,1.80554 0.95918,2.58463 -0.28104,0.17725 -0.40076,0.79984 -0.75777,0.6643 -0.94759,-0.36236 -2.00818,-0.55864 -2.98437,-0.1908 -0.40215,0.12357 -0.16723,0.57227 -0.0217,0.83147 0.37114,0.8758 1.2081,1.42221 1.98561,1.91248 -0.17632,0.28163 -0.0141,0.89453 -0.39449,0.92732 -1.01097,0.0847 -2.05173,0.367983 -2.77166,1.122963 -0.3087,0.2858 0.0976,0.58816 0.34122,0.75853 0.71439,0.62804 1.70558,0.75716 2.6188,0.8616 -0.0367,0.33025 0.37548,0.81204 0.0469,1.00665 -0.87415,0.51488 -1.68886,1.22174 -2.00994,2.2143 -0.15411,0.39144 0.3432,0.48753 0.63657,0.53535 0.91612,0.25589 1.86516,-0.0578 2.73323,-0.35995 0.11029,0.31341 0.69063,0.56869 0.47905,0.88657 -0.56412,0.8432 -0.99155,1.83352 -0.85015,2.86709 0.031,0.41952 0.52075,0.2904 0.80584,0.20618 0.93644,-0.16692 1.65537,-0.86139 2.30637,-1.51029 0.2353,0.23464 0.86901,0.21272 0.81629,0.59098 -0.14241,1.00446 -0.0978,2.08215 0.47802,2.95202 0.20997,0.36455 0.59517,0.0357 0.81548,-0.16387 0.77125,-0.55674 1.11768,-1.49435 1.42274,-2.36142 0.31379,0.10926 0.8752,-0.18537 0.99181,0.17824 0.30754,0.96678 0.81527,1.91842 1.71153,2.45229 0.34733,0.23738 0.55172,-0.22608 0.66362,-0.50146 0.45335,-0.83621 0.35866,-1.83128 0.25725,-2.74484 0.33011,-0.0378 0.70812,-0.54672 0.97093,-0.26977 0.69656,0.7376 1.5669,1.37477 2.60606,1.46683 0.41593,0.0632 0.39897,-0.44304 0.38032,-0.73972 0.0457,-0.95011 -0.4715,-1.8055 -0.95916,-2.58463 0.28105,-0.17722 0.40074,-0.79983 0.75772,-0.6643 0.94761,0.36234 2.00821,0.55865 2.98442,0.1908 0.40215,-0.12357 0.16723,-0.57228 0.0217,-0.83146 -0.37116,-0.87579 -1.20814,-1.42218 -1.98561,-1.91249 0.17632,-0.28163 0.0141,-0.89453 0.39449,-0.92732 1.01097,-0.0847 2.05173,-0.36799 2.77166,-1.12295 0.3087,-0.28581 -0.0976,-0.58817 -0.34122,-0.75854 -0.47483,-0.43652 -1.13407,-0.61787 -1.75144,-0.75008 z" | |
| 950 | style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke-width:0.04014921" | |
| 951 | id="path5687" /> | |
| 952 | <path | |
| 953 | style="fill:#05556e;fill-opacity:1;stroke-width:0.11881336" | |
| 954 | id="path4816" | |
| 955 | d="m 619.92265,90.37586 h 2.72717 v 2.71445 h 2.51374 v -2.71445 h 2.72716 v 8.21477 h -2.72716 v -2.73825 h -2.49003 v 2.73825 h -2.75088 m 11.57268,-5.47651 h -2.40702 v -2.73826 h 7.55307 v 2.73826 h -2.41888 v 5.47651 h -2.72717 m 6.34363,-8.21477 h 2.8576 l 1.75487,2.89303 1.75487,-2.89303 h 2.8576 v 8.21477 h -2.72717 v -4.07167 l -1.90901,2.95256 -1.90902,-2.95256 v 4.07167 h -2.67974 m 10.57667,-8.21477 h 2.72717 v 5.50033 h 3.86546 v 2.71444 h -6.59263" | |
| 956 | inkscape:connector-curvature="0" /> | |
| 957 | <path | |
| 958 | id="path4818" | |
| 959 | d="m 619.82779,146.45062 -3.91289,-44.09786 h 43.01811 l -3.91289,44.07405 -17.63174,4.90505" | |
| 960 | inkscape:connector-curvature="0" | |
| 961 | style="fill:#e44d26;stroke-width:0.11881336" /> | |
| 962 | <path | |
| 963 | id="path4820" | |
| 964 | d="m 637.42396,147.58164 v -41.60962 h 17.5843 l -3.3556,37.62129" | |
| 965 | inkscape:connector-curvature="0" | |
| 966 | style="fill:#f16529;stroke-width:0.11881336" /> | |
| 967 | <path | |
| 968 | id="path4822" | |
| 969 | d="m 623.90669,111.3652 h 13.51727 v 5.40508 h -7.61236 l 0.498,5.53605 h 7.11436 v 5.39318 h -12.04697 m 0.23714,2.71444 h 5.40691 l 0.37943,4.32169 6.02349,1.61914 v 5.64319 L 626.373,138.90255" | |
| 970 | inkscape:connector-curvature="0" | |
| 971 | style="fill:#ebebeb;stroke-width:0.11881336" /> | |
| 972 | <path | |
| 973 | id="path4824" | |
| 974 | d="m 650.89379,111.3652 h -13.49355 v 5.40508 h 12.99555 m -0.48615,5.53605 h -12.5094 v 5.40508 h 6.64006 l -0.62843,7.02423 -6.01163,1.61914 v 5.61938 l 11.02724,-3.07161" | |
| 975 | inkscape:connector-curvature="0" | |
| 976 | style="fill:#ffffff;stroke-width:0.11881336" /> | |
| 977 | <path | |
| 978 | sodipodi:nodetypes="cc" | |
| 979 | inkscape:connector-curvature="0" | |
| 980 | id="path5804" | |
| 981 | d="m 240.99252,105.07517 65.2338,-1.01308" | |
| 982 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9479)" /> | |
| 983 | <path | |
| 984 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9505)" | |
| 985 | d="m 481.61302,105.07517 65.2337,-1.01308" | |
| 986 | id="path9497" | |
| 987 | inkscape:connector-curvature="0" | |
| 988 | sodipodi:nodetypes="cc" /> | |
| 989 | <path | |
| 990 | inkscape:connector-curvature="0" | |
| 991 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 992 | d="m 235.85308,230.53494 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z" | |
| 993 | id="path6102" /> | |
| 994 | <path | |
| 995 | inkscape:connector-curvature="0" | |
| 996 | style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 997 | d="m 76.280823,230.53494 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z" | |
| 998 | id="path6106" /> | |
| 999 | <path | |
| 1000 | inkscape:connector-curvature="0" | |
| 1001 | style="fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2950)" | |
| 1002 | d="m 76.515198,222.53494 c -0.870125,0 -1.656831,0.35019 -2.22461,0.91797 -0.567778,0.56778 -0.917968,1.35253 -0.917968,2.22265 v 29.71485 h 165.62109 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z" | |
| 1003 | id="path6104" | |
| 1004 | sodipodi:nodetypes="sssccssss" /> | |
| 1005 | <path | |
| 1006 | sodipodi:nodetypes="ccssssc" | |
| 1007 | inkscape:connector-curvature="0" | |
| 1008 | style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2950)" | |
| 1009 | d="M 238.99371,255.39041 H 73.37262 v 152.63392 c 0,1.6112 1.297008,2.90821 2.908203,2.90821 H 236.08551 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z" | |
| 1010 | id="path6100" /> | |
| 1011 | <path | |
| 1012 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9509)" | |
| 1013 | d="m 240.99257,328.95043 65.23376,-1.01307" | |
| 1014 | id="path9485" | |
| 1015 | inkscape:connector-curvature="0" | |
| 1016 | sodipodi:nodetypes="cc" /> | |
| 1017 | <path | |
| 1018 | sodipodi:nodetypes="cc" | |
| 1019 | inkscape:connector-curvature="0" | |
| 1020 | id="path9757" | |
| 1021 | d="m 481.61298,300.08996 65.23376,-1.01307" | |
| 1022 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9761)" /> | |
| 1023 | <path | |
| 1024 | style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9767)" | |
| 1025 | d="M 552.61456,372.04139 487.3808,371.02832" | |
| 1026 | id="path9763" | |
| 1027 | inkscape:connector-curvature="0" | |
| 1028 | sodipodi:nodetypes="cc" /> | |
| 1029 | <text | |
| 1030 | id="text2269" | |
| 1031 | y="62.149761" | |
| 1032 | x="115.43707" | |
| 1033 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1034 | xml:space="preserve"><tspan | |
| 1035 | y="62.149761" | |
| 1036 | x="115.43707" | |
| 1037 | id="tspan2267" | |
| 1038 | sodipodi:role="line">Text Edit</tspan></text> | |
| 1039 | <text | |
| 1040 | transform="rotate(-90)" | |
| 1041 | id="text2273" | |
| 1042 | y="43.507812" | |
| 1043 | x="-132.24059" | |
| 1044 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1045 | xml:space="preserve"><tspan | |
| 1046 | y="43.507812" | |
| 1047 | x="-132.24059" | |
| 1048 | id="tspan2271" | |
| 1049 | sodipodi:role="line">Today</tspan></text> | |
| 1050 | <text | |
| 1051 | id="text2277" | |
| 1052 | y="61.540386" | |
| 1053 | x="358.88168" | |
| 1054 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1055 | xml:space="preserve"><tspan | |
| 1056 | y="61.540386" | |
| 1057 | x="358.88168" | |
| 1058 | id="tspan2275" | |
| 1059 | sodipodi:role="line">Process</tspan></text> | |
| 1060 | <text | |
| 1061 | id="text2281" | |
| 1062 | y="59.34898" | |
| 1063 | x="605.30872" | |
| 1064 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1065 | xml:space="preserve"><tspan | |
| 1066 | y="59.34898" | |
| 1067 | x="605.30872" | |
| 1068 | id="tspan2279" | |
| 1069 | sodipodi:role="line">Output</tspan></text> | |
| 1070 | <text | |
| 1071 | id="text2285" | |
| 1072 | y="245.17946" | |
| 1073 | x="605.30872" | |
| 1074 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1075 | xml:space="preserve"><tspan | |
| 1076 | y="245.17946" | |
| 1077 | x="605.30872" | |
| 1078 | id="tspan2283" | |
| 1079 | sodipodi:role="line">Output</tspan></text> | |
| 1080 | <text | |
| 1081 | id="text2289" | |
| 1082 | y="247.37088" | |
| 1083 | x="358.88168" | |
| 1084 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1085 | xml:space="preserve"><tspan | |
| 1086 | y="247.37088" | |
| 1087 | x="358.88168" | |
| 1088 | id="tspan2287" | |
| 1089 | sodipodi:role="line">Process</tspan></text> | |
| 1090 | <text | |
| 1091 | id="text2293" | |
| 1092 | y="247.98026" | |
| 1093 | x="115.43707" | |
| 1094 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1095 | xml:space="preserve"><tspan | |
| 1096 | y="247.98026" | |
| 1097 | x="115.43707" | |
| 1098 | id="tspan2291" | |
| 1099 | sodipodi:role="line">Text Edit</tspan></text> | |
| 1100 | <text | |
| 1101 | transform="rotate(-90)" | |
| 1102 | id="text2297" | |
| 1103 | y="43.630859" | |
| 1104 | x="-363.15442" | |
| 1105 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1106 | xml:space="preserve"><tspan | |
| 1107 | y="43.630859" | |
| 1108 | x="-363.15442" | |
| 1109 | id="tspan2295" | |
| 1110 | sodipodi:role="line">Proposed</tspan></text> | |
| 1111 | <text | |
| 1112 | id="text2301" | |
| 1113 | y="314.01108" | |
| 1114 | x="98.034729" | |
| 1115 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1116 | xml:space="preserve"><tspan | |
| 1117 | id="tspan2299" | |
| 1118 | sodipodi:role="line" | |
| 1119 | x="98.034729" | |
| 1120 | y="314.01108">R Markdown</tspan></text> | |
| 1121 | <text | |
| 1122 | id="text2305" | |
| 1123 | y="285.84311" | |
| 1124 | x="107.43903" | |
| 1125 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1126 | xml:space="preserve"><tspan | |
| 1127 | id="tspan2303" | |
| 1128 | sodipodi:role="line" | |
| 1129 | x="107.43903" | |
| 1130 | y="285.84311">Markdown</tspan></text> | |
| 1131 | <text | |
| 1132 | id="text2309" | |
| 1133 | y="342.91147" | |
| 1134 | x="134.3277" | |
| 1135 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1136 | xml:space="preserve"><tspan | |
| 1137 | id="tspan2307" | |
| 1138 | sodipodi:role="line" | |
| 1139 | x="134.3277" | |
| 1140 | y="342.91147">XML</tspan></text> | |
| 1141 | <text | |
| 1142 | id="text2313" | |
| 1143 | y="370.34702" | |
| 1144 | x="113.56207" | |
| 1145 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1146 | xml:space="preserve"><tspan | |
| 1147 | id="tspan2311" | |
| 1148 | sodipodi:role="line" | |
| 1149 | x="113.56207" | |
| 1150 | y="370.34702">DocBook</tspan></text> | |
| 1151 | <text | |
| 1152 | id="text2317" | |
| 1153 | y="398.51498" | |
| 1154 | x="114.3526" | |
| 1155 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1156 | xml:space="preserve"><tspan | |
| 1157 | id="tspan2315" | |
| 1158 | sodipodi:role="line" | |
| 1159 | x="114.3526" | |
| 1160 | y="398.51498">AsciiDoc</tspan></text> | |
| 1161 | <text | |
| 1162 | transform="rotate(-90)" | |
| 1163 | id="text2329" | |
| 1164 | y="43.507812" | |
| 1165 | x="-774.87335" | |
| 1166 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1167 | xml:space="preserve"><tspan | |
| 1168 | y="43.507812" | |
| 1169 | x="-774.87335" | |
| 1170 | id="tspan2327" | |
| 1171 | sodipodi:role="line">Example Processing Combination</tspan></text> | |
| 1172 | <text | |
| 1173 | id="text2333" | |
| 1174 | y="562.05426" | |
| 1175 | x="135.31207" | |
| 1176 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#f3fbfe;fill-opacity:1;stroke:none" | |
| 1177 | xml:space="preserve"><tspan | |
| 1178 | y="562.05426" | |
| 1179 | x="135.31207" | |
| 1180 | id="tspan2331" | |
| 1181 | sodipodi:role="line">XML</tspan></text> | |
| 1182 | <text | |
| 1183 | id="text2337" | |
| 1184 | y="495.6918" | |
| 1185 | x="381.64142" | |
| 1186 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#333333;fill-opacity:0.866667;stroke:none" | |
| 1187 | xml:space="preserve"><tspan | |
| 1188 | y="495.6918" | |
| 1189 | x="381.64142" | |
| 1190 | id="tspan2335" | |
| 1191 | sodipodi:role="line">XSLT</tspan></text> | |
| 1192 | <text | |
| 1193 | id="text2341" | |
| 1194 | y="562.05426" | |
| 1195 | x="323.97742" | |
| 1196 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1197 | xml:space="preserve"><tspan | |
| 1198 | y="562.05426" | |
| 1199 | x="323.97742" | |
| 1200 | id="tspan2339" | |
| 1201 | sodipodi:role="line">XSLT Processor</tspan></text> | |
| 1202 | <text | |
| 1203 | id="text2345" | |
| 1204 | y="562.54059" | |
| 1205 | x="579.27557" | |
| 1206 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1207 | xml:space="preserve"><tspan | |
| 1208 | y="562.54059" | |
| 1209 | x="579.27557" | |
| 1210 | id="tspan2343" | |
| 1211 | sodipodi:role="line">R Markdown</tspan></text> | |
| 1212 | <text | |
| 1213 | id="text2349" | |
| 1214 | y="643.24084" | |
| 1215 | x="588.75018" | |
| 1216 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1217 | xml:space="preserve"><tspan | |
| 1218 | y="643.24084" | |
| 1219 | x="588.75018" | |
| 1220 | id="tspan2347" | |
| 1221 | sodipodi:role="line">Markdown</tspan></text> | |
| 1222 | <text | |
| 1223 | id="text2353" | |
| 1224 | y="642.63147" | |
| 1225 | x="339.61023" | |
| 1226 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1227 | xml:space="preserve"><tspan | |
| 1228 | y="642.63147" | |
| 1229 | x="339.61023" | |
| 1230 | id="tspan2351" | |
| 1231 | sodipodi:role="line">R Processor</tspan></text> | |
| 1232 | <text | |
| 1233 | id="text2357" | |
| 1234 | y="722.93903" | |
| 1235 | x="318.43912" | |
| 1236 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:21.3333px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1237 | xml:space="preserve"><tspan | |
| 1238 | y="722.93903" | |
| 1239 | x="318.43912" | |
| 1240 | id="tspan2355" | |
| 1241 | sodipodi:role="line">Variable Processor</tspan></text> | |
| 1242 | <text | |
| 1243 | id="text2361" | |
| 1244 | y="723.3316" | |
| 1245 | x="604.07831" | |
| 1246 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1247 | xml:space="preserve"><tspan | |
| 1248 | y="723.3316" | |
| 1249 | x="604.07831" | |
| 1250 | id="tspan2359" | |
| 1251 | sodipodi:role="line">HTML5</tspan></text> | |
| 1252 | <text | |
| 1253 | id="text2365" | |
| 1254 | y="630.84766" | |
| 1255 | x="81.211723" | |
| 1256 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#333333;fill-opacity:0.866667;stroke:none" | |
| 1257 | xml:space="preserve"><tspan | |
| 1258 | y="630.84766" | |
| 1259 | x="81.211723" | |
| 1260 | id="tspan2363" | |
| 1261 | sodipodi:role="line">Structured Data Source</tspan></text> | |
| 1262 | <text | |
| 1263 | id="text2369" | |
| 1264 | y="756.39404" | |
| 1265 | x="215.65826" | |
| 1266 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1267 | xml:space="preserve"><tspan | |
| 1268 | y="756.39404" | |
| 1269 | x="215.65826" | |
| 1270 | id="tspan2367" | |
| 1271 | sodipodi:role="line">interpolated values</tspan></text> | |
| 1272 | <g | |
| 1273 | transform="translate(-0.25585322,11.831789)" | |
| 1274 | id="g2523"> | |
| 1275 | <text | |
| 1276 | xml:space="preserve" | |
| 1277 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1278 | x="156.49219" | |
| 1279 | y="708.2467" | |
| 1280 | id="text2373"><tspan | |
| 1281 | sodipodi:role="line" | |
| 1282 | id="tspan2371" | |
| 1283 | x="156.49219" | |
| 1284 | y="708.2467">CSON</tspan></text> | |
| 1285 | <text | |
| 1286 | xml:space="preserve" | |
| 1287 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1288 | x="156.49219" | |
| 1289 | y="688.41504" | |
| 1290 | id="text2377"><tspan | |
| 1291 | sodipodi:role="line" | |
| 1292 | id="tspan2375" | |
| 1293 | x="156.49219" | |
| 1294 | y="688.41504">JSONNET</tspan></text> | |
| 1295 | <text | |
| 1296 | xml:space="preserve" | |
| 1297 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1298 | x="156.49219" | |
| 1299 | y="668.24695" | |
| 1300 | id="text2381"><tspan | |
| 1301 | sodipodi:role="line" | |
| 1302 | id="tspan2379" | |
| 1303 | x="156.49219" | |
| 1304 | y="668.24695">JSON5</tspan></text> | |
| 1305 | <text | |
| 1306 | xml:space="preserve" | |
| 1307 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1308 | x="156.49219" | |
| 1309 | y="648.07886" | |
| 1310 | id="text2385"><tspan | |
| 1311 | sodipodi:role="line" | |
| 1312 | id="tspan2383" | |
| 1313 | x="156.49219" | |
| 1314 | y="648.07886">JSON</tspan></text> | |
| 1315 | <text | |
| 1316 | xml:space="preserve" | |
| 1317 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1318 | x="94.110725" | |
| 1319 | y="648.41534" | |
| 1320 | id="text2389"><tspan | |
| 1321 | sodipodi:role="line" | |
| 1322 | id="tspan2387" | |
| 1323 | x="94.110725" | |
| 1324 | y="648.41534">YAML</tspan></text> | |
| 1325 | <text | |
| 1326 | xml:space="preserve" | |
| 1327 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1328 | x="94.110725" | |
| 1329 | y="668.24695" | |
| 1330 | id="text2393"><tspan | |
| 1331 | sodipodi:role="line" | |
| 1332 | id="tspan2391" | |
| 1333 | x="94.110725" | |
| 1334 | y="668.24695">TOML</tspan></text> | |
| 1335 | <text | |
| 1336 | xml:space="preserve" | |
| 1337 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none" | |
| 1338 | x="94.110725" | |
| 1339 | y="688.41504" | |
| 1340 | id="text2397"><tspan | |
| 1341 | sodipodi:role="line" | |
| 1342 | id="tspan2395" | |
| 1343 | x="94.110725" | |
| 1344 | y="688.41504">XML</tspan></text> | |
| 1345 | </g> | |
| 1346 | <g | |
| 1347 | transform="translate(-1.2304677,-0.85937628)" | |
| 1348 | id="g2593"> | |
| 1349 | <g | |
| 1350 | id="g2532"> | |
| 1351 | <rect | |
| 1352 | id="rect4698" | |
| 1353 | ry="2.7292624" | |
| 1354 | y="91.740654" | |
| 1355 | x="129.16347" | |
| 1356 | height="32.205296" | |
| 1357 | width="54.039394" | |
| 1358 | style="fill:none;stroke:#05556e;stroke-width:2.72926;stroke-opacity:1" /> | |
| 1359 | <path | |
| 1360 | style="fill:#05556e;fill-opacity:1;stroke-width:0.272926" | |
| 1361 | id="path4700" | |
| 1362 | d="M 135.98663,117.12279 V 98.56381 h 5.45852 l 5.45853,6.82315 5.45852,-6.82315 h 5.45853 v 18.55898 h -5.45853 v -10.64412 l -5.45852,6.82315 -5.45853,-6.82315 v 10.64412 z m 34.11578,0 -8.18779,-9.00657 h 5.45852 v -9.55241 h 5.45853 v 9.55241 h 5.45852 z" | |
| 1363 | inkscape:connector-curvature="0" /> | |
| 1364 | </g> | |
| 1365 | <text | |
| 1366 | xml:space="preserve" | |
| 1367 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1368 | x="108.73981" | |
| 1369 | y="152.80437" | |
| 1370 | id="text2407"><tspan | |
| 1371 | sodipodi:role="line" | |
| 1372 | id="tspan2405" | |
| 1373 | x="108.73981" | |
| 1374 | y="152.80437">Markdown</tspan></text> | |
| 1375 | </g> | |
| 1376 | <path | |
| 1377 | inkscape:connector-curvature="0" | |
| 1378 | d="m 417.86562,272.90923 c -2.81873,0.35302 -5.58858,1.78683 -7.90222,4.10047 -1.79226,1.78682 -3.43787,4.20365 -5.01832,7.35911 -1.28173,2.56347 -2.29191,5.21927 -2.90019,7.59265 l -0.1738,0.68975 -0.68975,0.35302 c -0.96673,0.49423 -1.81398,1.01561 -2.77528,1.69993 -3.29666,2.35709 -6.15341,5.19211 -8.53222,8.46705 -0.23354,0.32586 -0.45621,0.58656 -0.49966,0.58656 -0.038,0 -0.33673,-0.0435 -0.65716,-0.0923 -0.73863,-0.11949 -3.19891,-0.13578 -4.11676,-0.0272 -3.79633,0.46164 -7.25593,1.57502 -11.41613,3.68228 -3.00339,1.5207 -4.93685,2.87304 -6.8323,4.77391 -2.37881,2.37882 -3.80176,5.01832 -4.21452,7.82076 -0.0978,0.62457 -0.0978,2.39511 0,3.0414 0.51052,3.55193 2.55804,6.94636 5.27358,8.74404 3.15003,2.08554 7.40256,2.6558 12.27424,1.65105 3.62253,-0.75492 7.20161,-2.14527 10.77526,-4.19822 3.47046,-1.99321 5.87643,-4.18193 7.57093,-6.87575 0.27155,-0.43449 0.35845,-0.52682 0.53224,-0.59199 2.79701,-1.01018 4.74677,-2.05295 6.96265,-3.72572 2.02036,-1.5207 3.43244,-2.85675 6.0991,-5.77324 0.68432,-0.74949 0.8038,-0.91785 0.84182,-1.16225 0.0326,-0.17379 0.0543,-0.20095 0.15207,-0.17922 0.51595,0.10319 2.20502,0.11948 2.94908,0.0272 2.08553,-0.25526 4.05701,-1.10251 6.01763,-2.57976 2.61778,-1.97691 5.06177,-5.27901 6.78885,-9.17853 2.59606,-5.86556 3.57908,-10.80785 3.01425,-15.19073 -0.14121,-1.12423 -0.28241,-1.74881 -0.59742,-2.71554 -0.42905,-1.29803 -1.08621,-2.55804 -1.89001,-3.62796 -0.43449,-0.57026 -1.57502,-1.70536 -2.14528,-2.12898 -1.59131,-1.17855 -3.93753,-2.13442 -6.03936,-2.46028 -0.66259,-0.10319 -2.29735,-0.14664 -2.85132,-0.0815 z m 2.44399,7.82076 c 1.94433,0.46707 3.2152,2.04751 3.5302,4.39917 0.0815,0.58656 0.0815,2.10183 0,2.7427 -0.32043,2.62864 -1.26544,5.70263 -2.61235,8.48878 -1.01561,2.10725 -1.79226,3.34011 -2.88933,4.58383 -0.32587,0.36931 -1.38493,1.31975 -1.42838,1.2763 -0.005,-0.005 0.0706,-0.34216 0.1738,-0.74406 0.24983,-0.97759 0.34215,-1.56958 0.3856,-2.41683 0.0706,-1.58044 -0.27155,-3.09571 -0.98302,-4.30684 -1.20027,-2.05295 -3.17175,-3.41072 -5.47453,-3.78547 -0.11405,-0.0163 -0.20638,-0.0489 -0.20638,-0.076 0,-0.0217 0.19552,-0.53768 0.42905,-1.15139 1.41752,-3.67684 2.66666,-5.83298 4.30142,-7.40799 1.0482,-1.01562 1.70536,-1.40665 2.73726,-1.62933 0.51596,-0.11405 1.49355,-0.0978 2.03666,0.0272 z m -10.34078,17.93885 c 0.52139,0.54311 0.56483,0.76579 0.46164,2.25933 l -0.0326,0.51596 -0.14121,-0.21725 c -0.22811,-0.34215 -0.40733,-0.72233 -0.52682,-1.1188 -0.0652,-0.20095 -0.15207,-0.43992 -0.20095,-0.53224 -0.0706,-0.13035 -0.17922,-0.91243 -0.19008,-1.34691 0,-0.11949 0.29871,0.0923 0.63,0.43991 z m -7.36997,3.01425 c 0.3856,2.28649 1.18397,4.05159 2.44941,5.40393 l 0.45078,0.47793 -0.13577,0.14664 c -0.0706,0.0815 -0.46165,0.51052 -0.86355,0.9613 -1.55328,1.73795 -2.81873,2.98167 -4.05158,3.97012 -0.41819,0.34216 -0.78208,0.61915 -0.79837,0.61915 -0.0163,0 -0.0435,-0.0923 -0.0652,-0.20638 -0.076,-0.4019 -0.46708,-1.4664 -0.8038,-2.15614 -0.54311,-1.12424 -1.14596,-2.0095 -2.08554,-3.0577 l -0.45621,-0.50509 0.41276,-0.50509 c 1.19484,-1.47182 2.92192,-3.26951 4.43177,-4.62728 0.85811,-0.76578 1.37949,-1.21656 1.39578,-1.20027 0.005,0.005 0.0597,0.315 0.11949,0.67888 z m -16.52135,9.77052 c -0.0163,0.11405 -0.0815,0.54311 -0.14664,0.9613 -0.22267,1.47182 -0.23353,3.57365 -0.0272,4.78478 0.19008,1.10251 0.57569,2.11812 1.08078,2.81873 0.27699,0.38018 0.87441,0.97759 1.22199,1.20027 l 0.23354,0.1575 -0.15207,0.12492 c -0.60285,0.48879 -2.54174,1.58044 -4.18193,2.34622 -2.4114,1.12967 -4.36659,1.7651 -6.62049,2.16157 -0.77664,0.13578 -0.99932,0.15207 -2.09096,0.15207 -0.98846,0 -1.30889,-0.0217 -1.67278,-0.0978 -1.5207,-0.33672 -2.53088,-0.97216 -3.1989,-2.0095 -0.53225,-0.82552 -0.72234,-1.48268 -0.72777,-2.43855 0,-1.56415 0.57027,-2.68296 2.17244,-4.27969 1.78682,-1.77597 3.93753,-3.05227 7.72299,-4.5784 2.01493,-0.81467 4.20366,-1.37407 5.75151,-1.4664 0.74406,-0.0434 0.66803,-0.0652 0.63544,0.16294 z m 6.13712,3.5302 c -0.0163,0.0543 -0.0272,0.0109 -0.0272,-0.0923 0,-0.10319 0.0109,-0.14664 0.0272,-0.0978 0.0109,0.0543 0.0109,0.14121 0,0.19009 z" | |
| 1379 | id="path8164" | |
| 1380 | style="fill:#df4d65;fill-opacity:1;stroke:none;stroke-width:0.00543108" /> | |
| 1381 | <g | |
| 1382 | transform="translate(1.378418e-5,1.0193503)" | |
| 1383 | id="g1168"> | |
| 1384 | <text | |
| 1385 | id="text1158" | |
| 1386 | y="364.17905" | |
| 1387 | x="349.05551" | |
| 1388 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1389 | xml:space="preserve"><tspan | |
| 1390 | id="tspan1156" | |
| 1391 | sodipodi:role="line" | |
| 1392 | x="349.05551" | |
| 1393 | y="364.17905">Processor</tspan></text> | |
| 1394 | <text | |
| 1395 | xml:space="preserve" | |
| 1396 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1397 | x="370.40707" | |
| 1398 | y="392.17905" | |
| 1399 | id="text1162"><tspan | |
| 1400 | y="392.17905" | |
| 1401 | x="370.40707" | |
| 1402 | sodipodi:role="line" | |
| 1403 | id="tspan1160">Chain</tspan></text> | |
| 1404 | </g> | |
| 1405 | <g | |
| 1406 | transform="translate(0,-2.3144459)" | |
| 1407 | id="g1206"> | |
| 1408 | <text | |
| 1409 | xml:space="preserve" | |
| 1410 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1411 | x="586.44855" | |
| 1412 | y="327.56967" | |
| 1413 | id="text1190"><tspan | |
| 1414 | y="327.56967" | |
| 1415 | x="586.44855" | |
| 1416 | sodipodi:role="line" | |
| 1417 | id="tspan1188">Processor-</tspan></text> | |
| 1418 | <text | |
| 1419 | id="text1194" | |
| 1420 | y="355.56967" | |
| 1421 | x="588.43488" | |
| 1422 | style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none" | |
| 1423 | xml:space="preserve"><tspan | |
| 1424 | id="tspan1192" | |
| 1425 | sodipodi:role="line" | |
| 1426 | x="588.43488" | |
| 1427 | y="355.56967">dependent</tspan></text> | |
| 1428 | </g> | |
| 1429 | </svg> | |
| 1 | 1430 |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> | |
| 2 | <title>HTML5 Logo</title> | |
| 3 | <path d="M108.4 0h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206 23h-20.3V0h63.7v23H229v46h-23M259.5 0h24.1l14.8 24.3L313.2 0h24.1v69h-23V34.8l-16.1 24.8l-16.1-24.8v34.2h-22.6M348.7 0h23v46.2h32.6V69h-55.6"/> | |
| 4 | <path fill="#e44d26" d="M107.6 471l-33-370.4h362.8l-33 370.2L255.7 512"/> | |
| 5 | <path fill="#f16529" d="M256 480.5V131H404.3L376 447"/> | |
| 6 | <path fill="#ebebeb" d="M142 176.3h114v45.4h-64.2l4.2 46.5h60v45.3H154.4M156.4 336.3H202l3.2 36.3 50.8 13.6v47.4l-93.2-26"/> | |
| 7 | <path fill="#fff" d="M369.6 176.3H255.8v45.4h109.6M361.3 268.2H255.8v45.4h56l-5.3 59-50.7 13.6v47.2l93-25.8"/> | |
| 8 | </svg> |
| 1 | <?xml version="1.0" standalone="no"?> | |
| 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | |
| 3 | "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | |
| 4 | <svg version="1.0" xmlns="http://www.w3.org/2000/svg" | |
| 5 | width="1280.000000pt" height="1123.000000pt" viewBox="0 0 1280.000000 1123.000000" | |
| 6 | preserveAspectRatio="xMidYMid meet"> | |
| 7 | <metadata> | |
| 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 | |
| 9 | </metadata> | |
| 10 | <g transform="translate(0.000000,1123.000000) scale(0.100000,-0.100000)" | |
| 11 | fill="#000000" stroke="none"> | |
| 12 | <path d="M10280 11220 c-519 -65 -1029 -329 -1455 -755 -330 -329 -633 -774 | |
| 13 | -924 -1355 -236 -472 -422 -961 -534 -1398 l-32 -127 -127 -65 c-178 -91 -334 | |
| 14 | -187 -511 -313 -607 -434 -1133 -956 -1571 -1559 -43 -60 -84 -108 -92 -108 | |
| 15 | -7 0 -62 8 -121 17 -136 22 -589 25 -758 5 -699 -85 -1336 -290 -2102 -678 | |
| 16 | -553 -280 -909 -529 -1258 -879 -438 -438 -700 -924 -776 -1440 -18 -115 -18 | |
| 17 | -441 0 -560 94 -654 471 -1279 971 -1610 580 -384 1363 -489 2260 -304 667 | |
| 18 | 139 1326 395 1984 773 639 367 1082 770 1394 1266 50 80 66 97 98 109 515 186 | |
| 19 | 874 378 1282 686 372 280 632 526 1123 1063 126 138 148 169 155 214 6 32 10 | |
| 20 | 37 28 33 95 -19 406 -22 543 -5 384 47 747 203 1108 475 482 364 932 972 1250 | |
| 21 | 1690 478 1080 659 1990 555 2797 -26 207 -52 322 -110 500 -79 239 -200 471 | |
| 22 | -348 668 -80 105 -290 314 -395 392 -293 217 -725 393 -1112 453 -122 19 -423 | |
| 23 | 27 -525 15z m450 -1440 c358 -86 592 -377 650 -810 15 -108 15 -387 0 -505 | |
| 24 | -59 -484 -233 -1050 -481 -1563 -187 -388 -330 -615 -532 -844 -60 -68 -255 | |
| 25 | -243 -263 -235 -1 1 13 63 32 137 46 180 63 289 71 445 13 291 -50 570 -181 | |
| 26 | 793 -221 378 -584 628 -1008 697 -21 3 -38 9 -38 14 0 4 36 99 79 212 261 677 | |
| 27 | 491 1074 792 1364 193 187 314 259 504 300 95 21 275 18 375 -5z m-1904 -3303 | |
| 28 | c96 -100 104 -141 85 -416 l-6 -95 -26 40 c-42 63 -75 133 -97 206 -12 37 -28 | |
| 29 | 81 -37 98 -13 24 -33 168 -35 248 0 22 55 -17 116 -81z m-1357 -555 c71 -421 | |
| 30 | 218 -746 451 -995 l83 -88 -25 -27 c-13 -15 -85 -94 -159 -177 -286 -320 -519 | |
| 31 | -549 -746 -731 -77 -63 -144 -114 -147 -114 -3 0 -8 17 -12 38 -14 74 -86 270 | |
| 32 | -148 397 -100 207 -211 370 -384 563 l-84 93 76 93 c220 271 538 602 816 852 | |
| 33 | 158 141 254 224 257 221 1 -1 11 -58 22 -125z m-3042 -1799 c-3 -21 -15 -100 | |
| 34 | -27 -177 -41 -271 -43 -658 -5 -881 35 -203 106 -390 199 -519 51 -70 161 | |
| 35 | -180 225 -221 l43 -29 -28 -23 c-111 -90 -468 -291 -770 -432 -444 -208 -804 | |
| 36 | -325 -1219 -398 -143 -25 -184 -28 -385 -28 -182 0 -241 4 -308 18 -280 62 | |
| 37 | -466 179 -589 370 -98 152 -133 273 -134 449 0 288 105 494 400 788 329 327 | |
| 38 | 725 562 1422 843 371 150 774 253 1059 270 137 8 123 12 117 -30z m1130 -650 | |
| 39 | c-3 -10 -5 -2 -5 17 0 19 2 27 5 18 2 -10 2 -26 0 -35z"/> | |
| 40 | </g> | |
| 41 | </svg> | |
| 1 | 42 |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="208" height="128" viewBox="0 0 208 128"><rect width="198" height="118" x="5" y="5" ry="10" stroke="#000" stroke-width="10" fill="none"/><path d="M30 98V30h20l20 25 20-25h20v68H90V59L70 84 50 59v39zm125 0l-30-33h20V30h20v35h20z"/></svg> |
| 1 | 1 | |
| 2 | Apache License | |
| 3 | Version 2.0, January 2004 | |
| 4 | http://www.apache.org/licenses/ | |
| 5 | ||
| 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
| 7 | ||
| 8 | 1. Definitions. | |
| 9 | ||
| 10 | "License" shall mean the terms and conditions for use, reproduction, | |
| 11 | and distribution as defined by Sections 1 through 9 of this document. | |
| 12 | ||
| 13 | "Licensor" shall mean the copyright owner or entity authorized by | |
| 14 | the copyright owner that is granting the License. | |
| 15 | ||
| 16 | "Legal Entity" shall mean the union of the acting entity and all | |
| 17 | other entities that control, are controlled by, or are under common | |
| 18 | control with that entity. For the purposes of this definition, | |
| 19 | "control" means (i) the power, direct or indirect, to cause the | |
| 20 | direction or management of such entity, whether by contract or | |
| 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
| 22 | outstanding shares, or (iii) beneficial ownership of such entity. | |
| 23 | ||
| 24 | "You" (or "Your") shall mean an individual or Legal Entity | |
| 25 | exercising permissions granted by this License. | |
| 26 | ||
| 27 | "Source" form shall mean the preferred form for making modifications, | |
| 28 | including but not limited to software source code, documentation | |
| 29 | source, and configuration files. | |
| 30 | ||
| 31 | "Object" form shall mean any form resulting from mechanical | |
| 32 | transformation or translation of a Source form, including but | |
| 33 | not limited to compiled object code, generated documentation, | |
| 34 | and conversions to other media types. | |
| 35 | ||
| 36 | "Work" shall mean the work of authorship, whether in Source or | |
| 37 | Object form, made available under the License, as indicated by a | |
| 38 | copyright notice that is included in or attached to the work | |
| 39 | (an example is provided in the Appendix below). | |
| 40 | ||
| 41 | "Derivative Works" shall mean any work, whether in Source or Object | |
| 42 | form, that is based on (or derived from) the Work and for which the | |
| 43 | editorial revisions, annotations, elaborations, or other modifications | |
| 44 | represent, as a whole, an original work of authorship. For the purposes | |
| 45 | of this License, Derivative Works shall not include works that remain | |
| 46 | separable from, or merely link (or bind by name) to the interfaces of, | |
| 47 | the Work and Derivative Works thereof. | |
| 48 | ||
| 49 | "Contribution" shall mean any work of authorship, including | |
| 50 | the original version of the Work and any modifications or additions | |
| 51 | to that Work or Derivative Works thereof, that is intentionally | |
| 52 | submitted to Licensor for inclusion in the Work by the copyright owner | |
| 53 | or by an individual or Legal Entity authorized to submit on behalf of | |
| 54 | the copyright owner. For the purposes of this definition, "submitted" | |
| 55 | means any form of electronic, verbal, or written communication sent | |
| 56 | to the Licensor or its representatives, including but not limited to | |
| 57 | communication on electronic mailing lists, source code control systems, | |
| 58 | and issue tracking systems that are managed by, or on behalf of, the | |
| 59 | Licensor for the purpose of discussing and improving the Work, but | |
| 60 | excluding communication that is conspicuously marked or otherwise | |
| 61 | designated in writing by the copyright owner as "Not a Contribution." | |
| 62 | ||
| 63 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
| 64 | on behalf of whom a Contribution has been received by Licensor and | |
| 65 | subsequently incorporated within the Work. | |
| 66 | ||
| 67 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
| 68 | this License, each Contributor hereby grants to You a perpetual, | |
| 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 70 | copyright license to reproduce, prepare Derivative Works of, | |
| 71 | publicly display, publicly perform, sublicense, and distribute the | |
| 72 | Work and such Derivative Works in Source or Object form. | |
| 73 | ||
| 74 | 3. Grant of Patent License. Subject to the terms and conditions of | |
| 75 | this License, each Contributor hereby grants to You a perpetual, | |
| 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 77 | (except as stated in this section) patent license to make, have made, | |
| 78 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
| 79 | where such license applies only to those patent claims licensable | |
| 80 | by such Contributor that are necessarily infringed by their | |
| 81 | Contribution(s) alone or by combination of their Contribution(s) | |
| 82 | with the Work to which such Contribution(s) was submitted. If You | |
| 83 | institute patent litigation against any entity (including a | |
| 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
| 85 | or a Contribution incorporated within the Work constitutes direct | |
| 86 | or contributory patent infringement, then any patent licenses | |
| 87 | granted to You under this License for that Work shall terminate | |
| 88 | as of the date such litigation is filed. | |
| 89 | ||
| 90 | 4. Redistribution. You may reproduce and distribute copies of the | |
| 91 | Work or Derivative Works thereof in any medium, with or without | |
| 92 | modifications, and in Source or Object form, provided that You | |
| 93 | meet the following conditions: | |
| 94 | ||
| 95 | (a) You must give any other recipients of the Work or | |
| 96 | Derivative Works a copy of this License; and | |
| 97 | ||
| 98 | (b) You must cause any modified files to carry prominent notices | |
| 99 | stating that You changed the files; and | |
| 100 | ||
| 101 | (c) You must retain, in the Source form of any Derivative Works | |
| 102 | that You distribute, all copyright, patent, trademark, and | |
| 103 | attribution notices from the Source form of the Work, | |
| 104 | excluding those notices that do not pertain to any part of | |
| 105 | the Derivative Works; and | |
| 106 | ||
| 107 | (d) If the Work includes a "NOTICE" text file as part of its | |
| 108 | distribution, then any Derivative Works that You distribute must | |
| 109 | include a readable copy of the attribution notices contained | |
| 110 | within such NOTICE file, excluding those notices that do not | |
| 111 | pertain to any part of the Derivative Works, in at least one | |
| 112 | of the following places: within a NOTICE text file distributed | |
| 113 | as part of the Derivative Works; within the Source form or | |
| 114 | documentation, if provided along with the Derivative Works; or, | |
| 115 | within a display generated by the Derivative Works, if and | |
| 116 | wherever such third-party notices normally appear. The contents | |
| 117 | of the NOTICE file are for informational purposes only and | |
| 118 | do not modify the License. You may add Your own attribution | |
| 119 | notices within Derivative Works that You distribute, alongside | |
| 120 | or as an addendum to the NOTICE text from the Work, provided | |
| 121 | that such additional attribution notices cannot be construed | |
| 122 | as modifying the License. | |
| 123 | ||
| 124 | You may add Your own copyright statement to Your modifications and | |
| 125 | may provide additional or different license terms and conditions | |
| 126 | for use, reproduction, or distribution of Your modifications, or | |
| 127 | for any such Derivative Works as a whole, provided Your use, | |
| 128 | reproduction, and distribution of the Work otherwise complies with | |
| 129 | the conditions stated in this License. | |
| 130 | ||
| 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
| 132 | any Contribution intentionally submitted for inclusion in the Work | |
| 133 | by You to the Licensor shall be under the terms and conditions of | |
| 134 | this License, without any additional terms or conditions. | |
| 135 | Notwithstanding the above, nothing herein shall supersede or modify | |
| 136 | the terms of any separate license agreement you may have executed | |
| 137 | with Licensor regarding such Contributions. | |
| 138 | ||
| 139 | 6. Trademarks. This License does not grant permission to use the trade | |
| 140 | names, trademarks, service marks, or product names of the Licensor, | |
| 141 | except as required for reasonable and customary use in describing the | |
| 142 | origin of the Work and reproducing the content of the NOTICE file. | |
| 143 | ||
| 144 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
| 145 | agreed to in writing, Licensor provides the Work (and each | |
| 146 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
| 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| 148 | implied, including, without limitation, any warranties or conditions | |
| 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
| 150 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
| 151 | appropriateness of using or redistributing the Work and assume any | |
| 152 | risks associated with Your exercise of permissions under this License. | |
| 153 | ||
| 154 | 8. Limitation of Liability. In no event and under no legal theory, | |
| 155 | whether in tort (including negligence), contract, or otherwise, | |
| 156 | unless required by applicable law (such as deliberate and grossly | |
| 157 | negligent acts) or agreed to in writing, shall any Contributor be | |
| 158 | liable to You for damages, including any direct, indirect, special, | |
| 159 | incidental, or consequential damages of any character arising as a | |
| 160 | result of this License or out of the use or inability to use the | |
| 161 | Work (including but not limited to damages for loss of goodwill, | |
| 162 | work stoppage, computer failure or malfunction, or any and all | |
| 163 | other commercial damages or losses), even if such Contributor | |
| 164 | has been advised of the possibility of such damages. | |
| 165 | ||
| 166 | 9. Accepting Warranty or Additional Liability. While redistributing | |
| 167 | the Work or Derivative Works thereof, You may choose to offer, | |
| 168 | and charge a fee for, acceptance of support, warranty, indemnity, | |
| 169 | or other liability obligations and/or rights consistent with this | |
| 170 | License. However, in accepting such obligations, You may act only | |
| 171 | on Your own behalf and on Your sole responsibility, not on behalf | |
| 172 | of any other Contributor, and only if You agree to indemnify, | |
| 173 | defend, and hold each Contributor harmless for any liability | |
| 174 | incurred by, or claims asserted against, such Contributor by reason | |
| 175 | of your accepting any such warranty or additional liability. | |
| 176 | ||
| 177 | END OF TERMS AND CONDITIONS | |
| 178 | ||
| 179 | APPENDIX: How to apply the Apache License to your work. | |
| 180 | ||
| 181 | To apply the Apache License to your work, attach the following | |
| 182 | boilerplate notice, with the fields enclosed by brackets "[]" | |
| 183 | replaced with your own identifying information. (Don't include | |
| 184 | the brackets!) The text should be enclosed in the appropriate | |
| 185 | comment syntax for the file format. We also recommend that a | |
| 186 | file or class name and description of purpose be included on the | |
| 187 | same "printed page" as the copyright notice for easier | |
| 188 | identification within third-party archives. | |
| 189 | ||
| 190 | Copyright [yyyy] [name of copyright owner] | |
| 191 | ||
| 192 | Licensed under the Apache License, Version 2.0 (the "License"); | |
| 193 | you may not use this file except in compliance with the License. | |
| 194 | You may obtain a copy of the License at | |
| 195 | ||
| 196 | http://www.apache.org/licenses/LICENSE-2.0 | |
| 197 | ||
| 198 | Unless required by applicable law or agreed to in writing, software | |
| 199 | distributed under the License is distributed on an "AS IS" BASIS, | |
| 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 201 | See the License for the specific language governing permissions and | |
| 202 | limitations under the License. | |
| 203 |
| 1 | The MIT License (MIT) | |
| 2 | ||
| 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | |
| 4 | ||
| 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | |
| 6 | ||
| 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 8 | ||
| 1 | 9 |
| 1 | Released into the Public Domain by David Croft. | |
| 2 | ||
| 3 | http://www.davidc.net/programming/java/java-preferences-using-file-backing-store | |
| 4 | http://creativecommons.org/publicdomain/zero/1.0/ | |
| 5 | ||
| 6 | CC0 1.0 Universal (CC0 1.0) | |
| 7 | ||
| 8 | Public Domain Dedication | |
| 9 | ||
| 10 | This is a human-readable summary of the Legal Code (read the full text). | |
| 11 | ||
| 12 | Disclaimer | |
| 13 | ||
| 14 | No Copyright | |
| 15 | ||
| 16 | * The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law. | |
| 17 | ||
| 18 | * You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. See Other Information below. | |
| 19 | ||
| 20 | This license is acceptable for Free Cultural Works. | |
| 21 | ||
| 22 | Other Information | |
| 23 | ||
| 24 | * In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights. | |
| 25 | * Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. | |
| 26 | * When using or citing the work, you should not imply endorsement by the author or the affirmer. | |
| 27 | ||
| 1 | 28 |
| 1 | Copyright (c) 2015-2016, Atlassian Pty Ltd | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Copyright (c) 2016-2018, Vladimir Schneider, | |
| 5 | All rights reserved. | |
| 6 | ||
| 7 | Redistribution and use in source and binary forms, with or without | |
| 8 | modification, are permitted provided that the following conditions are met: | |
| 9 | ||
| 10 | * Redistributions of source code must retain the above copyright notice, this | |
| 11 | list of conditions and the following disclaimer. | |
| 12 | ||
| 13 | * Redistributions in binary form must reproduce the above copyright notice, | |
| 14 | this list of conditions and the following disclaimer in the documentation | |
| 15 | and/or other materials provided with the distribution. | |
| 16 | ||
| 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 27 |
| 1 | Copyright (c) 2014, TomasMikula | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright notice, this | |
| 8 | list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright notice, | |
| 11 | this list of conditions and the following disclaimer in the documentation | |
| 12 | and/or other materials provided with the distribution. | |
| 13 | ||
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 24 |
| 1 | 1 | |
| 2 | Apache License | |
| 3 | Version 2.0, January 2004 | |
| 4 | http://www.apache.org/licenses/ | |
| 5 | ||
| 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
| 7 | ||
| 8 | 1. Definitions. | |
| 9 | ||
| 10 | "License" shall mean the terms and conditions for use, reproduction, | |
| 11 | and distribution as defined by Sections 1 through 9 of this document. | |
| 12 | ||
| 13 | "Licensor" shall mean the copyright owner or entity authorized by | |
| 14 | the copyright owner that is granting the License. | |
| 15 | ||
| 16 | "Legal Entity" shall mean the union of the acting entity and all | |
| 17 | other entities that control, are controlled by, or are under common | |
| 18 | control with that entity. For the purposes of this definition, | |
| 19 | "control" means (i) the power, direct or indirect, to cause the | |
| 20 | direction or management of such entity, whether by contract or | |
| 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
| 22 | outstanding shares, or (iii) beneficial ownership of such entity. | |
| 23 | ||
| 24 | "You" (or "Your") shall mean an individual or Legal Entity | |
| 25 | exercising permissions granted by this License. | |
| 26 | ||
| 27 | "Source" form shall mean the preferred form for making modifications, | |
| 28 | including but not limited to software source code, documentation | |
| 29 | source, and configuration files. | |
| 30 | ||
| 31 | "Object" form shall mean any form resulting from mechanical | |
| 32 | transformation or translation of a Source form, including but | |
| 33 | not limited to compiled object code, generated documentation, | |
| 34 | and conversions to other media types. | |
| 35 | ||
| 36 | "Work" shall mean the work of authorship, whether in Source or | |
| 37 | Object form, made available under the License, as indicated by a | |
| 38 | copyright notice that is included in or attached to the work | |
| 39 | (an example is provided in the Appendix below). | |
| 40 | ||
| 41 | "Derivative Works" shall mean any work, whether in Source or Object | |
| 42 | form, that is based on (or derived from) the Work and for which the | |
| 43 | editorial revisions, annotations, elaborations, or other modifications | |
| 44 | represent, as a whole, an original work of authorship. For the purposes | |
| 45 | of this License, Derivative Works shall not include works that remain | |
| 46 | separable from, or merely link (or bind by name) to the interfaces of, | |
| 47 | the Work and Derivative Works thereof. | |
| 48 | ||
| 49 | "Contribution" shall mean any work of authorship, including | |
| 50 | the original version of the Work and any modifications or additions | |
| 51 | to that Work or Derivative Works thereof, that is intentionally | |
| 52 | submitted to Licensor for inclusion in the Work by the copyright owner | |
| 53 | or by an individual or Legal Entity authorized to submit on behalf of | |
| 54 | the copyright owner. For the purposes of this definition, "submitted" | |
| 55 | means any form of electronic, verbal, or written communication sent | |
| 56 | to the Licensor or its representatives, including but not limited to | |
| 57 | communication on electronic mailing lists, source code control systems, | |
| 58 | and issue tracking systems that are managed by, or on behalf of, the | |
| 59 | Licensor for the purpose of discussing and improving the Work, but | |
| 60 | excluding communication that is conspicuously marked or otherwise | |
| 61 | designated in writing by the copyright owner as "Not a Contribution." | |
| 62 | ||
| 63 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
| 64 | on behalf of whom a Contribution has been received by Licensor and | |
| 65 | subsequently incorporated within the Work. | |
| 66 | ||
| 67 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
| 68 | this License, each Contributor hereby grants to You a perpetual, | |
| 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 70 | copyright license to reproduce, prepare Derivative Works of, | |
| 71 | publicly display, publicly perform, sublicense, and distribute the | |
| 72 | Work and such Derivative Works in Source or Object form. | |
| 73 | ||
| 74 | 3. Grant of Patent License. Subject to the terms and conditions of | |
| 75 | this License, each Contributor hereby grants to You a perpetual, | |
| 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 77 | (except as stated in this section) patent license to make, have made, | |
| 78 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
| 79 | where such license applies only to those patent claims licensable | |
| 80 | by such Contributor that are necessarily infringed by their | |
| 81 | Contribution(s) alone or by combination of their Contribution(s) | |
| 82 | with the Work to which such Contribution(s) was submitted. If You | |
| 83 | institute patent litigation against any entity (including a | |
| 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
| 85 | or a Contribution incorporated within the Work constitutes direct | |
| 86 | or contributory patent infringement, then any patent licenses | |
| 87 | granted to You under this License for that Work shall terminate | |
| 88 | as of the date such litigation is filed. | |
| 89 | ||
| 90 | 4. Redistribution. You may reproduce and distribute copies of the | |
| 91 | Work or Derivative Works thereof in any medium, with or without | |
| 92 | modifications, and in Source or Object form, provided that You | |
| 93 | meet the following conditions: | |
| 94 | ||
| 95 | (a) You must give any other recipients of the Work or | |
| 96 | Derivative Works a copy of this License; and | |
| 97 | ||
| 98 | (b) You must cause any modified files to carry prominent notices | |
| 99 | stating that You changed the files; and | |
| 100 | ||
| 101 | (c) You must retain, in the Source form of any Derivative Works | |
| 102 | that You distribute, all copyright, patent, trademark, and | |
| 103 | attribution notices from the Source form of the Work, | |
| 104 | excluding those notices that do not pertain to any part of | |
| 105 | the Derivative Works; and | |
| 106 | ||
| 107 | (d) If the Work includes a "NOTICE" text file as part of its | |
| 108 | distribution, then any Derivative Works that You distribute must | |
| 109 | include a readable copy of the attribution notices contained | |
| 110 | within such NOTICE file, excluding those notices that do not | |
| 111 | pertain to any part of the Derivative Works, in at least one | |
| 112 | of the following places: within a NOTICE text file distributed | |
| 113 | as part of the Derivative Works; within the Source form or | |
| 114 | documentation, if provided along with the Derivative Works; or, | |
| 115 | within a display generated by the Derivative Works, if and | |
| 116 | wherever such third-party notices normally appear. The contents | |
| 117 | of the NOTICE file are for informational purposes only and | |
| 118 | do not modify the License. You may add Your own attribution | |
| 119 | notices within Derivative Works that You distribute, alongside | |
| 120 | or as an addendum to the NOTICE text from the Work, provided | |
| 121 | that such additional attribution notices cannot be construed | |
| 122 | as modifying the License. | |
| 123 | ||
| 124 | You may add Your own copyright statement to Your modifications and | |
| 125 | may provide additional or different license terms and conditions | |
| 126 | for use, reproduction, or distribution of Your modifications, or | |
| 127 | for any such Derivative Works as a whole, provided Your use, | |
| 128 | reproduction, and distribution of the Work otherwise complies with | |
| 129 | the conditions stated in this License. | |
| 130 | ||
| 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
| 132 | any Contribution intentionally submitted for inclusion in the Work | |
| 133 | by You to the Licensor shall be under the terms and conditions of | |
| 134 | this License, without any additional terms or conditions. | |
| 135 | Notwithstanding the above, nothing herein shall supersede or modify | |
| 136 | the terms of any separate license agreement you may have executed | |
| 137 | with Licensor regarding such Contributions. | |
| 138 | ||
| 139 | 6. Trademarks. This License does not grant permission to use the trade | |
| 140 | names, trademarks, service marks, or product names of the Licensor, | |
| 141 | except as required for reasonable and customary use in describing the | |
| 142 | origin of the Work and reproducing the content of the NOTICE file. | |
| 143 | ||
| 144 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
| 145 | agreed to in writing, Licensor provides the Work (and each | |
| 146 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
| 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| 148 | implied, including, without limitation, any warranties or conditions | |
| 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
| 150 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
| 151 | appropriateness of using or redistributing the Work and assume any | |
| 152 | risks associated with Your exercise of permissions under this License. | |
| 153 | ||
| 154 | 8. Limitation of Liability. In no event and under no legal theory, | |
| 155 | whether in tort (including negligence), contract, or otherwise, | |
| 156 | unless required by applicable law (such as deliberate and grossly | |
| 157 | negligent acts) or agreed to in writing, shall any Contributor be | |
| 158 | liable to You for damages, including any direct, indirect, special, | |
| 159 | incidental, or consequential damages of any character arising as a | |
| 160 | result of this License or out of the use or inability to use the | |
| 161 | Work (including but not limited to damages for loss of goodwill, | |
| 162 | work stoppage, computer failure or malfunction, or any and all | |
| 163 | other commercial damages or losses), even if such Contributor | |
| 164 | has been advised of the possibility of such damages. | |
| 165 | ||
| 166 | 9. Accepting Warranty or Additional Liability. While redistributing | |
| 167 | the Work or Derivative Works thereof, You may choose to offer, | |
| 168 | and charge a fee for, acceptance of support, warranty, indemnity, | |
| 169 | or other liability obligations and/or rights consistent with this | |
| 170 | License. However, in accepting such obligations, You may act only | |
| 171 | on Your own behalf and on Your sole responsibility, not on behalf | |
| 172 | of any other Contributor, and only if You agree to indemnify, | |
| 173 | defend, and hold each Contributor harmless for any liability | |
| 174 | incurred by, or claims asserted against, such Contributor by reason | |
| 175 | of your accepting any such warranty or additional liability. | |
| 176 | ||
| 177 | END OF TERMS AND CONDITIONS | |
| 178 | ||
| 179 | APPENDIX: How to apply the Apache License to your work. | |
| 180 | ||
| 181 | To apply the Apache License to your work, attach the following | |
| 182 | boilerplate notice, with the fields enclosed by brackets "[]" | |
| 183 | replaced with your own identifying information. (Don't include | |
| 184 | the brackets!) The text should be enclosed in the appropriate | |
| 185 | comment syntax for the file format. We also recommend that a | |
| 186 | file or class name and description of purpose be included on the | |
| 187 | same "printed page" as the copyright notice for easier | |
| 188 | identification within third-party archives. | |
| 189 | ||
| 190 | Copyright [yyyy] [name of copyright owner] | |
| 191 | ||
| 192 | Licensed under the Apache License, Version 2.0 (the "License"); | |
| 193 | you may not use this file except in compliance with the License. | |
| 194 | You may obtain a copy of the License at | |
| 195 | ||
| 196 | http://www.apache.org/licenses/LICENSE-2.0 | |
| 197 | ||
| 198 | Unless required by applicable law or agreed to in writing, software | |
| 199 | distributed under the License is distributed on an "AS IS" BASIS, | |
| 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 201 | See the License for the specific language governing permissions and | |
| 202 | limitations under the License. | |
| 203 |
| 1 | Java Image Scaling | |
| 2 | ||
| 3 | Copyright (c) 2013, Morten Nobel-Joergensen | |
| 4 | All rights reserved. | |
| 5 | ||
| 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
| 7 | ||
| 8 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |
| 9 | ||
| 10 | Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 11 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 12 | ||
| 1 | 13 |
| 1 | MIT License | |
| 2 | ||
| 3 | Copyright (c) 2019 Raul Garcia | |
| 4 | ||
| 5 | Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | of this software and associated documentation files (the "Software"), to deal | |
| 7 | in the Software without restriction, including without limitation the rights | |
| 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | copies of the Software, and to permit persons to whom the Software is | |
| 10 | furnished to do so, subject to the following conditions: | |
| 11 | ||
| 12 | The above copyright notice and this permission notice shall be included in all | |
| 13 | copies or substantial portions of the Software. | |
| 14 | ||
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | SOFTWARE. | |
| 1 | 22 |
| 1 | Version: MPL 1.1/GPL 2.0/LGPL 2.1 | |
| 2 | ||
| 3 | The contents of this file are subject to the Mozilla Public License Version | |
| 4 | 1.1 (the "License"); you may not use this file except in compliance with | |
| 5 | the License. You may obtain a copy of the License at | |
| 6 | http://www.mozilla.org/MPL/ | |
| 7 | ||
| 8 | Software distributed under the License is distributed on an "AS IS" basis, | |
| 9 | WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License | |
| 10 | for the specific language governing rights and limitations under the | |
| 11 | License. | |
| 12 | ||
| 13 | The Original Code is Mozilla Universal charset detector code. | |
| 14 | ||
| 15 | The Initial Developer of the Original Code is | |
| 16 | Netscape Communications Corporation. | |
| 17 | Portions created by the Initial Developer are Copyright (C) 2001 | |
| 18 | the Initial Developer. All Rights Reserved. | |
| 19 | ||
| 20 | Contributor(s): | |
| 21 | Shy Shalom <shooshX@gmail.com> | |
| 22 | Kohei TAKETA <k-tak@void.in> (Java port) | |
| 23 | ||
| 24 | Alternatively, the contents of this file may be used under the terms of | |
| 25 | either the GNU General Public License Version 2 or later (the "GPL"), or | |
| 26 | the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), | |
| 27 | in which case the provisions of the GPL or the LGPL are applicable instead | |
| 28 | of those above. If you wish to allow use of your version of this file only | |
| 29 | under the terms of either the GPL or the LGPL, and not to allow others to | |
| 30 | use your version of this file under the terms of the MPL, indicate your | |
| 31 | decision by deleting the provisions above and replace them with the notice | |
| 32 | and other provisions required by the GPL or the LGPL. If you do not delete | |
| 33 | the provisions above, a recipient may use your version of this file under | |
| 34 | the terms of any one of the MPL, the GPL or the LGPL. | |
| 35 | ||
| 1 | 36 |
| 1 | Copyright © 2020 Mark Raynsford <code@io7m.com> http://io7m.com | |
| 2 | ||
| 3 | Permission to use, copy, modify, and/or distribute this software for any | |
| 4 | purpose with or without fee is hereby granted, provided that the above | |
| 5 | copyright notice and this permission notice appear in all copies. | |
| 6 | ||
| 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
| 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
| 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
| 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
| 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
| 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
| 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
| 1 | 14 |
| 1 | Copyright (c) 2015 Karl Tauber <karl@jformdesigner.com> | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright | |
| 8 | notice, this list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright | |
| 11 | notice, this list of conditions and the following disclaimer in the | |
| 12 | documentation and/or other materials provided with the distribution. | |
| 13 | ||
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 18 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 25 |
| 1 | Copyright (c) 2000 Mikael Grev | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions | |
| 6 | are met: | |
| 7 | 1. Redistributions of source code must retain the above copyright | |
| 8 | notice, this list of conditions and the following disclaimer. | |
| 9 | 2. Redistributions in binary form must reproduce the above copyright | |
| 10 | notice, this list of conditions and the following disclaimer in the | |
| 11 | documentation and/or other materials provided with the distribution. | |
| 12 | 3. The name of the author may not be used to endorse or promote products | |
| 13 | derived from this software without specific prior written permission. | |
| 14 | ||
| 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
| 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
| 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
| 18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
| 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
| 20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
| 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 | ||
| 1 | 26 |
| 1 | Apache License | |
| 2 | Version 2.0, January 2004 | |
| 3 | http://www.apache.org/licenses/ | |
| 4 | ||
| 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
| 6 | ||
| 7 | 1. Definitions. | |
| 8 | ||
| 9 | "License" shall mean the terms and conditions for use, reproduction, | |
| 10 | and distribution as defined by Sections 1 through 9 of this document. | |
| 11 | ||
| 12 | "Licensor" shall mean the copyright owner or entity authorized by | |
| 13 | the copyright owner that is granting the License. | |
| 14 | ||
| 15 | "Legal Entity" shall mean the union of the acting entity and all | |
| 16 | other entities that control, are controlled by, or are under common | |
| 17 | control with that entity. For the purposes of this definition, | |
| 18 | "control" means (i) the power, direct or indirect, to cause the | |
| 19 | direction or management of such entity, whether by contract or | |
| 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
| 21 | outstanding shares, or (iii) beneficial ownership of such entity. | |
| 22 | ||
| 23 | "You" (or "Your") shall mean an individual or Legal Entity | |
| 24 | exercising permissions granted by this License. | |
| 25 | ||
| 26 | "Source" form shall mean the preferred form for making modifications, | |
| 27 | including but not limited to software source code, documentation | |
| 28 | source, and configuration files. | |
| 29 | ||
| 30 | "Object" form shall mean any form resulting from mechanical | |
| 31 | transformation or translation of a Source form, including but | |
| 32 | not limited to compiled object code, generated documentation, | |
| 33 | and conversions to other media types. | |
| 34 | ||
| 35 | "Work" shall mean the work of authorship, whether in Source or | |
| 36 | Object form, made available under the License, as indicated by a | |
| 37 | copyright notice that is included in or attached to the work | |
| 38 | (an example is provided in the Appendix below). | |
| 39 | ||
| 40 | "Derivative Works" shall mean any work, whether in Source or Object | |
| 41 | form, that is based on (or derived from) the Work and for which the | |
| 42 | editorial revisions, annotations, elaborations, or other modifications | |
| 43 | represent, as a whole, an original work of authorship. For the purposes | |
| 44 | of this License, Derivative Works shall not include works that remain | |
| 45 | separable from, or merely link (or bind by name) to the interfaces of, | |
| 46 | the Work and Derivative Works thereof. | |
| 47 | ||
| 48 | "Contribution" shall mean any work of authorship, including | |
| 49 | the original version of the Work and any modifications or additions | |
| 50 | to that Work or Derivative Works thereof, that is intentionally | |
| 51 | submitted to Licensor for inclusion in the Work by the copyright owner | |
| 52 | or by an individual or Legal Entity authorized to submit on behalf of | |
| 53 | the copyright owner. For the purposes of this definition, "submitted" | |
| 54 | means any form of electronic, verbal, or written communication sent | |
| 55 | to the Licensor or its representatives, including but not limited to | |
| 56 | communication on electronic mailing lists, source code control systems, | |
| 57 | and issue tracking systems that are managed by, or on behalf of, the | |
| 58 | Licensor for the purpose of discussing and improving the Work, but | |
| 59 | excluding communication that is conspicuously marked or otherwise | |
| 60 | designated in writing by the copyright owner as "Not a Contribution." | |
| 61 | ||
| 62 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
| 63 | on behalf of whom a Contribution has been received by Licensor and | |
| 64 | subsequently incorporated within the Work. | |
| 65 | ||
| 66 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
| 67 | this License, each Contributor hereby grants to You a perpetual, | |
| 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 69 | copyright license to reproduce, prepare Derivative Works of, | |
| 70 | publicly display, publicly perform, sublicense, and distribute the | |
| 71 | Work and such Derivative Works in Source or Object form. | |
| 72 | ||
| 73 | 3. Grant of Patent License. Subject to the terms and conditions of | |
| 74 | this License, each Contributor hereby grants to You a perpetual, | |
| 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 76 | (except as stated in this section) patent license to make, have made, | |
| 77 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
| 78 | where such license applies only to those patent claims licensable | |
| 79 | by such Contributor that are necessarily infringed by their | |
| 80 | Contribution(s) alone or by combination of their Contribution(s) | |
| 81 | with the Work to which such Contribution(s) was submitted. If You | |
| 82 | institute patent litigation against any entity (including a | |
| 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
| 84 | or a Contribution incorporated within the Work constitutes direct | |
| 85 | or contributory patent infringement, then any patent licenses | |
| 86 | granted to You under this License for that Work shall terminate | |
| 87 | as of the date such litigation is filed. | |
| 88 | ||
| 89 | 4. Redistribution. You may reproduce and distribute copies of the | |
| 90 | Work or Derivative Works thereof in any medium, with or without | |
| 91 | modifications, and in Source or Object form, provided that You | |
| 92 | meet the following conditions: | |
| 93 | ||
| 94 | (a) You must give any other recipients of the Work or | |
| 95 | Derivative Works a copy of this License; and | |
| 96 | ||
| 97 | (b) You must cause any modified files to carry prominent notices | |
| 98 | stating that You changed the files; and | |
| 99 | ||
| 100 | (c) You must retain, in the Source form of any Derivative Works | |
| 101 | that You distribute, all copyright, patent, trademark, and | |
| 102 | attribution notices from the Source form of the Work, | |
| 103 | excluding those notices that do not pertain to any part of | |
| 104 | the Derivative Works; and | |
| 105 | ||
| 106 | (d) If the Work includes a "NOTICE" text file as part of its | |
| 107 | distribution, then any Derivative Works that You distribute must | |
| 108 | include a readable copy of the attribution notices contained | |
| 109 | within such NOTICE file, excluding those notices that do not | |
| 110 | pertain to any part of the Derivative Works, in at least one | |
| 111 | of the following places: within a NOTICE text file distributed | |
| 112 | as part of the Derivative Works; within the Source form or | |
| 113 | documentation, if provided along with the Derivative Works; or, | |
| 114 | within a display generated by the Derivative Works, if and | |
| 115 | wherever such third-party notices normally appear. The contents | |
| 116 | of the NOTICE file are for informational purposes only and | |
| 117 | do not modify the License. You may add Your own attribution | |
| 118 | notices within Derivative Works that You distribute, alongside | |
| 119 | or as an addendum to the NOTICE text from the Work, provided | |
| 120 | that such additional attribution notices cannot be construed | |
| 121 | as modifying the License. | |
| 122 | ||
| 123 | You may add Your own copyright statement to Your modifications and | |
| 124 | may provide additional or different license terms and conditions | |
| 125 | for use, reproduction, or distribution of Your modifications, or | |
| 126 | for any such Derivative Works as a whole, provided Your use, | |
| 127 | reproduction, and distribution of the Work otherwise complies with | |
| 128 | the conditions stated in this License. | |
| 129 | ||
| 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
| 131 | any Contribution intentionally submitted for inclusion in the Work | |
| 132 | by You to the Licensor shall be under the terms and conditions of | |
| 133 | this License, without any additional terms or conditions. | |
| 134 | Notwithstanding the above, nothing herein shall supersede or modify | |
| 135 | the terms of any separate license agreement you may have executed | |
| 136 | with Licensor regarding such Contributions. | |
| 137 | ||
| 138 | 6. Trademarks. This License does not grant permission to use the trade | |
| 139 | names, trademarks, service marks, or product names of the Licensor, | |
| 140 | except as required for reasonable and customary use in describing the | |
| 141 | origin of the Work and reproducing the content of the NOTICE file. | |
| 142 | ||
| 143 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
| 144 | agreed to in writing, Licensor provides the Work (and each | |
| 145 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
| 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| 147 | implied, including, without limitation, any warranties or conditions | |
| 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
| 149 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
| 150 | appropriateness of using or redistributing the Work and assume any | |
| 151 | risks associated with Your exercise of permissions under this License. | |
| 152 | ||
| 153 | 8. Limitation of Liability. In no event and under no legal theory, | |
| 154 | whether in tort (including negligence), contract, or otherwise, | |
| 155 | unless required by applicable law (such as deliberate and grossly | |
| 156 | negligent acts) or agreed to in writing, shall any Contributor be | |
| 157 | liable to You for damages, including any direct, indirect, special, | |
| 158 | incidental, or consequential damages of any character arising as a | |
| 159 | result of this License or out of the use or inability to use the | |
| 160 | Work (including but not limited to damages for loss of goodwill, | |
| 161 | work stoppage, computer failure or malfunction, or any and all | |
| 162 | other commercial damages or losses), even if such Contributor | |
| 163 | has been advised of the possibility of such damages. | |
| 164 | ||
| 165 | 9. Accepting Warranty or Additional Liability. While redistributing | |
| 166 | the Work or Derivative Works thereof, You may choose to offer, | |
| 167 | and charge a fee for, acceptance of support, warranty, indemnity, | |
| 168 | or other liability obligations and/or rights consistent with this | |
| 169 | License. However, in accepting such obligations, You may act only | |
| 170 | on Your own behalf and on Your sole responsibility, not on behalf | |
| 171 | of any other Contributor, and only if You agree to indemnify, | |
| 172 | defend, and hold each Contributor harmless for any liability | |
| 173 | incurred by, or claims asserted against, such Contributor by reason | |
| 174 | of your accepting any such warranty or additional liability. | |
| 175 | ||
| 176 | END OF TERMS AND CONDITIONS | |
| 177 | ||
| 178 | APPENDIX: How to apply the Apache License to your work. | |
| 179 | ||
| 180 | To apply the Apache License to your work, attach the following | |
| 181 | boilerplate notice, with the fields enclosed by brackets "{}" | |
| 182 | replaced with your own identifying information. (Don't include | |
| 183 | the brackets!) The text should be enclosed in the appropriate | |
| 184 | comment syntax for the file format. We also recommend that a | |
| 185 | file or class name and description of purpose be included on the | |
| 186 | same "printed page" as the copyright notice for easier | |
| 187 | identification within third-party archives. | |
| 188 | ||
| 189 | Copyright {yyyy} {name of copyright owner} | |
| 190 | ||
| 191 | Licensed under the Apache License, Version 2.0 (the "License"); | |
| 192 | you may not use this file except in compliance with the License. | |
| 193 | You may obtain a copy of the License at | |
| 194 | ||
| 195 | http://www.apache.org/licenses/LICENSE-2.0 | |
| 196 | ||
| 197 | Unless required by applicable law or agreed to in writing, software | |
| 198 | distributed under the License is distributed on an "AS IS" BASIS, | |
| 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 200 | See the License for the specific language governing permissions and | |
| 201 | limitations under the License. | |
| 1 | 202 |
| 1 | Copyright (c) 2013-2014, Tomas Mikula | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
| 5 | ||
| 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |
| 7 | ||
| 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |
| 9 | ||
| 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 11 |
| 1 | GNU GENERAL PUBLIC LICENSE | |
| 2 | Version 2, June 1991 | |
| 3 | ||
| 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. | |
| 5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 6 | Everyone is permitted to copy and distribute verbatim copies | |
| 7 | of this license document, but changing it is not allowed. | |
| 8 | ||
| 9 | Preamble | |
| 10 | ||
| 11 | The licenses for most software are designed to take away your | |
| 12 | freedom to share and change it. By contrast, the GNU General Public | |
| 13 | License is intended to guarantee your freedom to share and change free | |
| 14 | software--to make sure the software is free for all its users. This | |
| 15 | General Public License applies to most of the Free Software | |
| 16 | Foundation's software and to any other program whose authors commit to | |
| 17 | using it. (Some other Free Software Foundation software is covered by | |
| 18 | the GNU Library General Public License instead.) You can apply it to | |
| 19 | your programs, too. | |
| 20 | ||
| 21 | When we speak of free software, we are referring to freedom, not | |
| 22 | price. Our General Public Licenses are designed to make sure that you | |
| 23 | have the freedom to distribute copies of free software (and charge for | |
| 24 | this service if you wish), that you receive source code or can get it | |
| 25 | if you want it, that you can change the software or use pieces of it | |
| 26 | in new free programs; and that you know you can do these things. | |
| 27 | ||
| 28 | To protect your rights, we need to make restrictions that forbid | |
| 29 | anyone to deny you these rights or to ask you to surrender the rights. | |
| 30 | These restrictions translate to certain responsibilities for you if you | |
| 31 | distribute copies of the software, or if you modify it. | |
| 32 | ||
| 33 | For example, if you distribute copies of such a program, whether | |
| 34 | gratis or for a fee, you must give the recipients all the rights that | |
| 35 | you have. You must make sure that they, too, receive or can get the | |
| 36 | source code. And you must show them these terms so they know their | |
| 37 | rights. | |
| 38 | ||
| 39 | We protect your rights with two steps: (1) copyright the software, and | |
| 40 | (2) offer you this license which gives you legal permission to copy, | |
| 41 | distribute and/or modify the software. | |
| 42 | ||
| 43 | Also, for each author's protection and ours, we want to make certain | |
| 44 | that everyone understands that there is no warranty for this free | |
| 45 | software. If the software is modified by someone else and passed on, we | |
| 46 | want its recipients to know that what they have is not the original, so | |
| 47 | that any problems introduced by others will not reflect on the original | |
| 48 | authors' reputations. | |
| 49 | ||
| 50 | Finally, any free program is threatened constantly by software | |
| 51 | patents. We wish to avoid the danger that redistributors of a free | |
| 52 | program will individually obtain patent licenses, in effect making the | |
| 53 | program proprietary. To prevent this, we have made it clear that any | |
| 54 | patent must be licensed for everyone's free use or not licensed at all. | |
| 55 | ||
| 56 | The precise terms and conditions for copying, distribution and | |
| 57 | modification follow. | |
| 58 | ||
| 59 | GNU GENERAL PUBLIC LICENSE | |
| 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
| 61 | ||
| 62 | 0. This License applies to any program or other work which contains | |
| 63 | a notice placed by the copyright holder saying it may be distributed | |
| 64 | under the terms of this General Public License. The "Program", below, | |
| 65 | refers to any such program or work, and a "work based on the Program" | |
| 66 | means either the Program or any derivative work under copyright law: | |
| 67 | that is to say, a work containing the Program or a portion of it, | |
| 68 | either verbatim or with modifications and/or translated into another | |
| 69 | language. (Hereinafter, translation is included without limitation in | |
| 70 | the term "modification".) Each licensee is addressed as "you". | |
| 71 | ||
| 72 | Activities other than copying, distribution and modification are not | |
| 73 | covered by this License; they are outside its scope. The act of | |
| 74 | running the Program is not restricted, and the output from the Program | |
| 75 | is covered only if its contents constitute a work based on the | |
| 76 | Program (independent of having been made by running the Program). | |
| 77 | Whether that is true depends on what the Program does. | |
| 78 | ||
| 79 | 1. You may copy and distribute verbatim copies of the Program's | |
| 80 | source code as you receive it, in any medium, provided that you | |
| 81 | conspicuously and appropriately publish on each copy an appropriate | |
| 82 | copyright notice and disclaimer of warranty; keep intact all the | |
| 83 | notices that refer to this License and to the absence of any warranty; | |
| 84 | and give any other recipients of the Program a copy of this License | |
| 85 | along with the Program. | |
| 86 | ||
| 87 | You may charge a fee for the physical act of transferring a copy, and | |
| 88 | you may at your option offer warranty protection in exchange for a fee. | |
| 89 | ||
| 90 | 2. You may modify your copy or copies of the Program or any portion | |
| 91 | of it, thus forming a work based on the Program, and copy and | |
| 92 | distribute such modifications or work under the terms of Section 1 | |
| 93 | above, provided that you also meet all of these conditions: | |
| 94 | ||
| 95 | a) You must cause the modified files to carry prominent notices | |
| 96 | stating that you changed the files and the date of any change. | |
| 97 | ||
| 98 | b) You must cause any work that you distribute or publish, that in | |
| 99 | whole or in part contains or is derived from the Program or any | |
| 100 | part thereof, to be licensed as a whole at no charge to all third | |
| 101 | parties under the terms of this License. | |
| 102 | ||
| 103 | c) If the modified program normally reads commands interactively | |
| 104 | when run, you must cause it, when started running for such | |
| 105 | interactive use in the most ordinary way, to print or display an | |
| 106 | announcement including an appropriate copyright notice and a | |
| 107 | notice that there is no warranty (or else, saying that you provide | |
| 108 | a warranty) and that users may redistribute the program under | |
| 109 | these conditions, and telling the user how to view a copy of this | |
| 110 | License. (Exception: if the Program itself is interactive but | |
| 111 | does not normally print such an announcement, your work based on | |
| 112 | the Program is not required to print an announcement.) | |
| 113 | ||
| 114 | These requirements apply to the modified work as a whole. If | |
| 115 | identifiable sections of that work are not derived from the Program, | |
| 116 | and can be reasonably considered independent and separate works in | |
| 117 | themselves, then this License, and its terms, do not apply to those | |
| 118 | sections when you distribute them as separate works. But when you | |
| 119 | distribute the same sections as part of a whole which is a work based | |
| 120 | on the Program, the distribution of the whole must be on the terms of | |
| 121 | this License, whose permissions for other licensees extend to the | |
| 122 | entire whole, and thus to each and every part regardless of who wrote it. | |
| 123 | ||
| 124 | Thus, it is not the intent of this section to claim rights or contest | |
| 125 | your rights to work written entirely by you; rather, the intent is to | |
| 126 | exercise the right to control the distribution of derivative or | |
| 127 | collective works based on the Program. | |
| 128 | ||
| 129 | In addition, mere aggregation of another work not based on the Program | |
| 130 | with the Program (or with a work based on the Program) on a volume of | |
| 131 | a storage or distribution medium does not bring the other work under | |
| 132 | the scope of this License. | |
| 133 | ||
| 134 | 3. You may copy and distribute the Program (or a work based on it, | |
| 135 | under Section 2) in object code or executable form under the terms of | |
| 136 | Sections 1 and 2 above provided that you also do one of the following: | |
| 137 | ||
| 138 | a) Accompany it with the complete corresponding machine-readable | |
| 139 | source code, which must be distributed under the terms of Sections | |
| 140 | 1 and 2 above on a medium customarily used for software interchange; or, | |
| 141 | ||
| 142 | b) Accompany it with a written offer, valid for at least three | |
| 143 | years, to give any third party, for a charge no more than your | |
| 144 | cost of physically performing source distribution, a complete | |
| 145 | machine-readable copy of the corresponding source code, to be | |
| 146 | distributed under the terms of Sections 1 and 2 above on a medium | |
| 147 | customarily used for software interchange; or, | |
| 148 | ||
| 149 | c) Accompany it with the information you received as to the offer | |
| 150 | to distribute corresponding source code. (This alternative is | |
| 151 | allowed only for noncommercial distribution and only if you | |
| 152 | received the program in object code or executable form with such | |
| 153 | an offer, in accord with Subsection b above.) | |
| 154 | ||
| 155 | The source code for a work means the preferred form of the work for | |
| 156 | making modifications to it. For an executable work, complete source | |
| 157 | code means all the source code for all modules it contains, plus any | |
| 158 | associated interface definition files, plus the scripts used to | |
| 159 | control compilation and installation of the executable. However, as a | |
| 160 | special exception, the source code distributed need not include | |
| 161 | anything that is normally distributed (in either source or binary | |
| 162 | form) with the major components (compiler, kernel, and so on) of the | |
| 163 | operating system on which the executable runs, unless that component | |
| 164 | itself accompanies the executable. | |
| 165 | ||
| 166 | If distribution of executable or object code is made by offering | |
| 167 | access to copy from a designated place, then offering equivalent | |
| 168 | access to copy the source code from the same place counts as | |
| 169 | distribution of the source code, even though third parties are not | |
| 170 | compelled to copy the source along with the object code. | |
| 171 | ||
| 172 | 4. You may not copy, modify, sublicense, or distribute the Program | |
| 173 | except as expressly provided under this License. Any attempt | |
| 174 | otherwise to copy, modify, sublicense or distribute the Program is | |
| 175 | void, and will automatically terminate your rights under this License. | |
| 176 | However, parties who have received copies, or rights, from you under | |
| 177 | this License will not have their licenses terminated so long as such | |
| 178 | parties remain in full compliance. | |
| 179 | ||
| 180 | 5. You are not required to accept this License, since you have not | |
| 181 | signed it. However, nothing else grants you permission to modify or | |
| 182 | distribute the Program or its derivative works. These actions are | |
| 183 | prohibited by law if you do not accept this License. Therefore, by | |
| 184 | modifying or distributing the Program (or any work based on the | |
| 185 | Program), you indicate your acceptance of this License to do so, and | |
| 186 | all its terms and conditions for copying, distributing or modifying | |
| 187 | the Program or works based on it. | |
| 188 | ||
| 189 | 6. Each time you redistribute the Program (or any work based on the | |
| 190 | Program), the recipient automatically receives a license from the | |
| 191 | original licensor to copy, distribute or modify the Program subject to | |
| 192 | these terms and conditions. You may not impose any further | |
| 193 | restrictions on the recipients' exercise of the rights granted herein. | |
| 194 | You are not responsible for enforcing compliance by third parties to | |
| 195 | this License. | |
| 196 | ||
| 197 | 7. If, as a consequence of a court judgment or allegation of patent | |
| 198 | infringement or for any other reason (not limited to patent issues), | |
| 199 | conditions are imposed on you (whether by court order, agreement or | |
| 200 | otherwise) that contradict the conditions of this License, they do not | |
| 201 | excuse you from the conditions of this License. If you cannot | |
| 202 | distribute so as to satisfy simultaneously your obligations under this | |
| 203 | License and any other pertinent obligations, then as a consequence you | |
| 204 | may not distribute the Program at all. For example, if a patent | |
| 205 | license would not permit royalty-free redistribution of the Program by | |
| 206 | all those who receive copies directly or indirectly through you, then | |
| 207 | the only way you could satisfy both it and this License would be to | |
| 208 | refrain entirely from distribution of the Program. | |
| 209 | ||
| 210 | If any portion of this section is held invalid or unenforceable under | |
| 211 | any particular circumstance, the balance of the section is intended to | |
| 212 | apply and the section as a whole is intended to apply in other | |
| 213 | circumstances. | |
| 214 | ||
| 215 | It is not the purpose of this section to induce you to infringe any | |
| 216 | patents or other property right claims or to contest validity of any | |
| 217 | such claims; this section has the sole purpose of protecting the | |
| 218 | integrity of the free software distribution system, which is | |
| 219 | implemented by public license practices. Many people have made | |
| 220 | generous contributions to the wide range of software distributed | |
| 221 | through that system in reliance on consistent application of that | |
| 222 | system; it is up to the author/donor to decide if he or she is willing | |
| 223 | to distribute software through any other system and a licensee cannot | |
| 224 | impose that choice. | |
| 225 | ||
| 226 | This section is intended to make thoroughly clear what is believed to | |
| 227 | be a consequence of the rest of this License. | |
| 228 | ||
| 229 | 8. If the distribution and/or use of the Program is restricted in | |
| 230 | certain countries either by patents or by copyrighted interfaces, the | |
| 231 | original copyright holder who places the Program under this License | |
| 232 | may add an explicit geographical distribution limitation excluding | |
| 233 | those countries, so that distribution is permitted only in or among | |
| 234 | countries not thus excluded. In such case, this License incorporates | |
| 235 | the limitation as if written in the body of this License. | |
| 236 | ||
| 237 | 9. The Free Software Foundation may publish revised and/or new versions | |
| 238 | of the General Public License from time to time. Such new versions will | |
| 239 | be similar in spirit to the present version, but may differ in detail to | |
| 240 | address new problems or concerns. | |
| 241 | ||
| 242 | Each version is given a distinguishing version number. If the Program | |
| 243 | specifies a version number of this License which applies to it and "any | |
| 244 | later version", you have the option of following the terms and conditions | |
| 245 | either of that version or of any later version published by the Free | |
| 246 | Software Foundation. If the Program does not specify a version number of | |
| 247 | this License, you may choose any version ever published by the Free Software | |
| 248 | Foundation. | |
| 249 | ||
| 250 | 10. If you wish to incorporate parts of the Program into other free | |
| 251 | programs whose distribution conditions are different, write to the author | |
| 252 | to ask for permission. For software which is copyrighted by the Free | |
| 253 | Software Foundation, write to the Free Software Foundation; we sometimes | |
| 254 | make exceptions for this. Our decision will be guided by the two goals | |
| 255 | of preserving the free status of all derivatives of our free software and | |
| 256 | of promoting the sharing and reuse of software generally. | |
| 257 | ||
| 258 | NO WARRANTY | |
| 259 | ||
| 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | |
| 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN | |
| 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | |
| 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | |
| 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
| 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS | |
| 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE | |
| 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | |
| 268 | REPAIR OR CORRECTION. | |
| 269 | ||
| 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |
| 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | |
| 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | |
| 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | |
| 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | |
| 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | |
| 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | |
| 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | |
| 278 | POSSIBILITY OF SUCH DAMAGES. | |
| 279 | ||
| 280 | END OF TERMS AND CONDITIONS | |
| 281 | ||
| 282 | How to Apply These Terms to Your New Programs | |
| 283 | ||
| 284 | If you develop a new program, and you want it to be of the greatest | |
| 285 | possible use to the public, the best way to achieve this is to make it | |
| 286 | free software which everyone can redistribute and change under these terms. | |
| 287 | ||
| 288 | To do so, attach the following notices to the program. It is safest | |
| 289 | to attach them to the start of each source file to most effectively | |
| 290 | convey the exclusion of warranty; and each file should have at least | |
| 291 | the "copyright" line and a pointer to where the full notice is found. | |
| 292 | ||
| 293 | <one line to give the program's name and a brief idea of what it does.> | |
| 294 | Copyright (C) <year> <name of author> | |
| 295 | ||
| 296 | This program is free software; you can redistribute it and/or modify | |
| 297 | it under the terms of the GNU General Public License as published by | |
| 298 | the Free Software Foundation; either version 2 of the License, or | |
| 299 | (at your option) any later version. | |
| 300 | ||
| 301 | This program is distributed in the hope that it will be useful, | |
| 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 304 | GNU General Public License for more details. | |
| 305 | ||
| 306 | You should have received a copy of the GNU General Public License | |
| 307 | along with this program; if not, write to the Free Software | |
| 308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
| 309 | ||
| 310 | ||
| 311 | Also add information on how to contact you by electronic and paper mail. | |
| 312 | ||
| 313 | If the program is interactive, make it output a short notice like this | |
| 314 | when it starts in an interactive mode: | |
| 315 | ||
| 316 | Gnomovision version 69, Copyright (C) year name of author | |
| 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |
| 318 | This is free software, and you are welcome to redistribute it | |
| 319 | under certain conditions; type `show c' for details. | |
| 320 | ||
| 321 | The hypothetical commands `show w' and `show c' should show the appropriate | |
| 322 | parts of the General Public License. Of course, the commands you use may | |
| 323 | be called something other than `show w' and `show c'; they could even be | |
| 324 | mouse-clicks or menu items--whatever suits your program. | |
| 325 | ||
| 326 | You should also get your employer (if you work as a programmer) or your | |
| 327 | school, if any, to sign a "copyright disclaimer" for the program, if | |
| 328 | necessary. Here is a sample; alter the names: | |
| 329 | ||
| 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program | |
| 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. | |
| 332 | ||
| 333 | <signature of Ty Coon>, 1 April 1989 | |
| 334 | Ty Coon, President of Vice | |
| 335 | ||
| 336 | This General Public License does not permit incorporating your program into | |
| 337 | proprietary programs. If your program is a subroutine library, you may | |
| 338 | consider it more useful to permit linking proprietary applications with the | |
| 339 | library. If this is what you want to do, use the GNU Library General | |
| 340 | Public License instead of this License. | |
| 1 | 341 |
| 1 | Copyright (c) 2013-2017, Tomas Mikula and contributors | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
| 5 | ||
| 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |
| 7 | ||
| 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |
| 9 | ||
| 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 1 | 11 |
| 1 | Copyright (c) 2014, TomasMikula | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without modification, | |
| 5 | are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright notice, this | |
| 8 | list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright notice, this | |
| 11 | list of conditions and the following disclaimer in the documentation and/or | |
| 12 | other materials provided with the distribution. | |
| 1 | 13 | |
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | |
| 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
| 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
| 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |
| 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 1 | Copyright (c) 2014, TomasMikula | |
| 2 | All rights reserved. | |
| 3 | ||
| 4 | Redistribution and use in source and binary forms, with or without | |
| 5 | modification, are permitted provided that the following conditions are met: | |
| 6 | ||
| 7 | * Redistributions of source code must retain the above copyright notice, this | |
| 8 | list of conditions and the following disclaimer. | |
| 9 | ||
| 10 | * Redistributions in binary form must reproduce the above copyright notice, | |
| 11 | this list of conditions and the following disclaimer in the documentation | |
| 12 | and/or other materials provided with the distribution. | |
| 13 | ||
| 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 24 | ||
| 1 | 25 |
| 1 | URL: https://github.com/googlefonts/noto-cjk | |
| 2 | ||
| 3 | Version: 1.002 or later | |
| 4 | ||
| 5 | License: SIL Open Font License v1.1 | |
| 6 | ||
| 7 | License File: LICENSE | |
| 8 | ||
| 9 | Note: prior releases of the CJK fonts were issued under the Apache 2 | |
| 10 | license. This was changed to the SIL OFL v1.1 starting with Version 1.002. | |
| 11 | ||
| 12 | Description: | |
| 13 | Noto CJK fonts, supporting Simplified Chinese, Traditional Chinese, | |
| 14 | Japanese, and Korean. The supported scripts are Han, Hiragana, Katakana, | |
| 15 | Hangul, and Bopomofo. Latin, Greek, Cyrillic, and various symbols are also | |
| 16 | supported for compatibility with CJK standards. | |
| 17 | ||
| 18 | The fonts in this directory are developed by Google and Adobe and are | |
| 19 | released as open source under the Apache license version 2.0. The copyright | |
| 20 | is held by Adobe, while the trademarks on the names are held by Google. | |
| 21 | ||
| 22 | A README-formats file has been added explaining the different formats | |
| 23 | provided and their features and limitations. | |
| 1 | 24 |
| 1 | 1 | |
| 2 | Apache License | |
| 3 | Version 2.0, January 2004 | |
| 4 | http://www.apache.org/licenses/ | |
| 5 | ||
| 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
| 7 | ||
| 8 | 1. Definitions. | |
| 9 | ||
| 10 | "License" shall mean the terms and conditions for use, reproduction, | |
| 11 | and distribution as defined by Sections 1 through 9 of this document. | |
| 12 | ||
| 13 | "Licensor" shall mean the copyright owner or entity authorized by | |
| 14 | the copyright owner that is granting the License. | |
| 15 | ||
| 16 | "Legal Entity" shall mean the union of the acting entity and all | |
| 17 | other entities that control, are controlled by, or are under common | |
| 18 | control with that entity. For the purposes of this definition, | |
| 19 | "control" means (i) the power, direct or indirect, to cause the | |
| 20 | direction or management of such entity, whether by contract or | |
| 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
| 22 | outstanding shares, or (iii) beneficial ownership of such entity. | |
| 23 | ||
| 24 | "You" (or "Your") shall mean an individual or Legal Entity | |
| 25 | exercising permissions granted by this License. | |
| 26 | ||
| 27 | "Source" form shall mean the preferred form for making modifications, | |
| 28 | including but not limited to software source code, documentation | |
| 29 | source, and configuration files. | |
| 30 | ||
| 31 | "Object" form shall mean any form resulting from mechanical | |
| 32 | transformation or translation of a Source form, including but | |
| 33 | not limited to compiled object code, generated documentation, | |
| 34 | and conversions to other media types. | |
| 35 | ||
| 36 | "Work" shall mean the work of authorship, whether in Source or | |
| 37 | Object form, made available under the License, as indicated by a | |
| 38 | copyright notice that is included in or attached to the work | |
| 39 | (an example is provided in the Appendix below). | |
| 40 | ||
| 41 | "Derivative Works" shall mean any work, whether in Source or Object | |
| 42 | form, that is based on (or derived from) the Work and for which the | |
| 43 | editorial revisions, annotations, elaborations, or other modifications | |
| 44 | represent, as a whole, an original work of authorship. For the purposes | |
| 45 | of this License, Derivative Works shall not include works that remain | |
| 46 | separable from, or merely link (or bind by name) to the interfaces of, | |
| 47 | the Work and Derivative Works thereof. | |
| 48 | ||
| 49 | "Contribution" shall mean any work of authorship, including | |
| 50 | the original version of the Work and any modifications or additions | |
| 51 | to that Work or Derivative Works thereof, that is intentionally | |
| 52 | submitted to Licensor for inclusion in the Work by the copyright owner | |
| 53 | or by an individual or Legal Entity authorized to submit on behalf of | |
| 54 | the copyright owner. For the purposes of this definition, "submitted" | |
| 55 | means any form of electronic, verbal, or written communication sent | |
| 56 | to the Licensor or its representatives, including but not limited to | |
| 57 | communication on electronic mailing lists, source code control systems, | |
| 58 | and issue tracking systems that are managed by, or on behalf of, the | |
| 59 | Licensor for the purpose of discussing and improving the Work, but | |
| 60 | excluding communication that is conspicuously marked or otherwise | |
| 61 | designated in writing by the copyright owner as "Not a Contribution." | |
| 62 | ||
| 63 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
| 64 | on behalf of whom a Contribution has been received by Licensor and | |
| 65 | subsequently incorporated within the Work. | |
| 66 | ||
| 67 | 2. Grant of Copyright License. Subject to the terms and conditions of | |
| 68 | this License, each Contributor hereby grants to You a perpetual, | |
| 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 70 | copyright license to reproduce, prepare Derivative Works of, | |
| 71 | publicly display, publicly perform, sublicense, and distribute the | |
| 72 | Work and such Derivative Works in Source or Object form. | |
| 73 | ||
| 74 | 3. Grant of Patent License. Subject to the terms and conditions of | |
| 75 | this License, each Contributor hereby grants to You a perpetual, | |
| 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 77 | (except as stated in this section) patent license to make, have made, | |
| 78 | use, offer to sell, sell, import, and otherwise transfer the Work, | |
| 79 | where such license applies only to those patent claims licensable | |
| 80 | by such Contributor that are necessarily infringed by their | |
| 81 | Contribution(s) alone or by combination of their Contribution(s) | |
| 82 | with the Work to which such Contribution(s) was submitted. If You | |
| 83 | institute patent litigation against any entity (including a | |
| 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work | |
| 85 | or a Contribution incorporated within the Work constitutes direct | |
| 86 | or contributory patent infringement, then any patent licenses | |
| 87 | granted to You under this License for that Work shall terminate | |
| 88 | as of the date such litigation is filed. | |
| 89 | ||
| 90 | 4. Redistribution. You may reproduce and distribute copies of the | |
| 91 | Work or Derivative Works thereof in any medium, with or without | |
| 92 | modifications, and in Source or Object form, provided that You | |
| 93 | meet the following conditions: | |
| 94 | ||
| 95 | (a) You must give any other recipients of the Work or | |
| 96 | Derivative Works a copy of this License; and | |
| 97 | ||
| 98 | (b) You must cause any modified files to carry prominent notices | |
| 99 | stating that You changed the files; and | |
| 100 | ||
| 101 | (c) You must retain, in the Source form of any Derivative Works | |
| 102 | that You distribute, all copyright, patent, trademark, and | |
| 103 | attribution notices from the Source form of the Work, | |
| 104 | excluding those notices that do not pertain to any part of | |
| 105 | the Derivative Works; and | |
| 106 | ||
| 107 | (d) If the Work includes a "NOTICE" text file as part of its | |
| 108 | distribution, then any Derivative Works that You distribute must | |
| 109 | include a readable copy of the attribution notices contained | |
| 110 | within such NOTICE file, excluding those notices that do not | |
| 111 | pertain to any part of the Derivative Works, in at least one | |
| 112 | of the following places: within a NOTICE text file distributed | |
| 113 | as part of the Derivative Works; within the Source form or | |
| 114 | documentation, if provided along with the Derivative Works; or, | |
| 115 | within a display generated by the Derivative Works, if and | |
| 116 | wherever such third-party notices normally appear. The contents | |
| 117 | of the NOTICE file are for informational purposes only and | |
| 118 | do not modify the License. You may add Your own attribution | |
| 119 | notices within Derivative Works that You distribute, alongside | |
| 120 | or as an addendum to the NOTICE text from the Work, provided | |
| 121 | that such additional attribution notices cannot be construed | |
| 122 | as modifying the License. | |
| 123 | ||
| 124 | You may add Your own copyright statement to Your modifications and | |
| 125 | may provide additional or different license terms and conditions | |
| 126 | for use, reproduction, or distribution of Your modifications, or | |
| 127 | for any such Derivative Works as a whole, provided Your use, | |
| 128 | reproduction, and distribution of the Work otherwise complies with | |
| 129 | the conditions stated in this License. | |
| 130 | ||
| 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, | |
| 132 | any Contribution intentionally submitted for inclusion in the Work | |
| 133 | by You to the Licensor shall be under the terms and conditions of | |
| 134 | this License, without any additional terms or conditions. | |
| 135 | Notwithstanding the above, nothing herein shall supersede or modify | |
| 136 | the terms of any separate license agreement you may have executed | |
| 137 | with Licensor regarding such Contributions. | |
| 138 | ||
| 139 | 6. Trademarks. This License does not grant permission to use the trade | |
| 140 | names, trademarks, service marks, or product names of the Licensor, | |
| 141 | except as required for reasonable and customary use in describing the | |
| 142 | origin of the Work and reproducing the content of the NOTICE file. | |
| 143 | ||
| 144 | 7. Disclaimer of Warranty. Unless required by applicable law or | |
| 145 | agreed to in writing, Licensor provides the Work (and each | |
| 146 | Contributor provides its Contributions) on an "AS IS" BASIS, | |
| 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| 148 | implied, including, without limitation, any warranties or conditions | |
| 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
| 150 | PARTICULAR PURPOSE. You are solely responsible for determining the | |
| 151 | appropriateness of using or redistributing the Work and assume any | |
| 152 | risks associated with Your exercise of permissions under this License. | |
| 153 | ||
| 154 | 8. Limitation of Liability. In no event and under no legal theory, | |
| 155 | whether in tort (including negligence), contract, or otherwise, | |
| 156 | unless required by applicable law (such as deliberate and grossly | |
| 157 | negligent acts) or agreed to in writing, shall any Contributor be | |
| 158 | liable to You for damages, including any direct, indirect, special, | |
| 159 | incidental, or consequential damages of any character arising as a | |
| 160 | result of this License or out of the use or inability to use the | |
| 161 | Work (including but not limited to damages for loss of goodwill, | |
| 162 | work stoppage, computer failure or malfunction, or any and all | |
| 163 | other commercial damages or losses), even if such Contributor | |
| 164 | has been advised of the possibility of such damages. | |
| 165 | ||
| 166 | 9. Accepting Warranty or Additional Liability. While redistributing | |
| 167 | the Work or Derivative Works thereof, You may choose to offer, | |
| 168 | and charge a fee for, acceptance of support, warranty, indemnity, | |
| 169 | or other liability obligations and/or rights consistent with this | |
| 170 | License. However, in accepting such obligations, You may act only | |
| 171 | on Your own behalf and on Your sole responsibility, not on behalf | |
| 172 | of any other Contributor, and only if You agree to indemnify, | |
| 173 | defend, and hold each Contributor harmless for any liability | |
| 174 | incurred by, or claims asserted against, such Contributor by reason | |
| 175 | of your accepting any such warranty or additional liability. | |
| 176 | ||
| 177 | END OF TERMS AND CONDITIONS | |
| 178 | ||
| 179 | APPENDIX: How to apply the Apache License to your work. | |
| 180 | ||
| 181 | To apply the Apache License to your work, attach the following | |
| 182 | boilerplate notice, with the fields enclosed by brackets "[]" | |
| 183 | replaced with your own identifying information. (Don't include | |
| 184 | the brackets!) The text should be enclosed in the appropriate | |
| 185 | comment syntax for the file format. We also recommend that a | |
| 186 | file or class name and description of purpose be included on the | |
| 187 | same "printed page" as the copyright notice for easier | |
| 188 | identification within third-party archives. | |
| 189 | ||
| 190 | Copyright [yyyy] [name of copyright owner] | |
| 191 | ||
| 192 | Licensed under the Apache License, Version 2.0 (the "License"); | |
| 193 | you may not use this file except in compliance with the License. | |
| 194 | You may obtain a copy of the License at | |
| 195 | ||
| 196 | http://www.apache.org/licenses/LICENSE-2.0 | |
| 197 | ||
| 198 | Unless required by applicable law or agreed to in writing, software | |
| 199 | distributed under the License is distributed on an "AS IS" BASIS, | |
| 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 201 | See the License for the specific language governing permissions and | |
| 202 | limitations under the License. | |
| 203 |
| 1 | Copyright 2018 The Noto Project Authors (https://github.com/googlei18n/noto-fonts) | |
| 2 | ||
| 3 | This Font Software is licensed under the SIL Open Font License, | |
| 4 | Version 1.1. | |
| 5 | ||
| 6 | This license is copied below, and is also available with a FAQ at: | |
| 7 | http://scripts.sil.org/OFL | |
| 8 | ||
| 9 | ----------------------------------------------------------- | |
| 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 11 | ----------------------------------------------------------- | |
| 12 | ||
| 13 | PREAMBLE | |
| 14 | The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 15 | development of collaborative font projects, to support the font | |
| 16 | creation efforts of academic and linguistic communities, and to | |
| 17 | provide a free and open framework in which fonts may be shared and | |
| 18 | improved in partnership with others. | |
| 19 | ||
| 20 | The OFL allows the licensed fonts to be used, studied, modified and | |
| 21 | redistributed freely as long as they are not sold by themselves. The | |
| 22 | fonts, including any derivative works, can be bundled, embedded, | |
| 23 | redistributed and/or sold with any software provided that any reserved | |
| 24 | names are not used by derivative works. The fonts and derivatives, | |
| 25 | however, cannot be released under any other type of license. The | |
| 26 | requirement for fonts to remain under this license does not apply to | |
| 27 | any document created using the fonts or their derivatives. | |
| 28 | ||
| 29 | DEFINITIONS | |
| 30 | "Font Software" refers to the set of files released by the Copyright | |
| 31 | Holder(s) under this license and clearly marked as such. This may | |
| 32 | include source files, build scripts and documentation. | |
| 33 | ||
| 34 | "Reserved Font Name" refers to any names specified as such after the | |
| 35 | copyright statement(s). | |
| 36 | ||
| 37 | "Original Version" refers to the collection of Font Software | |
| 38 | components as distributed by the Copyright Holder(s). | |
| 39 | ||
| 40 | "Modified Version" refers to any derivative made by adding to, | |
| 41 | deleting, or substituting -- in part or in whole -- any of the | |
| 42 | components of the Original Version, by changing formats or by porting | |
| 43 | the Font Software to a new environment. | |
| 44 | ||
| 45 | "Author" refers to any designer, engineer, programmer, technical | |
| 46 | writer or other person who contributed to the Font Software. | |
| 47 | ||
| 48 | PERMISSION & CONDITIONS | |
| 49 | Permission is hereby granted, free of charge, to any person obtaining | |
| 50 | a copy of the Font Software, to use, study, copy, merge, embed, | |
| 51 | modify, redistribute, and sell modified and unmodified copies of the | |
| 52 | Font Software, subject to the following conditions: | |
| 53 | ||
| 54 | 1) Neither the Font Software nor any of its individual components, in | |
| 55 | Original or Modified Versions, may be sold by itself. | |
| 56 | ||
| 57 | 2) Original or Modified Versions of the Font Software may be bundled, | |
| 58 | redistributed and/or sold with any software, provided that each copy | |
| 59 | contains the above copyright notice and this license. These can be | |
| 60 | included either as stand-alone text files, human-readable headers or | |
| 61 | in the appropriate machine-readable metadata fields within text or | |
| 62 | binary files as long as those fields can be easily viewed by the user. | |
| 63 | ||
| 64 | 3) No Modified Version of the Font Software may use the Reserved Font | |
| 65 | Name(s) unless explicit written permission is granted by the | |
| 66 | corresponding Copyright Holder. This restriction only applies to the | |
| 67 | primary font name as presented to the users. | |
| 68 | ||
| 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 70 | Software shall not be used to promote, endorse or advertise any | |
| 71 | Modified Version, except to acknowledge the contribution(s) of the | |
| 72 | Copyright Holder(s) and the Author(s) or with their explicit written | |
| 73 | permission. | |
| 74 | ||
| 75 | 5) The Font Software, modified or unmodified, in part or in whole, | |
| 76 | must be distributed entirely under this license, and must not be | |
| 77 | distributed under any other license. The requirement for fonts to | |
| 78 | remain under this license does not apply to any document created using | |
| 79 | the Font Software. | |
| 80 | ||
| 81 | TERMINATION | |
| 82 | This license becomes null and void if any of the above conditions are | |
| 83 | not met. | |
| 84 | ||
| 85 | DISCLAIMER | |
| 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 94 | OTHER DEALINGS IN THE FONT SOFTWARE. | |
| 1 | 95 |
| 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. | |
| 2 | ||
| 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| 4 | This license is copied below, and is also available with a FAQ at: | |
| 5 | http://scripts.sil.org/OFL | |
| 6 | ||
| 7 | ||
| 8 | ----------------------------------------------------------- | |
| 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 10 | ----------------------------------------------------------- | |
| 11 | ||
| 12 | PREAMBLE | |
| 13 | The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 14 | development of collaborative font projects, to support the font creation | |
| 15 | efforts of academic and linguistic communities, and to provide a free and | |
| 16 | open framework in which fonts may be shared and improved in partnership | |
| 17 | with others. | |
| 18 | ||
| 19 | The OFL allows the licensed fonts to be used, studied, modified and | |
| 20 | redistributed freely as long as they are not sold by themselves. The | |
| 21 | fonts, including any derivative works, can be bundled, embedded, | |
| 22 | redistributed and/or sold with any software provided that any reserved | |
| 23 | names are not used by derivative works. The fonts and derivatives, | |
| 24 | however, cannot be released under any other type of license. The | |
| 25 | requirement for fonts to remain under this license does not apply | |
| 26 | to any document created using the fonts or their derivatives. | |
| 27 | ||
| 28 | DEFINITIONS | |
| 29 | "Font Software" refers to the set of files released by the Copyright | |
| 30 | Holder(s) under this license and clearly marked as such. This may | |
| 31 | include source files, build scripts and documentation. | |
| 32 | ||
| 33 | "Reserved Font Name" refers to any names specified as such after the | |
| 34 | copyright statement(s). | |
| 35 | ||
| 36 | "Original Version" refers to the collection of Font Software components as | |
| 37 | distributed by the Copyright Holder(s). | |
| 38 | ||
| 39 | "Modified Version" refers to any derivative made by adding to, deleting, | |
| 40 | or substituting -- in part or in whole -- any of the components of the | |
| 41 | Original Version, by changing formats or by porting the Font Software to a | |
| 42 | new environment. | |
| 43 | ||
| 44 | "Author" refers to any designer, engineer, programmer, technical | |
| 45 | writer or other person who contributed to the Font Software. | |
| 46 | ||
| 47 | PERMISSION & CONDITIONS | |
| 48 | Permission is hereby granted, free of charge, to any person obtaining | |
| 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
| 50 | redistribute, and sell modified and unmodified copies of the Font | |
| 51 | Software, subject to the following conditions: | |
| 52 | ||
| 53 | 1) Neither the Font Software nor any of its individual components, | |
| 54 | in Original or Modified Versions, may be sold by itself. | |
| 55 | ||
| 56 | 2) Original or Modified Versions of the Font Software may be bundled, | |
| 57 | redistributed and/or sold with any software, provided that each copy | |
| 58 | contains the above copyright notice and this license. These can be | |
| 59 | included either as stand-alone text files, human-readable headers or | |
| 60 | in the appropriate machine-readable metadata fields within text or | |
| 61 | binary files as long as those fields can be easily viewed by the user. | |
| 62 | ||
| 63 | 3) No Modified Version of the Font Software may use the Reserved Font | |
| 64 | Name(s) unless explicit written permission is granted by the corresponding | |
| 65 | Copyright Holder. This restriction only applies to the primary font name as | |
| 66 | presented to the users. | |
| 67 | ||
| 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 69 | Software shall not be used to promote, endorse or advertise any | |
| 70 | Modified Version, except to acknowledge the contribution(s) of the | |
| 71 | Copyright Holder(s) and the Author(s) or with their explicit written | |
| 72 | permission. | |
| 73 | ||
| 74 | 5) The Font Software, modified or unmodified, in part or in whole, | |
| 75 | must be distributed entirely under this license, and must not be | |
| 76 | distributed under any other license. The requirement for fonts to | |
| 77 | remain under this license does not apply to any document created | |
| 78 | using the Font Software. | |
| 79 | ||
| 80 | TERMINATION | |
| 81 | This license becomes null and void if any of the above conditions are | |
| 82 | not met. | |
| 83 | ||
| 84 | DISCLAIMER | |
| 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 93 | OTHER DEALINGS IN THE FONT SOFTWARE. | |
| 1 | 94 |
| 1 | Copyright 2014-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. | |
| 2 | ||
| 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. | |
| 4 | ||
| 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL | |
| 6 | ||
| 7 | ||
| 8 | ----------------------------------------------------------- | |
| 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 | |
| 10 | ----------------------------------------------------------- | |
| 11 | ||
| 12 | PREAMBLE | |
| 13 | The goals of the Open Font License (OFL) are to stimulate worldwide | |
| 14 | development of collaborative font projects, to support the font creation | |
| 15 | efforts of academic and linguistic communities, and to provide a free and | |
| 16 | open framework in which fonts may be shared and improved in partnership | |
| 17 | with others. | |
| 18 | ||
| 19 | The OFL allows the licensed fonts to be used, studied, modified and | |
| 20 | redistributed freely as long as they are not sold by themselves. The | |
| 21 | fonts, including any derivative works, can be bundled, embedded, | |
| 22 | redistributed and/or sold with any software provided that any reserved | |
| 23 | names are not used by derivative works. The fonts and derivatives, | |
| 24 | however, cannot be released under any other type of license. The | |
| 25 | requirement for fonts to remain under this license does not apply | |
| 26 | to any document created using the fonts or their derivatives. | |
| 27 | ||
| 28 | DEFINITIONS | |
| 29 | "Font Software" refers to the set of files released by the Copyright | |
| 30 | Holder(s) under this license and clearly marked as such. This may | |
| 31 | include source files, build scripts and documentation. | |
| 32 | ||
| 33 | "Reserved Font Name" refers to any names specified as such after the | |
| 34 | copyright statement(s). | |
| 35 | ||
| 36 | "Original Version" refers to the collection of Font Software components as | |
| 37 | distributed by the Copyright Holder(s). | |
| 38 | ||
| 39 | "Modified Version" refers to any derivative made by adding to, deleting, | |
| 40 | or substituting -- in part or in whole -- any of the components of the | |
| 41 | Original Version, by changing formats or by porting the Font Software to a | |
| 42 | new environment. | |
| 43 | ||
| 44 | "Author" refers to any designer, engineer, programmer, technical | |
| 45 | writer or other person who contributed to the Font Software. | |
| 46 | ||
| 47 | PERMISSION & CONDITIONS | |
| 48 | Permission is hereby granted, free of charge, to any person obtaining | |
| 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, | |
| 50 | redistribute, and sell modified and unmodified copies of the Font | |
| 51 | Software, subject to the following conditions: | |
| 52 | ||
| 53 | 1) Neither the Font Software nor any of its individual components, | |
| 54 | in Original or Modified Versions, may be sold by itself. | |
| 55 | ||
| 56 | 2) Original or Modified Versions of the Font Software may be bundled, | |
| 57 | redistributed and/or sold with any software, provided that each copy | |
| 58 | contains the above copyright notice and this license. These can be | |
| 59 | included either as stand-alone text files, human-readable headers or | |
| 60 | in the appropriate machine-readable metadata fields within text or | |
| 61 | binary files as long as those fields can be easily viewed by the user. | |
| 62 | ||
| 63 | 3) No Modified Version of the Font Software may use the Reserved Font | |
| 64 | Name(s) unless explicit written permission is granted by the corresponding | |
| 65 | Copyright Holder. This restriction only applies to the primary font name as | |
| 66 | presented to the users. | |
| 67 | ||
| 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font | |
| 69 | Software shall not be used to promote, endorse or advertise any | |
| 70 | Modified Version, except to acknowledge the contribution(s) of the | |
| 71 | Copyright Holder(s) and the Author(s) or with their explicit written | |
| 72 | permission. | |
| 73 | ||
| 74 | 5) The Font Software, modified or unmodified, in part or in whole, | |
| 75 | must be distributed entirely under this license, and must not be | |
| 76 | distributed under any other license. The requirement for fonts to | |
| 77 | remain under this license does not apply to any document created | |
| 78 | using the Font Software. | |
| 79 | ||
| 80 | TERMINATION | |
| 81 | This license becomes null and void if any of the above conditions are | |
| 82 | not met. | |
| 83 | ||
| 84 | DISCLAIMER | |
| 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF | |
| 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT | |
| 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE | |
| 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL | |
| 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM | |
| 93 | OTHER DEALINGS IN THE FONT SOFTWARE. | |
| 1 | 94 |
| 1 | ||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no" ?> | |
| 2 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
| 3 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1280" height="1024" viewBox="0 0 1280 1024" xml:space="preserve"> | |
| 4 | <desc>Created with Fabric.js 3.6.3</desc> | |
| 5 | <defs> | |
| 6 | </defs> | |
| 7 | <g transform="matrix(1.9692780337941629 0 0 1.9692780337941629 640.0153846153846 512.012312418764)" id="background-logo" > | |
| 8 | <rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;" paint-order="stroke" x="-325" y="-260" rx="0" ry="0" width="650" height="520" /> | |
| 9 | </g> | |
| 10 | <g transform="matrix(1.9692780337941629 0 0 1.9692780337941629 640.0170725174504 420.4016715831266)" id="logo-logo" > | |
| 11 | <g style="" paint-order="stroke" > | |
| 12 | <g transform="matrix(2.537 0 0 -2.537 -86.35385711719567 85.244912)" > | |
| 13 | <linearGradient id="SVGID_1_302284" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-24.348526 -27.478867 -27.478867 24.348526 138.479 129.67187)" x1="0" y1="0" x2="1" y2="0"> | |
| 14 | <stop offset="0%" style="stop-color:rgb(245,132,41);stop-opacity: 1"/> | |
| 15 | <stop offset="100%" style="stop-color:rgb(251,173,23);stop-opacity: 1"/> | |
| 16 | </linearGradient> | |
| 17 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: url(#SVGID_1_302284); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-127.92674550729492, -117.16399999999999)" d="m 118.951 124.648 c -9.395 -14.441 -5.243 -20.693 -5.243 -20.693 v 0 c 0 0 6.219 9.126 9.771 5.599 v 0 c 3.051 -3.023 -2.415 -8.668 -2.415 -8.668 v 0 c 0 0 33.24 13.698 17.995 28.872 v 0 c 0 0 -3.203 3.683 -7.932 3.684 v 0 c -3.46 0 -7.736 -1.97 -12.176 -8.794" stroke-linecap="round" /> | |
| 18 | </g> | |
| 19 | <g transform="matrix(2.537 0 0 -2.537 -84.52085711719567 70.2729119999999)" > | |
| 20 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(250,220,153); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(11.9895, -1.2609990716440347)" d="m 0 0 c 0 0 -6.501 6.719 -11.093 5.443 c -5.584 -1.545 -12.886 -12.078 -12.886 -12.078 c 0 0 5.98 16.932 15.29 15.731 C -1.19 8.127 0 0 0 0" stroke-linecap="round" /> | |
| 21 | </g> | |
| 22 | <g transform="matrix(2.537 0 0 -2.537 -22.327857117195663 48.729911999999956)" > | |
| 23 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(201,158,82); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-4.189, -10.432)" d="m 0 0 l -0.87 16.89 l 3.995 3.974 l 6.123 -6.156 z" stroke-linecap="round" /> | |
| 24 | </g> | |
| 25 | <g transform="matrix(2.537 0 0 -2.537 -11.3118571171957 24.124911999999966)" > | |
| 26 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(201,158,82); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(4.0955, -2.037)" d="m 0 0 l -2.081 -2.069 l -6.11 6.143 l 2.081 2.069 z" stroke-linecap="round" /> | |
| 27 | </g> | |
| 28 | <g transform="matrix(2.537 0 0 -2.537 46.27614288280432 -57.96708800000005)" > | |
| 29 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(217,170,93); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(12.070999999999998, 9.599000000000004)" d="m 0 0 c -1.226 0.69 -2.81 0.523 -3.862 -0.524 c -1.275 -1.268 -1.28 -3.33 -0.013 -4.604 l -31.681 -31.501 l -6.11 6.143 c 19.224 19.305 25.369 35.582 25.369 35.582 c 15.857 2.364 27.851 8.624 33.821 12.335 z" stroke-linecap="round" /> | |
| 30 | </g> | |
| 31 | <g transform="matrix(2.537 0 0 -2.537 -26.842857117195706 8.501911999999976)" > | |
| 32 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(217,170,93); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(4.1075, -2.0525)" d="M 0 0 L -2.081 -2.069 L -8.215 4.11 L -6.141 6.174 Z" stroke-linecap="round" /> | |
| 33 | </g> | |
| 34 | <g transform="matrix(2.537 0 0 -2.537 -51.495857117195726 19.491911999999985)" > | |
| 35 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(217,170,93); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(10.434000000000001, -1.0939999999999994)" d="m 0 0 l -3.995 -3.974 l -16.873 0.96 l 14.752 9.176 z" stroke-linecap="round" /> | |
| 36 | </g> | |
| 37 | <g transform="matrix(2.537 0 0 -2.537 55.72014288280434 -48.441088000000036)" > | |
| 38 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(201,158,82); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(9.671499999999998, 11.999499999999998)" d="M 0 0 L 17.536 17.443 C 13.788 11.486 7.47 -0.468 5.021 -16.312 c 0 0 -15.526 -6.982 -35.765 -25.13 l -6.135 6.168 l 31.681 31.5 c 1.273 -1.28 3.33 -1.279 4.604 -0.012 C 0.435 -2.764 0.629 -1.223 0 0" stroke-linecap="round" /> | |
| 39 | </g> | |
| 40 | </g> | |
| 41 | </g> | |
| 42 | <g transform="matrix(1.9692780337941629 0 0 1.9692780337941629 643.7363123827618 766.1975713477327)" id="text-logo-path" > | |
| 43 | <path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(247,149,33); fill-rule: nonzero; opacity: 1;" paint-order="stroke" transform=" translate(-186.83999999999997, 27.08)" d="M 4.47 -6.1 L 4.47 -6.1 L 4.47 -47.5 Q 4.47 -50.27 6.43 -52.23 Q 8.39 -54.19 11.16 -54.19 L 11.16 -54.19 Q 14.01 -54.19 15.95 -52.23 Q 17.89 -50.27 17.89 -47.5 L 17.89 -47.5 L 17.89 -30.09 L 34.95 -51.97 Q 35.74 -52.97 36.94 -53.58 Q 38.13 -54.19 39.42 -54.19 L 39.42 -54.19 Q 41.77 -54.19 43.42 -52.5 Q 45.07 -50.82 45.07 -48.5 L 45.07 -48.5 Q 45.07 -46.46 43.82 -44.93 L 43.82 -44.93 L 32.93 -31.44 L 46.8 -9.81 Q 47.84 -8.11 47.84 -6.27 L 47.84 -6.27 Q 47.84 -3.33 45.9 -1.39 Q 43.96 0.55 41.19 0.55 L 41.19 0.55 Q 39.42 0.55 37.89 -0.29 Q 36.37 -1.14 35.43 -2.57 L 35.43 -2.57 L 23.78 -21.15 L 17.89 -13.9 L 17.89 -6.1 Q 17.89 -3.33 15.93 -1.39 Q 13.97 0.55 11.16 0.55 L 11.16 0.55 Q 8.39 0.55 6.43 -1.39 Q 4.47 -3.33 4.47 -6.1 Z M 50.27 -19.24 L 50.27 -19.24 Q 50.27 -25.13 52.71 -29.78 Q 55.16 -34.43 59.7 -37.06 Q 64.24 -39.69 70.27 -39.69 L 70.27 -39.69 Q 76.37 -39.69 80.78 -37.09 Q 85.18 -34.49 87.43 -30.32 Q 89.69 -26.14 89.69 -21.6 L 89.69 -21.6 Q 89.69 -18.69 88.33 -17.26 Q 86.98 -15.84 83.86 -15.84 L 83.86 -15.84 L 62.89 -15.84 Q 63.23 -12.38 65.38 -10.31 Q 67.53 -8.25 70.86 -8.25 L 70.86 -8.25 Q 72.84 -8.25 74.19 -8.91 Q 75.54 -9.57 76.62 -10.64 L 76.62 -10.64 Q 77.62 -11.58 78.42 -12.03 Q 79.22 -12.48 80.43 -12.48 L 80.43 -12.48 Q 82.61 -12.48 84.19 -10.89 Q 85.77 -9.29 85.77 -7.04 L 85.77 -7.04 Q 85.77 -4.54 83.62 -2.77 L 83.62 -2.77 Q 81.71 -1.14 78.16 -0.03 Q 74.61 1.07 70.58 1.07 L 70.58 1.07 Q 64.76 1.07 60.13 -1.42 Q 55.5 -3.92 52.89 -8.53 Q 50.27 -13.14 50.27 -19.24 Z M 62.96 -23.57 L 62.96 -23.57 L 76.96 -23.57 Q 76.82 -26.97 74.93 -28.97 Q 73.05 -30.96 70.06 -30.96 L 70.06 -30.96 Q 67.08 -30.96 65.21 -28.97 Q 63.34 -26.97 62.96 -23.57 Z M 91.63 -19.24 L 91.63 -19.24 Q 91.63 -25.13 94.07 -29.78 Q 96.52 -34.43 101.06 -37.06 Q 105.6 -39.69 111.63 -39.69 L 111.63 -39.69 Q 117.73 -39.69 122.14 -37.09 Q 126.54 -34.49 128.79 -30.32 Q 131.04 -26.14 131.04 -21.6 L 131.04 -21.6 Q 131.04 -18.69 129.69 -17.26 Q 128.34 -15.84 125.22 -15.84 L 125.22 -15.84 L 104.25 -15.84 Q 104.59 -12.38 106.74 -10.31 Q 108.89 -8.25 112.22 -8.25 L 112.22 -8.25 Q 114.2 -8.25 115.55 -8.91 Q 116.9 -9.57 117.98 -10.64 L 117.98 -10.64 Q 118.98 -11.58 119.78 -12.03 Q 120.58 -12.48 121.79 -12.48 L 121.79 -12.48 Q 123.97 -12.48 125.55 -10.89 Q 127.13 -9.29 127.13 -7.04 L 127.13 -7.04 Q 127.13 -4.54 124.98 -2.77 L 124.98 -2.77 Q 123.07 -1.14 119.52 -0.03 Q 115.96 1.07 111.94 1.07 L 111.94 1.07 Q 106.12 1.07 101.49 -1.42 Q 96.86 -3.92 94.24 -8.53 Q 91.63 -13.14 91.63 -19.24 Z M 104.32 -23.57 L 104.32 -23.57 L 118.32 -23.57 Q 118.18 -26.97 116.29 -28.97 Q 114.4 -30.96 111.42 -30.96 L 111.42 -30.96 Q 108.44 -30.96 106.57 -28.97 Q 104.7 -26.97 104.32 -23.57 Z M 135.03 -6.03 L 135.03 -6.03 L 135.03 -33.14 Q 135.03 -35.64 136.85 -37.46 Q 138.67 -39.28 141.13 -39.28 L 141.13 -39.28 Q 143.7 -39.28 145.52 -37.46 Q 147.34 -35.64 147.34 -33.14 L 147.34 -33.14 L 147.34 -32.17 Q 148.97 -35.36 152.09 -37.42 Q 155.21 -39.49 159.82 -39.49 L 159.82 -39.49 Q 166.93 -39.49 170.19 -35.47 Q 173.44 -31.44 173.44 -24.44 L 173.44 -24.44 L 173.44 -6.03 Q 173.44 -3.33 171.5 -1.39 Q 169.56 0.55 166.86 0.55 L 166.86 0.55 Q 164.15 0.55 162.19 -1.39 Q 160.24 -3.33 160.24 -6.03 L 160.24 -6.03 L 160.24 -22.36 Q 160.24 -26.35 158.54 -27.91 Q 156.84 -29.47 154.65 -29.47 L 154.65 -29.47 Q 152.02 -29.47 150.13 -27.58 Q 148.24 -25.69 148.24 -20.73 L 148.24 -20.73 L 148.24 -6.03 Q 148.24 -3.33 146.3 -1.39 Q 144.36 0.55 141.65 0.55 L 141.65 0.55 Q 138.95 0.55 136.99 -1.39 Q 135.03 -3.33 135.03 -6.03 Z M 177.71 -47.56 L 177.71 -47.56 Q 177.71 -50.34 179.63 -52.26 Q 181.56 -54.19 184.23 -54.19 L 184.23 -54.19 Q 186.58 -54.19 188.39 -52.73 Q 190.19 -51.27 190.71 -48.99 L 190.71 -48.99 L 197.88 -15.12 L 206.52 -48.64 Q 207.07 -51.07 209.12 -52.63 Q 211.16 -54.19 213.69 -54.19 L 213.69 -54.19 Q 216.26 -54.19 218.25 -52.57 Q 220.25 -50.96 220.8 -48.64 L 220.8 -48.64 L 229.4 -15.39 L 236.64 -49.33 Q 237.06 -51.38 238.76 -52.78 Q 240.46 -54.19 242.61 -54.19 L 242.61 -54.19 Q 245.17 -54.19 246.94 -52.4 Q 248.71 -50.62 248.71 -48.05 L 248.71 -48.05 Q 248.71 -47.56 248.57 -46.73 L 248.57 -46.73 L 239.69 -7.38 Q 238.9 -3.99 236.11 -1.72 Q 233.32 0.55 229.68 0.55 L 229.68 0.55 Q 226.14 0.55 223.37 -1.61 Q 220.59 -3.78 219.73 -7.11 L 219.73 -7.11 L 213.07 -33.45 L 206.38 -7.11 Q 205.51 -3.71 202.79 -1.58 Q 200.07 0.55 196.53 0.55 L 196.53 0.55 Q 192.89 0.55 190.17 -1.72 Q 187.45 -3.99 186.65 -7.38 L 186.65 -7.38 L 177.85 -46.14 Q 177.71 -47.15 177.71 -47.56 Z M 253.35 -6.03 L 253.35 -6.03 L 253.35 -33.14 Q 253.35 -35.64 255.17 -37.46 Q 256.99 -39.28 259.46 -39.28 L 259.46 -39.28 Q 262.02 -39.28 263.84 -37.46 Q 265.66 -35.64 265.66 -33.14 L 265.66 -33.14 L 265.66 -31.44 L 265.94 -31.44 Q 266.8 -33.56 268.1 -35.24 Q 269.4 -36.92 270.69 -37.61 L 270.69 -37.61 Q 271.9 -38.24 273.46 -38.27 L 273.46 -38.27 Q 276.65 -38.27 278.14 -36.45 Q 279.63 -34.63 279.63 -32.52 L 279.63 -32.52 Q 279.63 -30.33 278.11 -28.62 Q 276.58 -26.9 274.08 -26.9 L 274.08 -26.9 Q 272.59 -26.9 271.07 -26.26 Q 269.54 -25.62 268.47 -24.34 L 268.47 -24.34 Q 266.56 -21.98 266.56 -17.68 L 266.56 -17.68 L 266.56 -6.03 Q 266.56 -3.33 264.62 -1.39 Q 262.68 0.55 259.98 0.55 L 259.98 0.55 Q 257.27 0.55 255.31 -1.39 Q 253.35 -3.33 253.35 -6.03 Z M 282.41 -49.71 L 282.41 -49.71 Q 282.41 -52 284.03 -53.61 Q 285.66 -55.23 287.95 -55.23 L 287.95 -55.23 L 291.21 -55.23 Q 293.5 -55.23 295.13 -53.6 Q 296.76 -51.97 296.76 -49.71 L 296.76 -49.71 Q 296.76 -47.43 295.11 -45.8 Q 293.46 -44.17 291.21 -44.17 L 291.21 -44.17 L 287.95 -44.17 Q 285.66 -44.17 284.03 -45.8 Q 282.41 -47.43 282.41 -49.71 Z M 282.96 -6.03 L 282.96 -6.03 L 282.96 -32.66 Q 282.96 -35.36 284.92 -37.32 Q 286.88 -39.28 289.58 -39.28 L 289.58 -39.28 Q 292.29 -39.28 294.23 -37.32 Q 296.17 -35.36 296.17 -32.66 L 296.17 -32.66 L 296.17 -6.03 Q 296.17 -3.33 294.21 -1.39 Q 292.25 0.55 289.58 0.55 L 289.58 0.55 Q 286.88 0.55 284.92 -1.39 Q 282.96 -3.33 282.96 -6.03 Z M 299.43 -34.29 L 299.43 -34.29 Q 299.43 -36.12 300.71 -37.41 Q 301.99 -38.69 303.76 -38.69 L 303.76 -38.69 L 306.19 -38.69 L 306.46 -43.96 Q 306.6 -46.32 308.34 -47.98 Q 310.07 -49.64 312.5 -49.64 L 312.5 -49.64 Q 314.99 -49.64 316.76 -47.86 Q 318.53 -46.07 318.53 -43.58 L 318.53 -43.58 L 318.53 -38.69 L 322.72 -38.69 Q 324.49 -38.69 325.77 -37.41 Q 327.06 -36.12 327.06 -34.36 L 327.06 -34.36 Q 327.06 -32.52 325.77 -31.24 Q 324.49 -29.95 322.72 -29.95 L 322.72 -29.95 L 318.81 -29.95 L 318.81 -14.14 Q 318.81 -11.23 320.05 -10.02 Q 321.3 -8.81 323.83 -8.81 L 323.83 -8.81 Q 325.46 -8.46 326.61 -7.14 Q 327.75 -5.82 327.75 -4.06 L 327.75 -4.06 Q 327.75 -2.57 326.94 -1.39 Q 326.12 -0.21 324.84 0.35 L 324.84 0.35 Q 322 0.83 318.11 0.87 L 318.11 0.87 Q 311.28 0.9 308.44 -2.5 L 308.44 -2.5 Q 305.67 -5.79 305.67 -12.65 L 305.67 -12.65 Q 305.67 -12.83 305.67 -13 L 305.67 -13 L 305.74 -29.95 L 303.76 -29.95 Q 301.99 -29.95 300.71 -31.24 Q 299.43 -32.52 299.43 -34.29 Z M 329.8 -19.24 L 329.8 -19.24 Q 329.8 -25.13 332.24 -29.78 Q 334.68 -34.43 339.23 -37.06 Q 343.77 -39.69 349.8 -39.69 L 349.8 -39.69 Q 355.9 -39.69 360.3 -37.09 Q 364.71 -34.49 366.96 -30.32 Q 369.21 -26.14 369.21 -21.6 L 369.21 -21.6 Q 369.21 -18.69 367.86 -17.26 Q 366.51 -15.84 363.39 -15.84 L 363.39 -15.84 L 342.42 -15.84 Q 342.76 -12.38 344.91 -10.31 Q 347.06 -8.25 350.39 -8.25 L 350.39 -8.25 Q 352.37 -8.25 353.72 -8.91 Q 355.07 -9.57 356.14 -10.64 L 356.14 -10.64 Q 357.15 -11.58 357.95 -12.03 Q 358.74 -12.48 359.96 -12.48 L 359.96 -12.48 Q 362.14 -12.48 363.72 -10.89 Q 365.3 -9.29 365.3 -7.04 L 365.3 -7.04 Q 365.3 -4.54 363.15 -2.77 L 363.15 -2.77 Q 361.24 -1.14 357.69 -0.03 Q 354.13 1.07 350.11 1.07 L 350.11 1.07 Q 344.29 1.07 339.66 -1.42 Q 335.03 -3.92 332.41 -8.53 Q 329.8 -13.14 329.8 -19.24 Z M 342.48 -23.57 L 342.48 -23.57 L 356.49 -23.57 Q 356.35 -26.97 354.46 -28.97 Q 352.57 -30.96 349.59 -30.96 L 349.59 -30.96 Q 346.61 -30.96 344.74 -28.97 Q 342.87 -26.97 342.48 -23.57 Z" stroke-linecap="round" /> | |
| 44 | </g> | |
| 45 | </svg> |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 4 | xmlns:cc="http://creativecommons.org/ns#" | |
| 5 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 6 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 7 | xmlns="http://www.w3.org/2000/svg" | |
| 8 | xmlns:xlink="http://www.w3.org/1999/xlink" | |
| 9 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 10 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 11 | height="197.4767" | |
| 12 | viewBox="0 0 695.99768 197.4767" | |
| 13 | width="695.99768" | |
| 14 | version="1.1" | |
| 15 | id="svg37" | |
| 16 | sodipodi:docname="new-logo-text.svg" | |
| 17 | inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"> | |
| 18 | <metadata | |
| 19 | id="metadata43"> | |
| 20 | <rdf:RDF> | |
| 21 | <cc:Work | |
| 22 | rdf:about=""> | |
| 23 | <dc:format>image/svg+xml</dc:format> | |
| 24 | <dc:type | |
| 25 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |
| 26 | <dc:title></dc:title> | |
| 27 | </cc:Work> | |
| 28 | </rdf:RDF> | |
| 29 | </metadata> | |
| 30 | <defs | |
| 31 | id="defs41"> | |
| 32 | <linearGradient | |
| 33 | id="a" | |
| 34 | gradientTransform="matrix(-8.7796153,42.985832,-42.985832,-8.7796153,514.83476,136.06192)" | |
| 35 | gradientUnits="userSpaceOnUse" | |
| 36 | x1=".152358" | |
| 37 | x2=".968809" | |
| 38 | y1="-.044912" | |
| 39 | y2="-.049471"> | |
| 40 | <stop | |
| 41 | offset="0" | |
| 42 | stop-color="#ec706a" | |
| 43 | id="stop2" /> | |
| 44 | <stop | |
| 45 | offset="1" | |
| 46 | stop-color="#ecd980" | |
| 47 | id="stop4" /> | |
| 48 | </linearGradient> | |
| 49 | </defs> | |
| 50 | <path | |
| 51 | style="fill:url(#a);fill-opacity:1.0;fill-rule:nonzero;stroke:none;stroke-width:1.226;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 52 | paint-order="stroke" | |
| 53 | d="m 496.76229,150.80474 c -4.25368,20.68081 3.28191,25.95476 3.28191,25.95476 v 0 c 0,0 3.00963,-13.19543 8.64082,-10.76172 v 0 c 4.83401,2.08299 1.12516,10.97002 1.12516,10.97002 v 0 c 0,0 31.78993,-30.5076 7.60484,-40.99434 v 0 c 0,0 -5.30287,-2.76791 -10.69842,-0.65209 v 0 c -3.94735,1.54891 -7.94375,5.71058 -9.95431,15.48337" | |
| 54 | stroke-linecap="round" | |
| 55 | id="path14" /> | |
| 56 | <path | |
| 57 | d="m 530.80335,138.63592 -10.99206,-16.95952 1.75995,-6.49966 10.01483,2.71233 z" | |
| 58 | fill="#126d95" | |
| 59 | id="path9" /> | |
| 60 | <path | |
| 61 | d="m 533.0598,112.36676 -0.91739,3.38458 -9.99361,-2.70665 0.91739,-3.38458 z" | |
| 62 | fill="#126d95" | |
| 63 | id="path11" /> | |
| 64 | <g | |
| 65 | fill="#51a9cf" | |
| 66 | id="g19" | |
| 67 | transform="translate(-295.50101,-692.52836)"> | |
| 68 | <path | |
| 69 | d="m 834.01973,741.0381 c -1.68105,0.0185 -3.22054,1.13771 -3.68367,2.84981 -0.56186,2.07405 0.665,4.21099 2.73743,4.77241 l -13.96475,51.52944 -9.99361,-2.70665 c 8.36013,-31.46487 4.99411,-51.98144 4.99411,-51.98144 14.99782,-11.92097 23.67,-25.56577 27.63101,-32.97331 z" | |
| 70 | id="path13" /> | |
| 71 | <path | |
| 72 | d="m 818.56767,802.18881 -0.9174,3.38458 -10.03996,-2.72957 0.91314,-3.37522 z" | |
| 73 | id="path15" /> | |
| 74 | <path | |
| 75 | d="m 817.07405,807.70594 -1.75995,6.49966 -18.03534,9.08805 9.78412,-18.31044 z" | |
| 76 | id="path17" /> | |
| 77 | </g> | |
| 78 | <path | |
| 79 | d="m 540.69709,49.12083 7.72577,-28.52932 c -0.3195,8.40427 0.28451,24.55036 7.21678,42.41047 0,0 -11.89603,16.50235 -21.99788,47.3763 l -10.03442,-2.71758 13.96533,-51.5284 c 2.08221,0.56405 4.21039,-0.66603 4.77182,-2.73844 0.45427,-1.67248 -0.26571,-3.38317 -1.64739,-4.27302" | |
| 80 | fill="#126d95" | |
| 81 | id="path21" /> | |
| 82 | <text | |
| 83 | transform="translate(-295.73751 -689.6407)" | |
| 84 | id="text25" /> | |
| 85 | <g | |
| 86 | style="font-style:italic;font-weight:800;font-size:133.333;font-family:Merriweather Sans;letter-spacing:0;word-spacing:0;fill:#51a9cf" | |
| 87 | id="g35"> | |
| 88 | <text | |
| 89 | x="16.133343" | |
| 90 | y="130.6234" | |
| 91 | id="text29"><tspan | |
| 92 | x="16.133343" | |
| 93 | y="130.6234" | |
| 94 | id="tspan27">KeenWr</tspan></text> | |
| 95 | <text | |
| 96 | x="552.53137" | |
| 97 | y="130.6234" | |
| 98 | id="text33"><tspan | |
| 99 | x="552.53137" | |
| 100 | y="130.6234" | |
| 101 | id="tspan31">te</tspan></text> | |
| 102 | </g> | |
| 103 | </svg> | |
| 1 | 104 |
| 1 | ||
| 1 | <svg height="197.4767" viewBox="0 0 493.25561 197.4767" width="493.25562" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(-8.7796153 42.985832 -42.985832 -8.7796153 810.33577 828.59028)" gradientUnits="userSpaceOnUse" x1=".152358" x2=".968809" y1="-.044912" y2="-.049471"><stop offset="0" stop-color="#ec706a"/><stop offset="1" stop-color="#ecd980"/></linearGradient><text transform="translate(-312.52749 -472.07353)"/><text fill="#51a9cf" font-family="'Noto Serif CJK SC'" font-size="35.1025" letter-spacing="0" transform="matrix(3.7983969 0 0 3.7983969 -330.7653 961.00598)" word-spacing="0"><tspan x="91.011719" y="-209.05206"><tspan x="91.011719" y="-209.05206">智能写<tspan fill="#51a9cf"/></tspan></tspan><tspan x="91.011719" y="-165.17393"/></text><g transform="translate(-377.88503 -692.52836)"><path d="m793.12811 845.45734c-1.09438 20.55837 6.93804 24.54772 6.93804 24.54772s.98325-13.16026 6.76656-11.6325c4.96369 1.30552 2.67983 10.4134 2.67983 10.4134s26.21535-34.03672 1.372-40.63137-5.51534-1.89773-10.40994.92679c-3.58074 2.06734-6.82887 6.66097-7.34649 16.37596" fill="url(#a)"/><path d="m826.30436 831.16428-10.99206-16.95952 1.75995-6.49966 10.01483 2.71233z" fill="#126d95"/><path d="m828.56081 804.89512-.91739 3.38458-9.99361-2.70665.91739-3.38458z" fill="#126d95"/><g fill="#51a9cf"><path d="m834.01973 741.0381c-1.68105.0185-3.22054 1.13771-3.68367 2.84981-.56186 2.07405.665 4.21099 2.73743 4.77241l-13.96475 51.52944-9.99361-2.70665c8.36013-31.46487 4.99411-51.98144 4.99411-51.98144 14.99782-11.92097 23.67-25.56577 27.63101-32.97331z"/><path d="m818.56767 802.18881-.9174 3.38458-10.03996-2.72957.91314-3.37522z"/><path d="m817.07405 807.70594-1.75995 6.49966-18.03534 9.08805 9.78412-18.31044z"/></g><path d="m836.1981 741.64919 7.72577-28.52932c-.3195 8.40427.28451 24.55036 7.21678 42.41047 0 0-11.89603 16.50235-21.99788 47.3763l-10.03442-2.71758 13.96533-51.5284c2.08221.56405 4.21039-.66603 4.77182-2.73844.45427-1.67248-.26571-3.38317-1.64739-4.27302" fill="#126d95"/></g></svg> |
| 1 | 1 | |
| 2 | Blues | |
| 3 | Light - 51a9cf | |
| 4 | Dark - 126d95 | |
| 5 | ||
| 6 | Red & Yellow | |
| 7 | Light yellow - ecd980 | |
| 8 | Light red - ec706a | |
| 9 | Dark red - 7e252f | |
| 10 | ||
| 11 | Greens | |
| 12 | Light - 76A786 | |
| 13 | Dark - 385742 | |
| 14 | ||
| 15 | Grayscale | |
| 16 | Light - bac2c5 | |
| 17 | Dark - 394343 | |
| 18 | ||
| 19 |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to use the [R](https://www.r-project.org/) | |
| 4 | programming language from within the application. The application uses an | |
| 5 | interpreter known as [Renjin](https://www.renjin.org/) to integrate with R. | |
| 6 | ||
| 7 | # Hello world | |
| 8 | ||
| 9 | Complete the following steps to see R in action: | |
| 10 | ||
| 11 | 1. Start the application. | |
| 12 | 1. Click **File → New** to create a new file. | |
| 13 | 1. Click **File → Save As**. | |
| 14 | 1. Set **Name** to: `addition.Rmd` | |
| 15 | 1. Click **Save**. | |
| 16 | ||
| 17 | Setting the file name extension tells the application what processor to | |
| 18 | use when transforming the contents for display in the preview pane. Continue | |
| 19 | by typing in the following text, including the backticks: | |
| 20 | ||
| 21 | ```r | |
| 22 | `r#1 + 1` | |
| 23 | ``` | |
| 24 | ||
| 25 | The preview pane shows the result of `1` plus `1`: | |
| 26 | ||
| 27 | ``` | |
| 28 | 2.0 | |
| 29 | ``` | |
| 30 | ||
| 31 | # Bootstrap script | |
| 32 | ||
| 33 | Being able to run R code while editing an R Markdown document is convenient. | |
| 34 | Having the ability to call functions is where the power of R can be | |
| 35 | leveraged. | |
| 36 | ||
| 37 | Complete the following steps to call an R function from your own library: | |
| 38 | ||
| 39 | 1. Click **File → New** to create a new file. | |
| 40 | 1. Click **File → Save As**. | |
| 41 | 1. Browse to your home directory. | |
| 42 | 1. Set **Name** to: `library.R`. | |
| 43 | 1. Click **Save**. | |
| 44 | 1. Set the contents to: | |
| 45 | ``` r | |
| 46 | sum <- function( a, b ) { | |
| 47 | a + b | |
| 48 | } | |
| 49 | ``` | |
| 50 | 1. Click the **Save** icon. | |
| 51 | 1. Click **R → Script**. | |
| 52 | 1. Set the **R Startup Script** contents to: | |
| 53 | ``` r | |
| 54 | source( 'library.R' ); | |
| 55 | ``` | |
| 56 | 1. Click **OK**. | |
| 57 | 1. Create a new file. | |
| 58 | 1. Set the contents to: | |
| 59 | ``` r | |
| 60 | `r#sum( 5, 5 )` | |
| 61 | ``` | |
| 62 | 1. Save the file as `sum.R`. | |
| 63 | ||
| 64 | The preview panel shows the result of calling the `sum` function: | |
| 65 | ||
| 66 | ``` | |
| 67 | 10.0 | |
| 68 | ``` | |
| 69 | ||
| 70 | This shows how the bootstrap script can load `library.R`, which defines | |
| 71 | a `sum` function that is called by name in the Markdown document. | |
| 72 | ||
| 73 | # Working directory | |
| 74 | ||
| 75 | R files may be sourced from any directory, not just the user's home | |
| 76 | directory. Accomplish this as follows: | |
| 77 | ||
| 78 | 1. Click **R → Directory**. | |
| 79 | 1. Set **Directory** to a different directory. | |
| 80 | 1. Click **OK**. | |
| 81 | 1. Create the directory if it does not exist. | |
| 82 | 1. Move `library.R` into the directory. | |
| 83 | 1. Append a new function to `library.R` as follows: | |
| 84 | ``` r | |
| 85 | mul <- function( a, b ) { | |
| 86 | a * b | |
| 87 | } | |
| 88 | ``` | |
| 89 | 1. Click **R → Script**. | |
| 90 | 1. Set the **R Startup Script** contents to: | |
| 91 | ``` r | |
| 92 | setwd( '{{application.r.working.directory}}' ); | |
| 93 | source( 'library.R' ); | |
| 94 | ``` | |
| 95 | 1. Change `sum.Rmd` to: | |
| 96 | ``` r | |
| 97 | `r#mul( 5, 5 )` | |
| 98 | ``` | |
| 99 | 1. Close the file `sum.Rmd`. | |
| 100 | 1. Confirm saving the file when prompted. | |
| 101 | 1. Re-open `sum.Rmd`. | |
| 102 | ||
| 103 | The preview panel shows: | |
| 104 | ||
| 105 | ``` | |
| 106 | 25.0 | |
| 107 | ``` | |
| 108 | ||
| 109 | Calling `setwd` using `'{{application.r.working.directory}}'` changes the | |
| 110 | working directory where the R engine searches for source files. | |
| 111 | ||
| 112 | # YAML variable definitions | |
| 113 | ||
| 114 | To see how variable definitions work in R, try the following: | |
| 115 | ||
| 116 | 1. Create a new file. | |
| 117 | 1. Change the contents to (use spaces not tabs): | |
| 118 | ``` yaml | |
| 119 | project: | |
| 120 | title: Project Title | |
| 121 | author: Author Name | |
| 122 | ``` | |
| 123 | 1. Save the file as `definitions.yaml`. | |
| 124 | 1. Click **File → Open**. | |
| 125 | 1. Set **Source Files** to **Variable Files**. | |
| 126 | 1. Select `definitions.yaml`. | |
| 127 | 1. Click **Open**. | |
| 128 | 1. Open `sum.Rmd` if it is not already open. | |
| 129 | 1. Type: `je` | |
| 130 | 1. Press `Ctrl+Space` | |
| 131 | ||
| 132 | The editor inserts the following text (matches `je` against Pro**je**ct): | |
| 133 | ||
| 134 | ``` r | |
| 135 | `r#x( v$project$title )` | |
| 136 | ``` | |
| 137 | ||
| 138 | The preview panel shows: | |
| 139 | ||
| 140 | ``` | |
| 141 | r#x( 'Project Title' ) | |
| 142 | ``` | |
| 143 | ||
| 144 | This is because the application inserts variable reference names based | |
| 145 | on the type of file being edited. By default, the R engine does not have | |
| 146 | a function named `x` defined. | |
| 147 | ||
| 148 | Continue as follows: | |
| 149 | ||
| 150 | 1. Click **R → Script**. | |
| 151 | 1. Append the following: | |
| 152 | ``` r | |
| 153 | x <- function( s ) { | |
| 154 | tryCatch( { | |
| 155 | r = eval( parse( text = s ) ) | |
| 156 | ||
| 157 | ifelse( is.atomic( r ), r, s ); | |
| 158 | }, | |
| 159 | warning = function( w ) { s }, | |
| 160 | error = function( e ) { s } ) | |
| 161 | } | |
| 162 | ``` | |
| 163 | 1. Click **OK**. | |
| 164 | 1. Close and re-open `sum.Rmd`. | |
| 165 | ||
| 166 | The preview panel shows: | |
| 167 | ||
| 168 | ``` | |
| 169 | 25.0 | |
| 170 | ||
| 171 | Project Title | |
| 172 | ``` | |
| 173 | ||
| 174 | The `x` function attempts to evaluate the expression defined by the YAML | |
| 175 | variable. This means that the YAML variables can also include expressions | |
| 176 | that R is capable of evaluating. | |
| 177 | ||
| 178 | While the `x` function can be defined within the R Startup Script, it is | |
| 179 | better practice to put it into its own library so that it can be reused | |
| 180 | outside of the application. | |
| 181 | ||
| 1 | 182 |
| 1 | *Song of the Yellow Bird*: | |
| 2 | ||
| 3 | 翩翩黃鳥, | |
| 4 | 雌雄相依。 | |
| 5 | 念我之獨, | |
| 6 | 誰其與歸? | |
| 7 | ||
| 8 | English translation: | |
| 9 | ||
| 10 | Orioles fly smoothly | |
| 11 | Female and male cuddle close together | |
| 12 | Thinking of my loneliness | |
| 13 | Whom shall I go with? | |
| 14 | ||
| 15 | Fonts: | |
| 16 | ||
| 17 | * Regular: 활판 인쇄술 | |
| 18 | * Bold: **활판 인쇄술** | |
| 19 | * Monospace: `활판 인쇄술` | |
| 20 | * Monospace bold: **`활판 인쇄술`** | |
| 21 | * Math: $E=mc^2$ | |
| 22 | ||
| 1 | 23 |
| 1 | --- | |
| 2 | formula: | |
| 3 | sqrt: | |
| 4 | value: "420" | |
| 5 | quadratic: | |
| 6 | a: "25" | |
| 7 | b: "84.906" | |
| 8 | c: "20" | |
| 1 | 9 |
| 1 |  | |
| 2 | ||
| 3 | Given the quadratic formula: | |
| 4 | ||
| 5 | $x = \frac{-b \pm \sqrt{b^2 -4ac}}{2a}$ | |
| 6 | ||
| 7 | Formatted in an R Markdown document as follows: | |
| 8 | ||
| 9 | $x = \frac{-b \pm \sqrt{b^2 -4ac}}{2a}$ | |
| 10 | ||
| 11 | We can substitute the following values: | |
| 12 | ||
| 13 | $a = `r# x(v$formula$quadratic$a)`, b = `r# x(v$formula$quadratic$b)`, c = `r# x(v$formula$quadratic$c)`$ | |
| 14 | ||
| 15 | `r# -x(v$formula$quadratic$b) + sqrt( v$formula$quadratic$b^2 - 4 * v$formula$quadratic$a * v$formula$quadratic$c )` | |
| 16 | ||
| 17 | To arrive at two solutions: | |
| 18 | ||
| 19 | $x = \frac{-b + \sqrt{b^2 -4ac}}{2a} = `r# (-x(v$formula$quadratic$b) + sqrt( x(v$formula$quadratic$b)^2 - 4 * x(v$formula$quadratic$a) * x(v$formula$quadratic$c) )) / (2 * x(v$formula$quadratic$a))`$ | |
| 20 | ||
| 21 | $x = \frac{-b - \sqrt{b^2 -4ac}}{2a} = `r# (-x(v$formula$quadratic$b) - sqrt( x(v$formula$quadratic$b)^2 - 4 * x(v$formula$quadratic$a) * x(v$formula$quadratic$c) )) / (2 * x(v$formula$quadratic$a))`$ | |
| 22 | ||
| 23 | Changing the variable values is reflected in the output immediately. | |
| 1 | 24 |
| 1 | #  | |
| 2 | ||
| 3 | # Real-time equation rendering | |
| 4 | ||
| 5 | Interpolated variables within R calculations, formatted as an equation: | |
| 6 | ||
| 7 | $\sqrt{`r#x( v$formula$sqrt$value)`} = \pm `r# round(sqrt(x( v$formula$sqrt$value )),5)`$ | |
| 8 | ||
| 9 | # Maxwell's equations | |
| 10 | ||
| 11 | $rot \vec{E} = \frac{1}{c} \frac{\partial{\vec{B}}}{\partial t}, div \vec{B} = 0$ | |
| 12 | ||
| 13 | $rot \vec{B} = \frac{1}{c} \frac{\partial{\vec{E}}}{\partial t} + \frac{4\pi}{c} \vec{j}, div \vec{E} = 4 \pi \rho_{\varepsilon}$ | |
| 14 | ||
| 15 | # Time-dependent Schrödinger equation | |
| 16 | ||
| 17 | $- \frac{{\hbar ^2 }}{{2m}}\frac{{\partial ^2 \psi (x,t)}}{{\partial x^2 }} + U(x)\psi (x,t) = i\hbar \frac{{\partial \psi (x,t)}}{{\partial t}}$ | |
| 18 | ||
| 19 | # Discrete-time Fourier transforms | |
| 20 | ||
| 21 | Unit step function: $u(n) \Leftrightarrow \frac{1}{1-e^{-jw}} + \sum_{k=-\infty}^{\infty} \pi \delta (\omega + 2\pi k)$ | |
| 22 | ||
| 23 | Shifted delta: $\delta (n - n_o ) \Leftrightarrow e^{ - j\omega n_o }$ | |
| 24 | ||
| 25 | # Faraday's Law | |
| 26 | ||
| 27 | $\oint_C {E \cdot d\ell = - \frac{d}{{dt}}} \int_S {B_n dA}$ | |
| 28 | ||
| 29 | # Infinite series | |
| 30 | ||
| 31 | $sin(x) = \sum_{n = 1}^{\infty} {\frac{{( { - 1})^{n - 1} x^{2n - 1} }}{{( {2n - 1})!}}}$ | |
| 32 | ||
| 33 | # Magnetic flux | |
| 34 | ||
| 35 | $\phi _m = \int_S {N{{B}} \cdot {{\hat n}}dA = } \int_S {NB_n dA}$ | |
| 36 | ||
| 37 | # Driven oscillation amplitude | |
| 38 | ||
| 39 | $A = \frac{{F_0 }}{{\sqrt {m^2 ( {\omega _0^2 - \omega ^2 } )^2 + b^2 \omega ^2 } }}$ | |
| 40 | ||
| 41 | # Optics | |
| 42 | ||
| 43 | $\phi = \frac{{2\pi }}{\lambda }a sin(\theta)$ | |
| 1 | 44 |
| 1 | # Variables | |
| 2 | ||
| 3 | Diagrams that include variables: | |
| 4 | ||
| 5 |  | |
| 6 | ||
| 7 |  | |
| 8 | ||
| 9 | # PDF themes | |
| 10 | ||
| 11 | In the background of the following screenshot, the editor shows a novel | |
| 12 | being edited: | |
| 13 | ||
| 14 |  | |
| 15 | ||
| 16 | Highlighted items of note: | |
| 17 | ||
| 18 | * PDF icon in the upper-left | |
| 19 | * Novel metadata as integrated variables towards the top-left | |
| 20 | * Theme selection dialog in the upper-middle | |
| 21 | * Three different styles, including: | |
| 22 | * Boschet, based on Baskerville font, nicely styled | |
| 23 | * Handrit, based on Courier font, double-spaced, manuscript format | |
| 24 | * Tarmes, based on Times Roman font, minimal styling | |
| 25 | * Variations in page numbers | |
| 26 | * Manuscript includes word count, automatically | |
| 27 | * Preferences dialog in the middle | |
| 28 | ||
| 29 | # Internationalization | |
| 30 | ||
| 31 | Poem with locale settings: | |
| 32 | ||
| 33 |  | |
| 34 | ||
| 35 | # Equations | |
| 36 | ||
| 37 | TeX equations with detached preview: | |
| 38 | ||
| 39 |  | |
| 40 | ||
| 41 | # Dockable tabs | |
| 42 | ||
| 43 | Document outline opened and docked in bottom-left corner: | |
| 44 | ||
| 45 |  | |
| 46 | ||
| 1 | 47 |
| 1 | # Skins | |
| 2 | ||
| 3 | The application provides bundled skins and the ability to add custom | |
| 4 | skins. This document describes the interplay between bundled skins | |
| 5 | and building your own look and feel. | |
| 6 | ||
| 7 | A skin is a set of styles, similar to cascading style sheet classes, | |
| 8 | that configures the user interface colours, fonts, spacing, highlights, | |
| 9 | drop-shadows, gradients, and more. | |
| 10 | ||
| 11 | For more information on CSS, see the [W3C CSS tutorial](https://www.w3.org/Style/Examples/011/firstcss). | |
| 12 | ||
| 13 | # Order | |
| 14 | ||
| 15 | The order that stylesheets are applied matters so that stylesheets can | |
| 16 | override styles defined previously. The application's user interface | |
| 17 | is made up of the following stylesheets, applied in the order listed: | |
| 18 | ||
| 19 | * **scene.css** --- Defines toolbar styling. | |
| 20 | * **markdown.css** --- Defines text editor styling. | |
| 21 | * **skins/skin_name.css** --- Bundled skin selected in preferences. | |
| 22 | * **custom.css** --- User-defined file set in preferences. | |
| 23 | ||
| 24 | # Customization | |
| 25 | ||
| 26 | Create a custom skin as follows: | |
| 27 | ||
| 28 | 1. Start the application. | |
| 29 | 1. Click **File → New** to create a new file. | |
| 30 | 1. Click **File → Save As** to rename the file. | |
| 31 | 1. Save the file as `custom.css`. | |
| 32 | 1. Change the content to the following: | |
| 33 | ``` css | |
| 34 | .root { | |
| 35 | -fx-base: rgb( 30, 30, 30 ); | |
| 36 | -fx-background: -fx-base; | |
| 37 | } | |
| 38 | ``` | |
| 39 | ||
| 40 | Next, apply the skin as follows: | |
| 41 | ||
| 42 | 1. Click **Edit → Preferences** to open the preferences dialog. | |
| 43 | 1. Click **Skins** to view the available options. | |
| 44 | 1. Click **Browse** to select a custom file. | |
| 45 | 1. Browse to and select `custom.css`, created previously. | |
| 46 | 1. Click **Open**. | |
| 47 | 1. Click **Apply**. | |
| 48 | ||
| 49 | The user interface immediately changes to a dark mode. Continue: | |
| 50 | ||
| 51 | 1. Click **OK** to close the dialog. | |
| 52 | 1. Change the **rgb** numbers in **custom.css** from `30` to `60`. | |
| 53 | 1. Click **File → Save** to save the CSS file. | |
| 54 | ||
| 55 | The user interface immediately changes colour. | |
| 56 | ||
| 57 | # Classes | |
| 58 | ||
| 59 | When creating your own skin, there many classes that can be styled. The | |
| 60 | previous section showed how to set up a rudimentary skin. Instead, start | |
| 61 | with a template that already has a number of classes defined so that you | |
| 62 | can tweak them to your taste. Accomplish this as follows: | |
| 63 | ||
| 64 | 1. Visit the [skin](https://github.com/DaveJarvis/keenwrite/tree/master/src/main/resources/com/keenwrite/skins) repository directory | |
| 65 | 1. Click one of the files (e.g., `haunted_grey.css`). | |
| 66 | 1. Click **Raw**. | |
| 67 | 1. Copy the entire text. | |
| 68 | 1. Return to `custom.css`. | |
| 69 | 1. Delete the contents. | |
| 70 | 1. Paste the copied text. | |
| 71 | 1. Save the file. | |
| 72 | ||
| 73 | To see how the CSS styles are applied to the text editor, open | |
| 74 | [markdown.css](https://github.com/DaveJarvis/keenwrite/blob/master/src/main/resources/com/keenwrite/editor/markdown.css), which is also in the repository. | |
| 75 | ||
| 76 | # Modena | |
| 77 | ||
| 78 | The basic look used by the application is _Modena Light_. Typically we | |
| 79 | only need to override a few classes to completely change the application's | |
| 80 | look and feel. For a full listing of available styles see the OpenJDK's | |
| 81 | [Modena CSS file](https://github.com/openjdk/jfx/blob/master/modules/javafx.controls/src/main/resources/com/sun/javafx/scene/control/skin/modena/modena.css). | |
| 82 | ||
| 83 | # JavaFX CSS | |
| 84 | ||
| 85 | The [Java CSS Reference Guide](https://openjfx.io/javadoc/11/javafx.graphics/javafx/scene/doc-files/cssref.html) is exhaustive. In addition to showing many | |
| 86 | differences between JavaFX CSS and W3C CSS, the guide introduces numerous | |
| 87 | helpful functions for manipulating colours and gradients using existing | |
| 88 | colour definitions. | |
| 89 | ||
| 90 | # RichTextFX | |
| 91 | ||
| 92 | The application uses RichTextFX to render the text editor. Styling various | |
| 93 | text editor classes can require using the prefix `-rtfx` instead of the | |
| 94 | regular JavaFX `-fx`. | |
| 95 | ||
| 96 | # Submit | |
| 97 | ||
| 98 | If you have a look that you'd like to contribute to the project, do pass | |
| 99 | it along. Either open a new issue in the [issue tracker](https://github.com/DaveJarvis/keenwrite/issues) that contains the CSS file or submit a pull request. | |
| 100 | ||
| 1 | 101 |
| 1 | # Introduction | |
| 2 | ||
| 3 | The Scalable Vector Graphics (SVG) drawing software---[Batik](https://xmlgraphics.apache.org/batik/)---that's used by the application may be unable to read certain SVG files produced by [Inkscape](https://inkscape.org/). The result is that embedding the vector graphics files may trigger the following issues: | |
| 4 | ||
| 5 | * Unable to create nested element | |
| 6 | * Black blocks, no text displayed | |
| 7 | * Black text instead of coloured | |
| 8 | ||
| 9 | The remainder of this document explains these problems and how to fix them. | |
| 10 | ||
| 11 | # Nested element | |
| 12 | ||
| 13 | When referencing a vector graphic using Markdown, the status bar may show the following error: | |
| 14 | ||
| 15 | > The current document is unable to create an element of the requested type (namespace: http://www.w3.org/2000/svg, name: flowRoot). | |
| 16 | ||
| 17 | This error is due to a version mismatch of the `flowRoot` element that Inkscape creates. | |
| 18 | ||
| 19 | ## Fix | |
| 20 | ||
| 21 | Resolve the issue by changing the SVG version number as follows: | |
| 22 | ||
| 23 | 1. Edit the vector graphics file using any text editor. | |
| 24 | 1. Find `version="1.1"` and change it to `version="1.2"`. | |
| 25 | 1. Save the file. | |
| 26 | ||
| 27 | The SVG will now appear inside the application; however, the text may appear as black blocks. | |
| 28 | ||
| 29 | # Black blocks | |
| 30 | ||
| 31 | Depending on how text is added to a vector graphic in Inkscape, the text may be inserted within an element called a `flowRoot`. Although Batik recognizes `flowRoot` for SVG version 1.2, it cannot fully interpret the contents. Black blocks are drawn instead of the text, such as those depicted in the following figure: | |
| 32 | ||
| 33 |  | |
| 34 | ||
| 35 | ## Fix | |
| 36 | ||
| 37 | Resolve the issue by "unflowing" all text elements as follows: | |
| 38 | ||
| 39 | 1. Start Inkscape. | |
| 40 | 1. Load the SVG file. | |
| 41 | 1. Select all the text elements. | |
| 42 | 1. Click **Text → Unflow**. | |
| 43 | ||
| 44 | The text may change size and position; recreate the text without dragging using the text tool. After all the text areas have been recreated, continue as follows: | |
| 45 | ||
| 46 | 1. Click **Edit → XML Editor**. | |
| 47 | 1. Expand the **XML Editor** to see more elements. | |
| 48 | 1. Delete all elements named `svg:flowRoot`. | |
| 49 | 1. Save the file. | |
| 50 | ||
| 51 | When the illustration is reloaded, the black blocks will have disappeared, but the text elements ignore any assigned colour. | |
| 52 | ||
| 53 | # Black text | |
| 54 | ||
| 55 | When an SVG `style` attribute contains a reference to `-inkscape-font-specification`, Batik ignores all values that follow said reference. This results in black text, such as: | |
| 56 | ||
| 57 |  | |
| 58 | ||
| 59 | ## Fix | |
| 60 | ||
| 61 | Resolve the issue of colourless text as follows: | |
| 62 | ||
| 63 | 1. Open the SVG file in a plain text editor. | |
| 64 | 1. Remove all references `-inkscape-font-specification:'<FONT>';`, including the trailing (or leading) semicolon. | |
| 65 | 1. Save the file. | |
| 66 | ||
| 67 | When the illustration is reloaded, the colours will have reappeared, such as: | |
| 68 | ||
| 69 |  | |
| 70 | ||
| 1 | 71 |
| 1 | # Overview | |
| 2 | ||
| 3 | Typesetting PDF files entails the following: | |
| 4 | ||
| 5 | * Download and install typesetting software | |
| 6 | * Download a theme pack | |
| 7 | ||
| 8 | These are described in the subsequent sections. Once the requirements have been met, continue reading to learn how to typeset a document. | |
| 9 | ||
| 10 | # Download typesetter | |
| 11 | ||
| 12 | Download the typesetting software as follows: | |
| 13 | ||
| 14 | 1. Start the text editor. | |
| 15 | 1. Click **File → Export As → PDF**. | |
| 16 | * Note the following details (e.g., Windows X86 64-bit): | |
| 17 | * operating system name; | |
| 18 | * instruction set; and | |
| 19 | * architecture. | |
| 20 | 1. Click the [link](https://wiki.contextgarden.net/Installation) in the dialog. | |
| 21 | 1. Download the appropriate archive file. | |
| 22 | ||
| 23 | # Install typesetter | |
| 24 | ||
| 25 | This section describes the installation steps for various platforms. Follow the steps that apply to the computer's operating system: | |
| 26 | ||
| 27 | * [Windows](#windows) (includes Windows 7, Windows 10, and similar) | |
| 28 | * [Unix](#unix) (includes MacOS, FreeBSD, Linux, and similar) | |
| 29 | ||
| 30 | ## Windows | |
| 31 | ||
| 32 | Proceed with a Windows installation of the typesetting software as follows: | |
| 33 | ||
| 34 | 1. Extract the `.zip` file into `C:\Users\%USERNAME%\AppData\Local\context` (the "root" directory) | |
| 35 | 1. Run **install.bat** to download and install the software. | |
| 36 | * If prompted, click **Run anyway** (or click **More info** first). | |
| 37 | 1. Right-click <a href="https://github.com/DaveJarvis/keenwrite/raw/master/scripts/localpath.bat">localpath.bat</a>. | |
| 38 | 1. Select **Save Link As** (or similar). | |
| 39 | 1. Save the file to the typesetting software's "root" directory. | |
| 40 | 1. Rename `localpath.bat.txt` to `localpath.bat`, if necessary. | |
| 41 | 1. Run `localpath.bat` (to set and save the `PATH` environment variable). | |
| 42 | ||
| 43 | Installation is complete. Verify the installation as follows: | |
| 44 | ||
| 45 | 1. Type: `context --version` | |
| 46 | 1. Press `Enter`. | |
| 47 | ||
| 48 | If version information is displayed then the software is installed correctly. | |
| 49 | ||
| 50 | Continue by installing a [theme pack](#theme-pack). | |
| 51 | ||
| 52 | ## Unix | |
| 53 | ||
| 54 | For Linux, MacOS, FreeBSD, and similar operating systems, proceed as follows: | |
| 55 | ||
| 56 | 1. Create `$HOME/.local/bin/context` | |
| 57 | 1. Extract the `.zip` file within `$HOME/.local/bin/context` | |
| 58 | 1. Run `sh install.sh` | |
| 59 | 1. Add `export PATH=$PATH:$HOME/.local/bin/context/tex/texmf-linux-64/bin` to the login script. | |
| 60 | ||
| 61 | Installation is complete. Verify the installation as follows: | |
| 62 | ||
| 63 | 1. Open a new terminal (to export the new PATH setting). | |
| 64 | 1. Type: `context --version` | |
| 65 | 1. Press `Enter`. | |
| 66 | ||
| 67 | If version information is displayed then the software is installed correctly. | |
| 68 | ||
| 69 | Continue by installing a [theme pack](#theme-pack). | |
| 70 | ||
| 71 | # Theme pack | |
| 72 | ||
| 73 | A theme pack is a set of themes that define how documents appear when typeset. Broadly, themes are applied as follows: | |
| 74 | ||
| 75 | * Install a theme pack | |
| 76 | * Configure individual themes | |
| 77 | ||
| 78 | ## Install theme pack | |
| 79 | ||
| 80 | Install and configure the default theme pack as follows: | |
| 81 | ||
| 82 | 1. Download the <a href="https://gitreleases.dev/gh/DaveJarvis/keenwrite-themes/latest/theme-pack.zip">theme-pack.zip</a> archive. | |
| 83 | 1. Extract archive into a known location. | |
| 84 | 1. Start the text editor, if not already running. | |
| 85 | 1. Click **Edit → Preferences**. | |
| 86 | 1. Click **Typesetting**. | |
| 87 | 1. Click **Browse** beside **Themes**. | |
| 88 | 1. Navigate to the `themes` directory. | |
| 89 | 1. Click **Open**. | |
| 90 | 1. Click **OK**. | |
| 91 | ||
| 92 | The theme pack is installed. | |
| 93 | ||
| 94 | Each theme has its own requirements, described below. | |
| 95 | ||
| 96 | ## Configure Boschet theme | |
| 97 | ||
| 98 | Download and install the following font families: | |
| 99 | ||
| 100 | * [Libre Baskerville](https://fonts.google.com/specimen/Libre+Baskerville) | |
| 101 | * [Archivo Narrow](https://fonts.google.com/specimen/Archivo+Narrow) | |
| 102 | * [Inconsolata](https://fonts.google.com/specimen/Inconsolata) | |
| 103 | ||
| 104 | The theme is configured. | |
| 105 | ||
| 106 | # Typeset single document | |
| 107 | ||
| 108 | Typeset a document as follows: | |
| 109 | ||
| 110 | 1. Start the text editor, if not already running. | |
| 111 | 1. Click **File → New** (or type `Ctrl+n`). | |
| 112 | 1. Type in some text. | |
| 113 | 1. Click **File → Export As → PDF** (or type `Ctrl+p`). | |
| 114 | 1. Select a theme from the drop-down list. | |
| 115 | 1. Click **OK** (or press `Enter`). | |
| 116 | 1. Set the **File name** to the PDF file name. | |
| 117 | 1. Click **Save**. | |
| 118 | ||
| 119 | The document is typeset; open the PDF file in a PDF reader to view the result. | |
| 120 | ||
| 121 | # Typeset multiple documents | |
| 122 | ||
| 123 | Typeset multiple documents similar to single documents, with one difference: | |
| 124 | ||
| 125 | * Click **File → Export As → Joined PDF** (or type `Ctrl+Shift+p`). | |
| 126 | ||
| 127 | All documents having the same file name extension in the same directory | |
| 128 | (or sub-directories) as the actively edited file are first concatenated then | |
| 129 | typeset into a single PDF document. The order that files are concatenated | |
| 130 | is numeric and alphabetic. | |
| 131 | ||
| 132 | For example, if `1.Rmd` is a sibling of the following files in the same | |
| 133 | directory, then all the files will be included in the PDF, as expected: | |
| 134 | ||
| 135 | chapter_1.Rmd | |
| 136 | chapter_2.Rmd | |
| 137 | chapter_2a.Rmd | |
| 138 | chapter_2b.Rmd | |
| 139 | chapter_3.Rmd | |
| 140 | chapter_10.Rmd | |
| 141 | ||
| 142 | Basically, sorting honours numbers and letters in file names. | |
| 143 | ||
| 144 | # Background | |
| 145 | ||
| 146 | This text editor helps keep content separated from presentation. Plain text documents will remain readable long after proprietary formats have become obsolete. However, we've come to expect much more in what we read than mere text: from hyperlinked tables of contents to indexes, from footers to footnotes, from mathematical expressions to complex graphics, modern documents are nuanced and multifaceted. | |
| 147 | ||
| 148 | ## History | |
| 149 | ||
| 150 | Before computer-based typesetting, much of mathematics was put to page by hand. Professional typesetters, who were often expensive and usually not mathematicians, would inadvertently introduce typographic errors into equations. Phototypesetting technology improved upon hand-typesetting, but well-known computer scientist Donald Knuth---whose third volume of *The Art of Computer Programming* was phototypeset in 1976---expressed dissatisfaction with its typographic quality. He set himself two goals: let anyone create high-quality books without much effort and provide software that typesets consistently on all capable computers. Two years later, he released a typesetting system and a font description language: TeX and METAFONT, respectively. | |
| 151 | ||
| 152 | In short, TeX is software that helps typeset plain text documents. | |
| 153 | ||
| 154 | ## ConTeXt | |
| 155 | ||
| 156 | Programming computers to typeset internationalized text automatically at the level we've become accustomed takes decades of development effort. Many free and open source software solutions can typeset text, including: ConTeXt, LaTeX, Sile, and others. ConTeXt, which builds upon TeX, is ideal for typesetting plain text into beautiful documents because it is developed with a notion of *setups*. These setups can wholly describe how text is to be typeset and---by being external to the text itself---configuring setups provides ample control over the document's final appearance without changing the prose. | |
| 157 | ||
| 158 | # Further reading | |
| 159 | ||
| 160 | Here are a few documents that introduce the typesetting system: | |
| 161 | ||
| 162 | * *What is ConTeXt?* ([English](https://www.pragma-ade.com/general/manuals/what-is-context.pdf)) | |
| 163 | * *A not so short introduction to ConTeXt* ([English](https://github.com/contextgarden/not-so-short-introduction-to-context/raw/main/en/introCTX_eng.pdf) or [Spanish](https://raw.githubusercontent.com/contextgarden/not-so-short-introduction-to-context/main/es/introCTX_esp.pdf)) | |
| 164 | * *Dealing with XML in ConTeXt MKIV* ([English](https://pragma-ade.com/general/manuals/xml-mkiv.pdf)) | |
| 165 | * *Typographic Programming* ([English](https://www.pragma-ade.com/general/manuals/style.pdf)) | |
| 166 | ||
| 167 | The [documentation library](https://wiki.contextgarden.net/Documentation) includes the following gems: | |
| 168 | ||
| 169 | * [ConTeXt Manual](https://www.pragma-ade.nl/general/manuals/ma-cb-en.pdf) | |
| 170 | * [ConTeXt command reference](https://www.pragma-ade.nl/general/qrcs/setup-en.pdf) | |
| 171 | * [METAFUN Manual](https://www.pragma-ade.nl/general/manuals/metafun-p.pdf) | |
| 172 | * [It's in the Details](https://www.pragma-ade.nl/general/manuals/details.pdf) | |
| 173 | * [Fonts out of ConTeXt](https://www.pragma-ade.com/general/manuals/fonts-mkiv.pdf) | |
| 174 | ||
| 175 | Expert-level documentation includes the [LuaTeX Reference Manual](https://www.pragma-ade.nl/general/manuals/luatex.pdf). | |
| 176 | ||
| 1 | 177 |
| 1 | # Introduction | |
| 2 | ||
| 3 | This document describes how to use the application. | |
| 4 | ||
| 5 | # Variable definitions | |
| 6 | ||
| 7 | Variable definitions provide a way to insert key names having associated values into a document. The variable names and values are declared inside an external file using the [YAML](http://www.yaml.org/) file format. Simply put, variables are written in the file as follows: | |
| 8 | ||
| 9 | ``` | |
| 10 | key: value | |
| 11 | ``` | |
| 12 | ||
| 13 | Any number of variables can be defined, in any order: | |
| 14 | ||
| 15 | ``` | |
| 16 | key_1: Value 1 | |
| 17 | key_2: Value 2 | |
| 18 | ``` | |
| 19 | ||
| 20 | Variables can reference other variables by bookending the key name within symbols: | |
| 21 | ||
| 22 | ``` | |
| 23 | key: Value | |
| 24 | key_1: {{key}} 1 | |
| 25 | key_2: {{key}} 2 | |
| 26 | ``` | |
| 27 | ||
| 28 | Variables can use a nested structure to help group related information: | |
| 29 | ||
| 30 | ``` | |
| 31 | novel: | |
| 32 | title: Book Title | |
| 33 | author: Author Name | |
| 34 | isbn: 978-3-16-148410-0 | |
| 35 | ``` | |
| 36 | ||
| 37 | Use a period to reference nested keys, such as: | |
| 38 | ||
| 39 | ``` | |
| 40 | novel: | |
| 41 | author: Author Name | |
| 42 | copyright: | |
| 43 | owner: {{novel.author}} | |
| 44 | ``` | |
| 45 | ||
| 46 | Save the variable definitions in a file having an extension of `.yaml` or `.yml`. | |
| 47 | ||
| 48 | # Document editing | |
| 49 | ||
| 50 | The application's purpose is to completely separate the document's content from its presentation. To achieve this, documents are composed using a [plain text](http://spec.commonmark.org/0.28/) format. | |
| 51 | ||
| 52 | ## Create document | |
| 53 | ||
| 54 | Start a new document as follows: | |
| 55 | ||
| 56 | 1. Start the application. | |
| 57 | 1. Click **File → New** to create an empty document to edit. | |
| 58 | 1. Click **File → Open** to open a variable definition file. | |
| 59 | 1. Change **Source Files** to **Variable Files** to list variable definition files. | |
| 60 | 1. Browse to and select a file saved with a `.yaml` or `.yml` extension. | |
| 61 | 1. Click **Open**. | |
| 62 | ||
| 63 | The variable definitions appear in the variable definition pane under the heading of **Variables**. | |
| 64 | ||
| 65 | ## Edit document | |
| 66 | ||
| 67 | Edit the document as normal. Notice how the preview pane updates as new content is added. The toolbar shows various icons that perform different formatting operations. Try them to see how they appear in the preview pane. Other operations not shown on the toolbar include: | |
| 68 | ||
| 69 | * Struck text (enclose the words within `~~` and `~~`) | |
| 70 | * Horizontal rule (use `---` on an otherwise empty line). | |
| 71 | ||
| 72 | The preview pane shows one way to interpret and format the document, but many other presentations are possible. | |
| 73 | ||
| 74 | ## Insert variable | |
| 75 | ||
| 76 | Let's assume that the variable definitions loaded into the application include: | |
| 77 | ||
| 78 | ``` | |
| 79 | novel: | |
| 80 | title: Diary of {{novel.author}} | |
| 81 | author: Anne Frank | |
| 82 | ``` | |
| 83 | ||
| 84 | To reference a variable, type in the key name enclosed within double braces, such as: | |
| 85 | ||
| 86 | ``` | |
| 87 | The novel "{{novel.title}}" is one of the most widely read books in the world. | |
| 88 | ``` | |
| 89 | ||
| 90 | The preview pane shows: | |
| 91 | ||
| 92 | > The novel "Diary of Anne Frank" is one of the most widely read books in the world. | |
| 93 | ||
| 94 | As it is laborious to type in variable names, it is possible to inject the variable name using autocomplete. Accomplish this as follows: | |
| 95 | ||
| 96 | 1. Create a new file. | |
| 97 | 1. Type in a partial variable value, such as **Dia**. | |
| 98 | 1. Press `Ctrl+Space` (hold down the `Control` key and tap the spacebar). | |
| 99 | ||
| 100 | The editor shows: | |
| 101 | ||
| 102 | ``` | |
| 103 | {{novel.title}} | |
| 104 | ``` | |
| 105 | ||
| 106 | The preview pane shows: | |
| 107 | ||
| 108 | ``` | |
| 109 | Diary of Anne Frank | |
| 110 | ``` | |
| 111 | ||
| 112 | The variable name is inserted into the document and the preview pane shows the variable's value. | |
| 113 | ||
| 1 | 114 |
| 1 | *.avi | |
| 2 | *.wav | |
| 3 | *.png | |
| 4 | *.mp4 | |
| 5 | *.mp3 | |
| 6 | ||
| 1 | 7 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 4 | xmlns:cc="http://creativecommons.org/ns#" | |
| 5 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 6 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 7 | xmlns="http://www.w3.org/2000/svg" | |
| 8 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 9 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 10 | width="211.87125mm" | |
| 11 | height="56.576mm" | |
| 12 | viewBox="0 0 211.87125 56.576" | |
| 13 | version="1.1" | |
| 14 | id="svg8" | |
| 15 | inkscape:version="1.0 (4035a4fb49, 2020-05-01)" | |
| 16 | sodipodi:docname="traced-text.svg"> | |
| 17 | <defs | |
| 18 | id="defs2" /> | |
| 19 | <sodipodi:namedview | |
| 20 | id="base" | |
| 21 | pagecolor="#ffffff" | |
| 22 | bordercolor="#666666" | |
| 23 | borderopacity="1.0" | |
| 24 | inkscape:pageopacity="0.0" | |
| 25 | inkscape:pageshadow="2" | |
| 26 | inkscape:zoom="1.4142136" | |
| 27 | inkscape:cx="367.6429" | |
| 28 | inkscape:cy="129.23348" | |
| 29 | inkscape:document-units="mm" | |
| 30 | inkscape:current-layer="layer1" | |
| 31 | inkscape:document-rotation="0" | |
| 32 | showgrid="false" | |
| 33 | fit-margin-top="10" | |
| 34 | fit-margin-left="10" | |
| 35 | fit-margin-right="10" | |
| 36 | fit-margin-bottom="10" /> | |
| 37 | <metadata | |
| 38 | id="metadata5"> | |
| 39 | <rdf:RDF> | |
| 40 | <cc:Work | |
| 41 | rdf:about=""> | |
| 42 | <dc:format>image/svg+xml</dc:format> | |
| 43 | <dc:type | |
| 44 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |
| 45 | <dc:title></dc:title> | |
| 46 | </cc:Work> | |
| 47 | </rdf:RDF> | |
| 48 | </metadata> | |
| 49 | <g | |
| 50 | inkscape:label="Layer 1" | |
| 51 | inkscape:groupmode="layer" | |
| 52 | id="layer1" | |
| 53 | transform="translate(-1.4263456,-106.05539)"> | |
| 54 | <text | |
| 55 | xml:space="preserve" | |
| 56 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.8px;line-height:1.25;font-family:'Alex Brush';-inkscape-font-specification:'Alex Brush, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583" | |
| 57 | x="12.289946" | |
| 58 | y="147.80539" | |
| 59 | id="text835"><tspan | |
| 60 | sodipodi:role="line" | |
| 61 | id="tspan833" | |
| 62 | x="12.289946" | |
| 63 | y="147.80539" | |
| 64 | style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:50.8px;font-family:'Alex Brush';-inkscape-font-specification:'Alex Brush, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583">Scrivenvar</tspan></text> | |
| 65 | <path | |
| 66 | sodipodi:nodetypes="cssssc" | |
| 67 | id="path859" | |
| 68 | d="m 47.37594,126.25759 c 5.878995,0.58684 8.108819,-2.8906 6.991897,-5.39049 -4.163299,-9.31827 -26.104298,-1.57165 -26.47428,4.67958 -0.290066,4.90098 4.329286,5.69691 9.138161,6.81221 4.75698,1.10326 9.980125,1.72503 10.138085,4.5281 0.511551,9.07772 -11.28247,13.50974 -21.577969,13.14767" | |
| 69 | style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#4eb059;stroke-width:0.132292;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 70 | <path | |
| 71 | sodipodi:nodetypes="cssc" | |
| 72 | id="path861" | |
| 73 | d="m 61.538159,137.91416 c 8.229745,-12.05206 -9.227635,-1.22793 -10.272792,5.40306 -0.929347,5.89623 4.566953,5.63307 9.024721,2.11036 5.095939,-4.02702 8.706628,-8.11599 12.031905,-13.9409" | |
| 74 | style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#4eb059;stroke-width:0.132292;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 75 | <path | |
| 76 | sodipodi:nodetypes="ccssc" | |
| 77 | id="path863" | |
| 78 | d="m 72.321991,131.48668 c 3.834665,-5.91801 -1.131419,0.83402 0.75311,2.48796 2.189872,1.94816 6.580549,-2.11016 5.400159,-0.72958 -0.854851,0.99983 -9.857527,10.41157 -5.126492,13.80621 2.461609,1.76627 8.936925,-2.58857 11.751532,-5.5313" | |
| 79 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 80 | <path | |
| 81 | sodipodi:nodetypes="csssc" | |
| 82 | id="path963" | |
| 83 | d="m 85.1003,141.51997 c 0,0 6.754775,-9.24626 6.743495,-8.01563 -0.01328,1.44899 -5.040946,6.68411 -6.63123,10.08427 -0.90584,1.93677 -0.626402,4.68995 2.447111,4.25184 1.468017,-0.20926 5.212094,-2.44913 10.029682,-7.66684" | |
| 84 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 85 | <path | |
| 86 | sodipodi:nodetypes="csccc" | |
| 87 | id="path965" | |
| 88 | d="m 97.689357,140.17361 c 0,0 3.797813,-8.42805 4.594353,-7.95573 0.58723,0.34822 -6.526154,13.32545 -5.477472,14.50806 2.435753,1.7862 19.064212,-11.51107 15.563042,-16.73913 -0.73409,-1.34256 -3.18033,-1.99148 -3.18033,-1.99148" | |
| 89 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 90 | <path | |
| 91 | sodipodi:nodetypes="csssc" | |
| 92 | d="m 113.37707,141.34636 c 4.23091,0.29831 11.94363,-4.90618 10.94354,-7.7799 -1.29105,-3.70978 -8.05529,1.78774 -9.69006,3.68511 -4.97668,5.77609 -4.11733,10.31478 -0.92228,10.61275 3.436,0.32045 8.83724,-3.13085 13.69698,-9.62574" | |
| 93 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 94 | id="path967" /> | |
| 95 | <path | |
| 96 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 97 | d="m 146.49943,140.17361 c 0,0 3.79781,-8.42805 4.59435,-7.95573 0.58723,0.34822 -6.52616,13.32545 -5.47747,14.50806 2.43575,1.7862 19.06421,-11.51107 15.56304,-16.73913 -0.73409,-1.34256 -3.10123,-1.96263 -3.10123,-1.96263" | |
| 98 | id="path970" | |
| 99 | sodipodi:nodetypes="csccc" /> | |
| 100 | <path | |
| 101 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 102 | d="m 188.80833,131.36316 c 3.83466,-5.91801 -1.13142,0.83402 0.75311,2.48796 2.18987,1.94816 6.58055,-2.11016 5.40016,-0.72958 -0.85485,0.99983 -9.98962,10.60367 -5.12649,13.80621 2.8329,1.86556 9.63808,-2.25455 13.61435,-8.05051" | |
| 103 | id="path987" | |
| 104 | sodipodi:nodetypes="ccssc" /> | |
| 105 | <path | |
| 106 | sodipodi:nodetypes="ccsssccc" | |
| 107 | d="m 127.40525,138.23858 c 1.53961,-1.23511 5.06979,-6.4876 5.94375,-5.82833 -1.7832,2.5949 -8.95273,13.68991 -7.1105,13.94503 1.19011,0.16482 7.25976,-8.00422 10.87675,-10.901 1.83151,-1.46682 4.35069,-3.49971 5.94917,-3.73267 1.66376,-0.24247 -1.93803,2.90472 -3.80099,5.77097 -1.36327,2.14988 -4.92421,8.02816 -2.69839,9.35481 3.0826,1.21137 7.35116,-4.27566 9.93439,-6.67382" | |
| 108 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" | |
| 109 | id="path989" /> | |
| 110 | <path | |
| 111 | sodipodi:nodetypes="csscsc" | |
| 112 | id="path992" | |
| 113 | d="m 176.85645,132.78853 c -3.26879,-6.24001 -16.43513,7.99373 -16.14879,12.14556 0.1378,1.99804 2.16776,3.14653 3.8818,2.44798 4.44909,-1.8132 11.93103,-13.58278 13.4413,-14.18515 -6.97685,9.84354 -7.04537,13.29844 -4.02229,13.83262 2.49715,0.44125 8.94275,-6.11484 14.79986,-15.66638" | |
| 114 | style="fill:none;fill-opacity:0.8;stroke:#4eb059;stroke-width:0.132292;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> | |
| 115 | </g> | |
| 116 | </svg> | |
| 1 | 117 |
| 1 | # Fonts | |
| 2 | ||
| 3 | For best results, it is recommended that the Noto Font family is installed | |
| 4 | on the system. The required font families include: | |
| 5 | ||
| 6 | * Sans-serif --- editor pane | |
| 7 | * Serif --- preview pane | |
| 8 | * Serif monospace --- preview pane | |
| 9 | ||
| 10 | # Chinese, Japanese, and Korean (CJK) | |
| 11 | ||
| 12 | Download and install from the following font bundles: | |
| 13 | ||
| 14 | * [Hong Kong](noto-hk.zip) | |
| 15 | * [Japanese](noto-jp.zip) | |
| 16 | * [Korean](noto-kr.zip) | |
| 17 | * [Simplified Chinese](noto-sc.zip) | |
| 18 | * [Traditional Chinese](noto-tc.zip) | |
| 19 | ||
| 20 | Except for Hong Kong, each bundle contains all the required font families; | |
| 21 | Hong Kong must be paired with the Simplified Chinese. | |
| 22 | ||
| 23 | The [official versions](https://www.google.com/get/noto/) of these fonts | |
| 24 | are updated regularly at the Noto Fonts | |
| 25 | [repository](https://github.com/googlefonts/noto-fonts/). If downloading | |
| 26 | from the original location, be sure to retrieve all font families needed | |
| 27 | for the application to render text correctly. | |
| 28 | ||
| 29 | # Internationalization | |
| 30 | ||
| 31 | Fonts for other languages may work but have not been tested. | |
| 32 | ||
| 1 | 33 |
| 1 | org.gradle.jvmargs=-Xmx1G -XX:MaxPermSize=512m | |
| 2 | org.gradle.daemon=true | |
| 3 | org.gradle.parallel=true | |
| 4 | ||
| 1 | 5 |
| 1 | <svg height='19pt' viewBox='0 0 25 19' width='25pt' xmlns='http://www.w3.org/2000/svg'><g fill='#454545'><path d='m8.042969 11.085938c.332031 1.445312 1.660156 2.503906 3.214843 2.558593zm0 0'/><path d='m6.792969 9.621094-.300781.226562.242187.195313c.015625-.144531.03125-.28125.058594-.421875zm0 0'/><path d='m10.597656.949219-2.511718.207031c-.777344.066406-1.429688.582031-1.636719 1.292969l-.367188 1.253906-3.414062.28125c-1.027344.085937-1.792969.949219-1.699219 1.925781l.976562 10.621094c.089844.976562.996094 1.699219 2.023438 1.613281l11.710938-.972656-3.117188-2.484375c-.246094.0625-.5.109375-.765625.132812-2.566406.210938-4.835937-1.597656-5.0625-4.039062-.023437-.25-.019531-.496094 0-.738281l-.242187-.195313.300781-.226562c.359375-1.929688 2.039062-3.472656 4.191406-3.652344.207031-.015625.414063-.015625.617187-.007812l.933594-.707032zm0 0'/><path d='m10.234375 11.070312 2.964844 2.820313c.144531.015625.285156.027344.433593.027344 1.890626 0 3.429688-1.460938 3.429688-3.257813 0-1.792968-1.539062-3.257812-3.429688-3.257812-1.890624 0-3.429687 1.464844-3.429687 3.257812 0 .140625.011719.277344.03125.410156zm0 0'/><path d='m14.488281.808594 1.117188 4.554687-1.042969.546875c2.25.476563 3.84375 2.472656 3.636719 4.714844-.199219 2.191406-2.050781 3.871094-4.285157 4.039062l2.609376 2.957032 4.4375.371094c1.03125.085937 1.9375-.640626 2.027343-1.617188l.976563-10.617188c.089844-.980468-.667969-1.839843-1.699219-1.925781l-3.414063-.285156-.371093-1.253906c-.207031-.710938-.859375-1.226563-1.636719-1.289063zm0 0'/></g></svg> | |
| 1 | 2 |
| 1 | #!/usr/bin/env bash | |
| 2 | ||
| 3 | # --------------------------------------------------------------------------- | |
| 4 | # This script cross-compiles application launchers for different platforms. | |
| 5 | # | |
| 6 | # The application binaries are self-contained launchers that do not need | |
| 7 | # to be installed. | |
| 8 | # --------------------------------------------------------------------------- | |
| 9 | ||
| 10 | source $HOME/bin/build-template | |
| 11 | ||
| 12 | readonly APP_NAME=$(find "${SCRIPT_DIR}/src" -type f -name "settings.properties" -exec cat {} \; | grep "application.title=" | cut -d'=' -f2) | |
| 13 | readonly FILE_APP_JAR="${APP_NAME}.jar" | |
| 14 | ||
| 15 | # JDK 16 work-around until RichTextFX is fixed. | |
| 16 | # See: https://github.com/FXMisc/RichTextFX/issues/1013 | |
| 17 | readonly OPT_JAVA="--illegal-access=permit" | |
| 18 | ||
| 19 | ARG_JAVA_OS="linux" | |
| 20 | ARG_JAVA_ARCH="amd64" | |
| 21 | ARG_JAVA_VERSION="16.0.1" | |
| 22 | ARG_JAVA_UPDATE="9" | |
| 23 | ARG_JAVA_DIR="java" | |
| 24 | ||
| 25 | ARG_DIR_DIST="dist" | |
| 26 | ||
| 27 | FILE_DIST_EXEC="run.sh" | |
| 28 | ||
| 29 | ARG_PATH_DIST_JAR="${SCRIPT_DIR}/build/libs/${FILE_APP_JAR}" | |
| 30 | ||
| 31 | DEPENDENCIES=( | |
| 32 | "gradle,https://gradle.org" | |
| 33 | "warp-packer,https://github.com/dgiagio/warp" | |
| 34 | "tar,https://www.gnu.org/software/tar" | |
| 35 | "unzip,http://infozip.sourceforge.net" | |
| 36 | ) | |
| 37 | ||
| 38 | ARGUMENTS+=( | |
| 39 | "a,arch,Target operating system architecture (amd64)" | |
| 40 | "o,os,Target operating system (linux, windows, mac)" | |
| 41 | "u,update,Java update version number (${ARG_JAVA_UPDATE})" | |
| 42 | "v,version,Full Java version (${ARG_JAVA_VERSION})" | |
| 43 | ) | |
| 44 | ||
| 45 | ARCHIVE_EXT="tar.gz" | |
| 46 | ARCHIVE_APP="tar xf" | |
| 47 | APP_EXTENSION="bin" | |
| 48 | ||
| 49 | # --------------------------------------------------------------------------- | |
| 50 | # Generates | |
| 51 | # --------------------------------------------------------------------------- | |
| 52 | execute() { | |
| 53 | $do_configure_target | |
| 54 | $do_build | |
| 55 | $do_clean | |
| 56 | ||
| 57 | pushd "${ARG_DIR_DIST}" > /dev/null 2>&1 | |
| 58 | ||
| 59 | $do_extract_java | |
| 60 | $do_create_launch_script | |
| 61 | $do_copy_archive | |
| 62 | ||
| 63 | popd > /dev/null 2>&1 | |
| 64 | ||
| 65 | $do_create_launcher | |
| 66 | ||
| 67 | return 1 | |
| 68 | } | |
| 69 | ||
| 70 | # --------------------------------------------------------------------------- | |
| 71 | # Configure platform-specific commands and file names. | |
| 72 | # --------------------------------------------------------------------------- | |
| 73 | utile_configure_target() { | |
| 74 | if [ "${ARG_JAVA_OS}" = "windows" ]; then | |
| 75 | ARCHIVE_EXT="zip" | |
| 76 | ARCHIVE_APP="unzip -qq" | |
| 77 | FILE_DIST_EXEC="run.bat" | |
| 78 | APP_EXTENSION="exe" | |
| 79 | do_create_launch_script=utile_create_launch_script_windows | |
| 80 | fi | |
| 81 | } | |
| 82 | ||
| 83 | # --------------------------------------------------------------------------- | |
| 84 | # Build platform-specific überjar. | |
| 85 | # --------------------------------------------------------------------------- | |
| 86 | utile_build() { | |
| 87 | $log "Delete ${ARG_PATH_DIST_JAR}" | |
| 88 | rm -f "${ARG_PATH_DIST_JAR}" | |
| 89 | ||
| 90 | $log "Build application for ${ARG_JAVA_OS}" | |
| 91 | gradle clean jar -PtargetOs="${ARG_JAVA_OS}" | |
| 92 | } | |
| 93 | ||
| 94 | # --------------------------------------------------------------------------- | |
| 95 | # Purges the existing distribution directory to recreate the launcher. | |
| 96 | # This refreshes the JRE from the downloaded archive. | |
| 97 | # --------------------------------------------------------------------------- | |
| 98 | utile_clean() { | |
| 99 | $log "Recreate ${ARG_DIR_DIST}" | |
| 100 | rm -rf "${ARG_DIR_DIST}" | |
| 101 | mkdir -p "${ARG_DIR_DIST}" | |
| 102 | } | |
| 103 | ||
| 104 | # --------------------------------------------------------------------------- | |
| 105 | # Extract platform-specific Java Runtime Environment. This will download | |
| 106 | # and cache the required Java Runtime Environment for the target platform. | |
| 107 | # On subsequent runs, the cached version is used, instead of issuing another | |
| 108 | # download. | |
| 109 | # --------------------------------------------------------------------------- | |
| 110 | utile_extract_java() { | |
| 111 | $log "Extract Java" | |
| 112 | local -r java_vm="jre" | |
| 113 | local -r java_version="${ARG_JAVA_VERSION}+${ARG_JAVA_UPDATE}" | |
| 114 | local -r url_java="https://download.bell-sw.com/java/${java_version}/bellsoft-${java_vm}${java_version}-${ARG_JAVA_OS}-${ARG_JAVA_ARCH}-full.${ARCHIVE_EXT}" | |
| 115 | ||
| 116 | local -r file_java="${java_vm}-${java_version}-${ARG_JAVA_OS}-${ARG_JAVA_ARCH}.${ARCHIVE_EXT}" | |
| 117 | local -r path_java="/tmp/${file_java}" | |
| 118 | ||
| 119 | # File must have contents. | |
| 120 | if [ ! -s ${path_java} ]; then | |
| 121 | $log "Download ${url_java} to ${path_java}" | |
| 122 | wget -q "${url_java}" -O "${path_java}" | |
| 123 | fi | |
| 124 | ||
| 125 | $log "Unpack ${path_java}" | |
| 126 | $ARCHIVE_APP "${path_java}" | |
| 127 | ||
| 128 | local -r dir_java="${java_vm}-${ARG_JAVA_VERSION}-full" | |
| 129 | ||
| 130 | $log "Rename ${dir_java} to ${ARG_JAVA_DIR}" | |
| 131 | mv "${dir_java}" "${ARG_JAVA_DIR}" | |
| 132 | } | |
| 133 | ||
| 134 | # --------------------------------------------------------------------------- | |
| 135 | # Create Linux-specific launch script. | |
| 136 | # --------------------------------------------------------------------------- | |
| 137 | utile_create_launch_script_linux() { | |
| 138 | $log "Create Linux launch script" | |
| 139 | ||
| 140 | cat > "${FILE_DIST_EXEC}" << __EOT | |
| 141 | #!/usr/bin/env bash | |
| 142 | ||
| 143 | readonly SCRIPT_SRC="\$(dirname "\${BASH_SOURCE[\${#BASH_SOURCE[@]} - 1]}")" | |
| 144 | ||
| 145 | "\${SCRIPT_SRC}/${ARG_JAVA_DIR}/bin/java" ${OPT_JAVA} -jar "\${SCRIPT_SRC}/${FILE_APP_JAR}" "\$@" 2>/dev/null & | |
| 146 | __EOT | |
| 147 | ||
| 148 | chmod +x "${FILE_DIST_EXEC}" | |
| 149 | } | |
| 150 | ||
| 151 | # --------------------------------------------------------------------------- | |
| 152 | # Create Windows-specific launch script. | |
| 153 | # --------------------------------------------------------------------------- | |
| 154 | utile_create_launch_script_windows() { | |
| 155 | $log "Create Windows launch script" | |
| 156 | ||
| 157 | cat > "${FILE_DIST_EXEC}" << __EOT | |
| 158 | @echo off | |
| 159 | ||
| 160 | set SCRIPT_DIR=%~dp0 | |
| 161 | "%SCRIPT_DIR%\\${ARG_JAVA_DIR}\\bin\\java" ${OPT_JAVA} -jar "%SCRIPT_DIR%\\${APP_NAME}.jar" %* 2>nul | |
| 162 | __EOT | |
| 163 | ||
| 164 | # Convert Unix end of line characters (\n) to Windows format (\r\n). | |
| 165 | # This avoids any potential line conversion issues with the repository. | |
| 166 | sed -i 's/$/\r/' "${FILE_DIST_EXEC}" | |
| 167 | } | |
| 168 | ||
| 169 | # --------------------------------------------------------------------------- | |
| 170 | # Copy application überjar. | |
| 171 | # --------------------------------------------------------------------------- | |
| 172 | utile_copy_archive() { | |
| 173 | $log "Create copy of ${FILE_APP_JAR}" | |
| 174 | cp "${ARG_PATH_DIST_JAR}" "${FILE_APP_JAR}" | |
| 175 | } | |
| 176 | ||
| 177 | # --------------------------------------------------------------------------- | |
| 178 | # Create platform-specific launcher binary. | |
| 179 | # --------------------------------------------------------------------------- | |
| 180 | utile_create_launcher() { | |
| 181 | local -r FILE_APP_NAME="${APP_NAME}.${APP_EXTENSION}" | |
| 182 | $log "Create ${FILE_APP_NAME}" | |
| 183 | ||
| 184 | # Warp-packer does not seem to overwrite the file. | |
| 185 | rm -f "${FILE_APP_NAME}" | |
| 186 | ||
| 187 | # Download uses amd64, but warp-packer differs. | |
| 188 | if [ "${ARG_JAVA_ARCH}" = "amd64" ]; then | |
| 189 | ARG_JAVA_ARCH="x64" | |
| 190 | fi | |
| 191 | ||
| 192 | warp-packer \ | |
| 193 | --arch "${ARG_JAVA_OS}-${ARG_JAVA_ARCH}" \ | |
| 194 | --input_dir "${ARG_DIR_DIST}" \ | |
| 195 | --exec "${FILE_DIST_EXEC}" \ | |
| 196 | --output "${FILE_APP_NAME}" > /dev/null | |
| 197 | ||
| 198 | chmod +x "${FILE_APP_NAME}" | |
| 199 | } | |
| 200 | ||
| 201 | argument() { | |
| 202 | local consume=2 | |
| 203 | ||
| 204 | case "$1" in | |
| 205 | -a|--arch) | |
| 206 | ARG_JAVA_ARCH="$2" | |
| 207 | ;; | |
| 208 | -o|--os) | |
| 209 | ARG_JAVA_OS="$2" | |
| 210 | ;; | |
| 211 | -u|--update) | |
| 212 | ARG_JAVA_UPDATE="$2" | |
| 213 | ;; | |
| 214 | -v|--version) | |
| 215 | ARG_JAVA_VERSION="$2" | |
| 216 | ;; | |
| 217 | esac | |
| 218 | ||
| 219 | return ${consume} | |
| 220 | } | |
| 221 | ||
| 222 | do_configure_target=utile_configure_target | |
| 223 | do_build=utile_build | |
| 224 | do_clean=utile_clean | |
| 225 | do_extract_java=utile_extract_java | |
| 226 | do_create_launch_script=utile_create_launch_script_linux | |
| 227 | do_copy_archive=utile_copy_archive | |
| 228 | do_create_launcher=utile_create_launcher | |
| 229 | ||
| 230 | main "$@" | |
| 231 | ||
| 1 | 232 |
| 1 | #!/usr/bin/env bash | |
| 2 | ||
| 3 | java --illegal-access=permit -jar build/libs/keenwrite.jar 2> /dev/null | |
| 4 | ||
| 1 | 5 |
| 1 | #!/usr/bin/env bash | |
| 2 | ||
| 3 | # --------------------------------------------------------------------------- | |
| 4 | # This script builds Windows, Linux, and Java archive binaries for a | |
| 5 | # release. | |
| 6 | # --------------------------------------------------------------------------- | |
| 7 | ||
| 8 | source $HOME/bin/build-template | |
| 9 | ||
| 10 | readonly FILE_PROPERTIES="${SCRIPT_DIR}/src/main/resources/bootstrap.properties" | |
| 11 | readonly BIN_INSTALLER="${SCRIPT_DIR}/installer.sh" | |
| 12 | ||
| 13 | DEPENDENCIES=( | |
| 14 | "gradle,https://gradle.org" | |
| 15 | "zip,http://infozip.sourceforge.net" | |
| 16 | "${FILE_PROPERTIES},File containing application name" | |
| 17 | ) | |
| 18 | ||
| 19 | execute() { | |
| 20 | $log "Build Windows installer binary" | |
| 21 | ${BIN_INSTALLER} -o windows | |
| 22 | ||
| 23 | $log "Build Linux installer binary" | |
| 24 | ${BIN_INSTALLER} -o linux | |
| 25 | ||
| 26 | $log "Build Java archive" | |
| 27 | gradle clean jar | |
| 28 | mv "build/libs/${application_title}.jar" . | |
| 29 | } | |
| 30 | ||
| 31 | preprocess() { | |
| 32 | while IFS='=' read -r key value; do | |
| 33 | if [[ "${key}" = "" || "${key}" = "#"* ]]; then | |
| 34 | continue | |
| 35 | fi | |
| 36 | ||
| 37 | key=$(echo $key | tr '.' '_') | |
| 38 | eval ${key}=\${value} | |
| 39 | done < "${FILE_PROPERTIES}" | |
| 40 | ||
| 41 | application_title="${application_title,,}" | |
| 42 | ||
| 43 | return 1 | |
| 44 | } | |
| 45 | ||
| 46 | main "$@" | |
| 47 | ||
| 1 | 48 |
| 1 | #!/usr/bin/env bash | |
| 2 | ||
| 3 | # ----------------------------------------------------------------------------- | |
| 4 | # Copyright 2020 Dave Jarvis | |
| 5 | # | |
| 6 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 7 | # copy of this software and associated documentation files (the | |
| 8 | # "Software"), to deal in the Software without restriction, including | |
| 9 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 10 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 11 | # permit persons to whom the Software is furnished to do so, subject to | |
| 12 | # the following conditions: | |
| 13 | # | |
| 14 | # The above copyright notice and this permission notice shall be included | |
| 15 | # in all copies or substantial portions of the Software. | |
| 16 | # | |
| 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 18 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 19 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 20 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 21 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 22 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 23 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | ||
| 26 | set -o errexit | |
| 27 | set -o nounset | |
| 28 | ||
| 29 | readonly SCRIPT_SRC="$(dirname "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}")" | |
| 30 | readonly SCRIPT_DIR="$(cd "${SCRIPT_SRC}" >/dev/null 2>&1 && pwd)" | |
| 31 | readonly SCRIPT_NAME=$(basename "$0") | |
| 32 | ||
| 33 | # ----------------------------------------------------------------------------- | |
| 34 | # The main entry point is responsible for parsing command-line arguments, | |
| 35 | # changing to the appropriate directory, and running all commands requested | |
| 36 | # by the user. | |
| 37 | # | |
| 38 | # $@ - Command-line arguments | |
| 39 | # ----------------------------------------------------------------------------- | |
| 40 | main() { | |
| 41 | arguments "$@" | |
| 42 | ||
| 43 | $usage && terminate 3 | |
| 44 | requirements && terminate 4 | |
| 45 | traps && terminate 5 | |
| 46 | ||
| 47 | directory && terminate 6 | |
| 48 | preprocess && terminate 7 | |
| 49 | execute && terminate 8 | |
| 50 | postprocess && terminate 9 | |
| 51 | ||
| 52 | terminate 0 | |
| 53 | } | |
| 54 | ||
| 55 | # ----------------------------------------------------------------------------- | |
| 56 | # Perform all commands that the script requires. | |
| 57 | # | |
| 58 | # @return 0 - Indicate to terminate the script with non-zero exit level | |
| 59 | # @return 1 - All tasks completed successfully (default) | |
| 60 | # ----------------------------------------------------------------------------- | |
| 61 | execute() { | |
| 62 | return 1 | |
| 63 | } | |
| 64 | ||
| 65 | # ----------------------------------------------------------------------------- | |
| 66 | # Changes to the script's working directory, provided it exists. | |
| 67 | # | |
| 68 | # @return 0 - Change directory failed | |
| 69 | # @return 1 - Change directory succeeded | |
| 70 | # ----------------------------------------------------------------------------- | |
| 71 | directory() { | |
| 72 | $log "Change directory" | |
| 73 | local result=1 | |
| 74 | ||
| 75 | # Track whether change directory failed. | |
| 76 | cd "${SCRIPT_DIR}" > /dev/null 2>&1 || result=0 | |
| 77 | ||
| 78 | return "${result}" | |
| 79 | } | |
| 80 | ||
| 81 | # ----------------------------------------------------------------------------- | |
| 82 | # Perform any initialization required prior to executing tasks. | |
| 83 | # | |
| 84 | # @return 0 - Preprocessing failed | |
| 85 | # @return 1 - Preprocessing succeeded | |
| 86 | # ----------------------------------------------------------------------------- | |
| 87 | preprocess() { | |
| 88 | $log "Preprocess" | |
| 89 | ||
| 90 | return 1 | |
| 91 | } | |
| 92 | ||
| 93 | # ----------------------------------------------------------------------------- | |
| 94 | # Perform any clean up required prior to executing tasks. | |
| 95 | # | |
| 96 | # @return 0 - Postprocessing failed | |
| 97 | # @return 1 - Postprocessing succeeded | |
| 98 | # ----------------------------------------------------------------------------- | |
| 99 | postprocess() { | |
| 100 | $log "Postprocess" | |
| 101 | ||
| 102 | return 1 | |
| 103 | } | |
| 104 | ||
| 105 | # ----------------------------------------------------------------------------- | |
| 106 | # Check that all required commands are available. | |
| 107 | # | |
| 108 | # @return 0 - At least one command is missing | |
| 109 | # @return 1 - All commands are available | |
| 110 | # ----------------------------------------------------------------------------- | |
| 111 | requirements() { | |
| 112 | $log "Verify requirements" | |
| 113 | local -r expected_count=${#DEPENDENCIES[@]} | |
| 114 | local total_count=0 | |
| 115 | ||
| 116 | # Verify that each command exists. | |
| 117 | for dependency in "${DEPENDENCIES[@]}"; do | |
| 118 | # Extract the command name [0] and URL [1]. | |
| 119 | IFS=',' read -ra dependent <<< "${dependency}" | |
| 120 | ||
| 121 | required "${dependent[0]}" "${dependent[1]}" | |
| 122 | total_count=$(( total_count + $? )) | |
| 123 | done | |
| 124 | ||
| 125 | unset IFS | |
| 126 | ||
| 127 | # Total dependencies found must match the expected number. | |
| 128 | # Integer-only division rounds down. | |
| 129 | return $(( total_count / expected_count )) | |
| 130 | } | |
| 131 | ||
| 132 | # ----------------------------------------------------------------------------- | |
| 133 | # Called before terminating the script. | |
| 134 | # ----------------------------------------------------------------------------- | |
| 135 | cleanup() { | |
| 136 | $log "Cleanup" | |
| 137 | } | |
| 138 | ||
| 139 | # ----------------------------------------------------------------------------- | |
| 140 | # Terminates the program immediately. | |
| 141 | # ----------------------------------------------------------------------------- | |
| 142 | trap_control_c() { | |
| 143 | $log "Interrupted" | |
| 144 | cleanup | |
| 145 | error "⯃" | |
| 146 | terminate 1 | |
| 147 | } | |
| 148 | ||
| 149 | # ----------------------------------------------------------------------------- | |
| 150 | # Configure signal traps. | |
| 151 | # | |
| 152 | # @return 1 - Signal traps are set. | |
| 153 | # ----------------------------------------------------------------------------- | |
| 154 | traps() { | |
| 155 | # Suppress echoing ^C if pressed. | |
| 156 | stty -echoctl | |
| 157 | trap trap_control_c INT | |
| 158 | ||
| 159 | return 1 | |
| 160 | } | |
| 161 | ||
| 162 | # ----------------------------------------------------------------------------- | |
| 163 | # Check for a required command. | |
| 164 | # | |
| 165 | # $1 - Command or file to check for existence | |
| 166 | # $2 - Command's website (e.g., download for binaries and source code) | |
| 167 | # | |
| 168 | # @return 0 - Command is missing | |
| 169 | # @return 1 - Command exists | |
| 170 | # ----------------------------------------------------------------------------- | |
| 171 | required() { | |
| 172 | local result=0 | |
| 173 | ||
| 174 | test -f "$1" || \ | |
| 175 | command -v "$1" > /dev/null 2>&1 && result=1 || \ | |
| 176 | warning "Missing: $1 ($2)" | |
| 177 | ||
| 178 | return ${result} | |
| 179 | } | |
| 180 | ||
| 181 | # ----------------------------------------------------------------------------- | |
| 182 | # Show acceptable command-line arguments. | |
| 183 | # | |
| 184 | # @return 0 - Indicate script may not continue | |
| 185 | # ----------------------------------------------------------------------------- | |
| 186 | utile_usage() { | |
| 187 | printf "Usage: %s [OPTIONS...]\n\n" "${SCRIPT_NAME}" >&2 | |
| 188 | ||
| 189 | # Number of spaces to pad after the longest long argument. | |
| 190 | local -r PADDING=2 | |
| 191 | ||
| 192 | # Determine the longest long argument to adjust spacing. | |
| 193 | local -r LEN=$(printf '%s\n' "${ARGUMENTS[@]}" | \ | |
| 194 | awk -F"," '{print length($2)+'${PADDING}'}' | sort -n | tail -1) | |
| 195 | ||
| 196 | local duplicates | |
| 197 | ||
| 198 | for argument in "${ARGUMENTS[@]}"; do | |
| 199 | # Extract the short [0] and long [1] arguments and description [2]. | |
| 200 | arg=("$(echo ${argument} | cut -d ',' -f1)" \ | |
| 201 | "$(echo ${argument} | cut -d ',' -f2)" \ | |
| 202 | "$(echo ${argument} | cut -d ',' -f3-)") | |
| 203 | ||
| 204 | duplicates+=("${arg[0]}") | |
| 205 | ||
| 206 | printf " -%s, --%-${LEN}s%s\n" "${arg[0]}" "${arg[1]}" "${arg[2]}" >&2 | |
| 207 | done | |
| 208 | ||
| 209 | # Sort the arguments to make sure no duplicates exist. | |
| 210 | duplicates=$(echo "${duplicates[@]}" | tr ' ' '\n' | sort | uniq -c -d) | |
| 211 | ||
| 212 | # Warn the developer that there's a duplicate command-line option. | |
| 213 | if [ -n "${duplicates}" ]; then | |
| 214 | # Trim all the whitespaces | |
| 215 | duplicates=$(echo "${duplicates}" | xargs echo -n) | |
| 216 | error "Duplicate command-line argument exists: ${duplicates}" | |
| 217 | fi | |
| 218 | ||
| 219 | return 0 | |
| 220 | } | |
| 221 | ||
| 222 | # ----------------------------------------------------------------------------- | |
| 223 | # Write coloured text to standard output. | |
| 224 | # | |
| 225 | # $1 - Text to write | |
| 226 | # $2 - Text's colour | |
| 227 | # ----------------------------------------------------------------------------- | |
| 228 | coloured_text() { | |
| 229 | printf "%b%s%b\n" "$2" "$1" "${COLOUR_OFF}" | |
| 230 | } | |
| 231 | ||
| 232 | # ----------------------------------------------------------------------------- | |
| 233 | # Write a warning message to standard output. | |
| 234 | # | |
| 235 | # $1 - Text to write | |
| 236 | # ----------------------------------------------------------------------------- | |
| 237 | warning() { | |
| 238 | coloured_text "$1" "${COLOUR_WARNING}" | |
| 239 | } | |
| 240 | ||
| 241 | # ----------------------------------------------------------------------------- | |
| 242 | # Write an error message to standard output. | |
| 243 | # | |
| 244 | # $1 - Text to write | |
| 245 | # ----------------------------------------------------------------------------- | |
| 246 | error() { | |
| 247 | coloured_text "$1" "${COLOUR_ERROR}" | |
| 248 | } | |
| 249 | ||
| 250 | # ----------------------------------------------------------------------------- | |
| 251 | # Write a timestamp and message to standard output. | |
| 252 | # | |
| 253 | # $1 - Text to write | |
| 254 | # ----------------------------------------------------------------------------- | |
| 255 | utile_log() { | |
| 256 | printf "[%s] " "$(date +%H:%M:%S.%4N)" | |
| 257 | coloured_text "$1" "${COLOUR_LOGGING}" | |
| 258 | } | |
| 259 | ||
| 260 | # ----------------------------------------------------------------------------- | |
| 261 | # Perform no operations. | |
| 262 | # | |
| 263 | # return 1 - Success | |
| 264 | # ----------------------------------------------------------------------------- | |
| 265 | noop() { | |
| 266 | return 1 | |
| 267 | } | |
| 268 | ||
| 269 | # ----------------------------------------------------------------------------- | |
| 270 | # Exit the program with a given exit code. | |
| 271 | # | |
| 272 | # $1 - Exit code | |
| 273 | # ----------------------------------------------------------------------------- | |
| 274 | terminate() { | |
| 275 | exit "$1" | |
| 276 | } | |
| 277 | ||
| 278 | # ----------------------------------------------------------------------------- | |
| 279 | # Set global variables from command-line arguments. | |
| 280 | # ----------------------------------------------------------------------------- | |
| 281 | arguments() { | |
| 282 | while [ "$#" -gt "0" ]; do | |
| 283 | local consume=1 | |
| 284 | ||
| 285 | case "$1" in | |
| 286 | -V|--verbose) | |
| 287 | log=utile_log | |
| 288 | ;; | |
| 289 | -h|-\?|--help) | |
| 290 | usage=utile_usage | |
| 291 | ;; | |
| 292 | *) | |
| 293 | set +e | |
| 294 | argument "$@" | |
| 295 | consume=$? | |
| 296 | set -e | |
| 297 | ;; | |
| 298 | esac | |
| 299 | ||
| 300 | shift ${consume} | |
| 301 | done | |
| 302 | } | |
| 303 | ||
| 304 | # ----------------------------------------------------------------------------- | |
| 305 | # Parses a single command-line argument. This must return a value greater | |
| 306 | # than or equal to 1, otherwise parsing the command-line arguments will | |
| 307 | # loop indefinitely. | |
| 308 | # | |
| 309 | # @return The number of arguments to consume (1 by default). | |
| 310 | # ----------------------------------------------------------------------------- | |
| 311 | argument() { | |
| 312 | return 1 | |
| 313 | } | |
| 314 | ||
| 315 | # ANSI colour escape sequences. | |
| 316 | readonly COLOUR_BLUE='\033[1;34m' | |
| 317 | readonly COLOUR_PINK='\033[1;35m' | |
| 318 | readonly COLOUR_DKGRAY='\033[30m' | |
| 319 | readonly COLOUR_DKRED='\033[31m' | |
| 320 | readonly COLOUR_LTRED='\033[1;31m' | |
| 321 | readonly COLOUR_YELLOW='\033[1;33m' | |
| 322 | readonly COLOUR_OFF='\033[0m' | |
| 323 | ||
| 324 | # Colour definitions used by script. | |
| 325 | COLOUR_LOGGING=${COLOUR_BLUE} | |
| 326 | COLOUR_WARNING=${COLOUR_YELLOW} | |
| 327 | COLOUR_ERROR=${COLOUR_LTRED} | |
| 328 | ||
| 329 | # Define required commands to check when script starts. | |
| 330 | DEPENDENCIES=( | |
| 331 | "awk,https://www.gnu.org/software/gawk/manual/gawk.html" | |
| 332 | "cut,https://www.gnu.org/software/coreutils" | |
| 333 | ) | |
| 334 | ||
| 335 | # Define help for command-line arguments. | |
| 336 | ARGUMENTS=( | |
| 337 | "V,verbose,Log messages while processing" | |
| 338 | "h,help,Show this help message then exit" | |
| 339 | ) | |
| 340 | ||
| 341 | # These functions may be set to utile delegates while parsing arguments. | |
| 342 | usage=noop | |
| 343 | log=noop | |
| 344 | ||
| 1 | 345 |
| 1 | #!/usr/bin/env bash | |
| 2 | ||
| 3 | # Outputs font names for all font files. | |
| 4 | ||
| 5 | find src/main/resources/fonts -type f \( -name "*otf" -o -name "*ttf" \) -exec \ | |
| 6 | fc-scan --format "%{foundry}: %{family}\n" {} \; | uniq | sort | |
| 7 | ||
| 1 | 8 |
| 1 | @echo off | |
| 2 | set "OWNPATH=%~dp0" | |
| 3 | set "PLATFORM=mswin" | |
| 4 | ||
| 5 | if defined ProgramFiles(x86) set "PLATFORM=win64" | |
| 6 | if "%PROCESSOR_ARCHITECTURE%"=="AMD64" set "PLATFORM=win64" | |
| 7 | if exist "%OWNPATH%tex\texmf-mswin\bin\context.exe" set "PLATFORM=mswin" | |
| 8 | if exist "%OWNPATH%tex\texmf-win64\bin\context.exe" set "PLATFORM=win64" | |
| 9 | ||
| 10 | set "TeXPath=%OWNPATH%tex\texmf-%PLATFORM%\bin" | |
| 11 | ||
| 12 | echo %PATH% | findstr "texmf-%PLATFORM%" > nul | |
| 13 | ||
| 14 | rem Only update the PATH if not previously updated | |
| 15 | if ERRORLEVEL 1 ( | |
| 16 | setlocal enabledelayedexpansion | |
| 17 | set "Exists=false" | |
| 18 | set "Key=HKCU\Environment" | |
| 19 | ||
| 20 | for /F "USEBACKQ tokens=2*" %%A in (`reg query %%Key%% /v PATH 2^>nul`) do ( | |
| 21 | if not "%%~B" == "" ( | |
| 22 | set "Exists=true" | |
| 23 | ||
| 24 | rem Preserve the existing PATH | |
| 25 | echo %%B > currpath.txt | |
| 26 | ||
| 27 | rem Change the PATH environment variable | |
| 28 | setx PATH "%%B;%TeXPath%" | |
| 29 | ) | |
| 30 | ) | |
| 31 | ||
| 32 | rem The user-defined PATH does not exist, create it | |
| 33 | if "!Exists!" == "false" ( | |
| 34 | rem Change the user PATH environment variable | |
| 35 | setx PATH "%TeXPath%" | |
| 36 | ) | |
| 37 | ||
| 38 | endlocal | |
| 39 | ||
| 40 | rem Update the current session | |
| 41 | set "PATH=%PATH%;%TeXPath%" | |
| 42 | ) | |
| 43 | ||
| 1 | 44 |
| 1 | 1 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.io.FileType; | |
| 5 | ||
| 6 | import java.nio.file.Path; | |
| 7 | ||
| 8 | import static com.keenwrite.constants.Constants.GLOB_PREFIX_FILE; | |
| 9 | import static com.keenwrite.constants.Constants.sSettings; | |
| 10 | import static com.keenwrite.io.FileType.UNKNOWN; | |
| 11 | import static com.keenwrite.predicates.PredicateFactory.createFileTypePredicate; | |
| 12 | ||
| 13 | /** | |
| 14 | * Provides common behaviours for factories that instantiate classes based on | |
| 15 | * file type. | |
| 16 | */ | |
| 17 | public abstract class AbstractFileFactory { | |
| 18 | ||
| 19 | /** | |
| 20 | * Determines the file type from the path extension. This should only be | |
| 21 | * called when it is known that the file type won't be a definition file | |
| 22 | * (e.g., YAML or other definition source), but rather an editable file | |
| 23 | * (e.g., Markdown, R Markdown, etc.). | |
| 24 | * | |
| 25 | * @param path The path with a file name extension. | |
| 26 | * @return The FileType for the given path. | |
| 27 | */ | |
| 28 | public static FileType lookup( final Path path ) { | |
| 29 | assert path != null; | |
| 30 | ||
| 31 | return lookup( path, GLOB_PREFIX_FILE ); | |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * Creates a file type that corresponds to the given path. | |
| 36 | * | |
| 37 | * @param path Reference to a variable definition file. | |
| 38 | * @param prefix One of GLOB_PREFIX_DEFINITION or GLOB_PREFIX_FILE. | |
| 39 | * @return The file type that corresponds to the given path. | |
| 40 | */ | |
| 41 | protected static FileType lookup( final Path path, final String prefix ) { | |
| 42 | assert path != null; | |
| 43 | assert prefix != null; | |
| 44 | ||
| 45 | final var keys = sSettings.getKeys( prefix ); | |
| 46 | ||
| 47 | var found = false; | |
| 48 | var fileType = UNKNOWN; | |
| 49 | ||
| 50 | while( keys.hasNext() && !found ) { | |
| 51 | final var key = keys.next(); | |
| 52 | final var patterns = sSettings.getStringSettingList( key ); | |
| 53 | final var predicate = createFileTypePredicate( patterns ); | |
| 54 | ||
| 55 | if( found = predicate.test( path.toFile() ) ) { | |
| 56 | // Remove the EXTENSIONS_PREFIX to get the file name extension mapped | |
| 57 | // to a standard name (as defined in the settings.properties file). | |
| 58 | final String suffix = key.replace( prefix + '.', "" ); | |
| 59 | fileType = FileType.from( suffix ); | |
| 60 | } | |
| 61 | } | |
| 62 | ||
| 63 | return fileType; | |
| 64 | } | |
| 65 | } | |
| 1 | 66 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.constants.Constants; | |
| 5 | ||
| 6 | import java.io.InputStream; | |
| 7 | import java.util.Calendar; | |
| 8 | import java.util.Properties; | |
| 9 | ||
| 10 | import static org.apache.batik.util.ParsedURL.setGlobalUserAgent; | |
| 11 | ||
| 12 | /** | |
| 13 | * Responsible for loading the bootstrap.properties file, which is | |
| 14 | * tactically located outside of the standard resource reverse domain name | |
| 15 | * namespace to avoid hard-coding the application name in many places. | |
| 16 | * Instead, the application name is located in the bootstrap file, which is | |
| 17 | * then used to look-up the remaining settings. | |
| 18 | * <p> | |
| 19 | * See {@link Constants#PATH_PROPERTIES_SETTINGS} for details. | |
| 20 | * </p> | |
| 21 | */ | |
| 22 | public final class Bootstrap { | |
| 23 | /** | |
| 24 | * Order matters, this must be populated before deriving the app title. | |
| 25 | */ | |
| 26 | private static final Properties P = new Properties(); | |
| 27 | ||
| 28 | static { | |
| 29 | try( final var in = openResource( "/bootstrap.properties" ) ) { | |
| 30 | P.load( in ); | |
| 31 | } catch( final Exception ignored ) { | |
| 32 | // Bootstrap properties cannot be found, throw in the towel. | |
| 33 | } | |
| 34 | } | |
| 35 | ||
| 36 | public static final String APP_TITLE = P.getProperty( "application.title" ); | |
| 37 | public static final String APP_TITLE_LOWERCASE = APP_TITLE.toLowerCase(); | |
| 38 | public static final String APP_VERSION = Launcher.getVersion(); | |
| 39 | public static final String APP_YEAR = getYear(); | |
| 40 | ||
| 41 | static { | |
| 42 | System.setProperty( "http.agent", APP_TITLE + " " + APP_VERSION ); | |
| 43 | setGlobalUserAgent( System.getProperty( "http.agent" ) ); | |
| 44 | } | |
| 45 | ||
| 46 | @SuppressWarnings( "SameParameterValue" ) | |
| 47 | private static InputStream openResource( final String path ) { | |
| 48 | return Constants.class.getResourceAsStream( path ); | |
| 49 | } | |
| 50 | ||
| 51 | private static String getYear() { | |
| 52 | return Integer.toString( Calendar.getInstance().get( Calendar.YEAR ) ); | |
| 53 | } | |
| 54 | } | |
| 1 | 55 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.util.GenericBuilder; | |
| 5 | import javafx.beans.value.ObservableValue; | |
| 6 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 7 | import org.fxmisc.richtext.model.Paragraph; | |
| 8 | import org.reactfx.collection.LiveList; | |
| 9 | ||
| 10 | import java.util.Collection; | |
| 11 | ||
| 12 | import static com.keenwrite.constants.Constants.STATUS_BAR_LINE; | |
| 13 | import static com.keenwrite.Messages.get; | |
| 14 | ||
| 15 | /** | |
| 16 | * Represents the absolute, relative, and maximum position of the caret. The | |
| 17 | * caret position is a character offset into the text. | |
| 18 | */ | |
| 19 | public class Caret { | |
| 20 | ||
| 21 | public static GenericBuilder<Caret.Mutator, Caret> builder() { | |
| 22 | return GenericBuilder.of( Caret.Mutator::new, Caret::new ); | |
| 23 | } | |
| 24 | ||
| 25 | /** | |
| 26 | * Used for building a new {@link Caret} instance. | |
| 27 | */ | |
| 28 | public static class Mutator { | |
| 29 | /** | |
| 30 | * Caret's current paragraph index (i.e., current caret line number). | |
| 31 | */ | |
| 32 | private ObservableValue<Integer> mParagraph; | |
| 33 | ||
| 34 | /** | |
| 35 | * Used to count the number of lines in the text editor document. | |
| 36 | */ | |
| 37 | private LiveList<Paragraph<Collection<String>, String, | |
| 38 | Collection<String>>> mParagraphs; | |
| 39 | ||
| 40 | /** | |
| 41 | * Caret offset into the full text, represented as a string index. | |
| 42 | */ | |
| 43 | private ObservableValue<Integer> mTextOffset; | |
| 44 | ||
| 45 | /** | |
| 46 | * Caret offset into the current paragraph, represented as a string index. | |
| 47 | */ | |
| 48 | private ObservableValue<Integer> mParaOffset; | |
| 49 | ||
| 50 | /** | |
| 51 | * Total number of characters in the document. | |
| 52 | */ | |
| 53 | private ObservableValue<Integer> mTextLength; | |
| 54 | ||
| 55 | /** | |
| 56 | * Configures this caret position using properties from the given editor. | |
| 57 | * | |
| 58 | * @param editor The text editor that has a caret with position properties. | |
| 59 | */ | |
| 60 | public void setEditor( final StyleClassedTextArea editor ) { | |
| 61 | mParagraph = editor.currentParagraphProperty(); | |
| 62 | mParagraphs = editor.getParagraphs(); | |
| 63 | mParaOffset = editor.caretColumnProperty(); | |
| 64 | mTextOffset = editor.caretPositionProperty(); | |
| 65 | mTextLength = editor.lengthProperty(); | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | private final Mutator mMutator; | |
| 70 | ||
| 71 | /** | |
| 72 | * Force using the builder pattern. | |
| 73 | */ | |
| 74 | private Caret( final Mutator mutator ) { | |
| 75 | assert mutator != null; | |
| 76 | ||
| 77 | mMutator = mutator; | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Allows observers to be notified when the value of the caret changes. | |
| 82 | * | |
| 83 | * @return An observer for the caret's document offset. | |
| 84 | */ | |
| 85 | public ObservableValue<Integer> textOffsetProperty() { | |
| 86 | return mMutator.mTextOffset; | |
| 87 | } | |
| 88 | ||
| 89 | /** | |
| 90 | * Answers whether the caret's offset into the text is between the given | |
| 91 | * offsets. | |
| 92 | * | |
| 93 | * @param began Starting value compared against the caret's text offset. | |
| 94 | * @param ended Ending value compared against the caret's text offset. | |
| 95 | * @return {@code true} when the caret's text offset is between the given | |
| 96 | * values, inclusively (for either value). | |
| 97 | */ | |
| 98 | public boolean isBetweenText( final int began, final int ended ) { | |
| 99 | final var offset = getTextOffset(); | |
| 100 | return began <= offset && offset <= ended; | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Answers whether the caret's offset into the paragraph is before the given | |
| 105 | * offset. | |
| 106 | * | |
| 107 | * @param offset Compared against the caret's paragraph offset. | |
| 108 | * @return {@code true} the caret's offset is before the given offset. | |
| 109 | */ | |
| 110 | public boolean isBeforeColumn( final int offset ) { | |
| 111 | return getParaOffset() < offset; | |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * Answers whether the caret's offset into the text is before the given | |
| 116 | * text offset. | |
| 117 | * | |
| 118 | * @param offset Compared against the caret's text offset. | |
| 119 | * @return {@code true} the caret's offset is after the given offset. | |
| 120 | */ | |
| 121 | public boolean isAfterColumn( final int offset ) { | |
| 122 | return getParaOffset() > offset; | |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * Answers whether the caret's offset into the text exceeds the length of | |
| 127 | * the text. | |
| 128 | * | |
| 129 | * @return {@code true} when the caret is at the end of the text boundary. | |
| 130 | */ | |
| 131 | public boolean isAfterText() { | |
| 132 | return getTextOffset() >= getTextLength(); | |
| 133 | } | |
| 134 | ||
| 135 | public boolean isAfter( final int offset ) { | |
| 136 | return offset >= getTextOffset(); | |
| 137 | } | |
| 138 | ||
| 139 | private int getParagraph() { | |
| 140 | return mMutator.mParagraph.getValue(); | |
| 141 | } | |
| 142 | ||
| 143 | /** | |
| 144 | * Returns the number of lines in the text editor. | |
| 145 | * | |
| 146 | * @return The size of the text editor's paragraph list plus one. | |
| 147 | */ | |
| 148 | private int getParagraphCount() { | |
| 149 | return mMutator.mParagraphs.size() + 1; | |
| 150 | } | |
| 151 | ||
| 152 | /** | |
| 153 | * Returns the absolute position of the caret within the entire document. | |
| 154 | * | |
| 155 | * @return A zero-based index of the caret position. | |
| 156 | */ | |
| 157 | private int getTextOffset() { | |
| 158 | return mMutator.mTextOffset.getValue(); | |
| 159 | } | |
| 160 | ||
| 161 | /** | |
| 162 | * Returns the position of the caret within the current paragraph being | |
| 163 | * edited. | |
| 164 | * | |
| 165 | * @return A zero-based index of the caret position relative to the | |
| 166 | * current paragraph. | |
| 167 | */ | |
| 168 | private int getParaOffset() { | |
| 169 | return mMutator.mParaOffset.getValue(); | |
| 170 | } | |
| 171 | ||
| 172 | /** | |
| 173 | * Returns the total number of characters in the document being edited. | |
| 174 | * | |
| 175 | * @return A zero-based count of the total characters in the document. | |
| 176 | */ | |
| 177 | private int getTextLength() { | |
| 178 | return mMutator.mTextLength.getValue(); | |
| 179 | } | |
| 180 | ||
| 181 | /** | |
| 182 | * Returns a human-readable string that shows the current caret position | |
| 183 | * within the text. Typically this will include the current line number, | |
| 184 | * the number of lines, and the character offset into the text. | |
| 185 | * <p> | |
| 186 | * If the {@link Caret} has not been properly built, this will return a | |
| 187 | * string for the status bar having all values set to zero. This can happen | |
| 188 | * during unit testing, but should not happen any other time. | |
| 189 | * </p> | |
| 190 | * | |
| 191 | * @return A string to present to an end user. | |
| 192 | */ | |
| 193 | @Override | |
| 194 | public String toString() { | |
| 195 | try { | |
| 196 | return get( STATUS_BAR_LINE, | |
| 197 | getParagraph() + 1, | |
| 198 | getParagraphCount(), | |
| 199 | getTextOffset() + 1 ); | |
| 200 | } catch( final Exception ex ) { | |
| 201 | return get( STATUS_BAR_LINE, 0, 0, 0 ); | |
| 202 | } | |
| 203 | } | |
| 204 | } | |
| 1 | 205 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.editors.TextDefinition; | |
| 5 | import com.keenwrite.editors.TextEditor; | |
| 6 | import com.keenwrite.editors.definition.DefinitionTreeItem; | |
| 7 | import com.keenwrite.sigils.SigilOperator; | |
| 8 | ||
| 9 | import static com.keenwrite.constants.Constants.*; | |
| 10 | import static com.keenwrite.events.StatusEvent.clue; | |
| 11 | ||
| 12 | /** | |
| 13 | * Provides the logic for injecting variable names within the editor. | |
| 14 | */ | |
| 15 | public final class DefinitionNameInjector { | |
| 16 | ||
| 17 | /** | |
| 18 | * Prevent instantiation. | |
| 19 | */ | |
| 20 | private DefinitionNameInjector() { | |
| 21 | } | |
| 22 | ||
| 23 | /** | |
| 24 | * Find a node that matches the current word and substitute the definition | |
| 25 | * reference. | |
| 26 | */ | |
| 27 | public static void autoinsert( | |
| 28 | final TextEditor editor, | |
| 29 | final TextDefinition definitions, | |
| 30 | final SigilOperator operator ) { | |
| 31 | try { | |
| 32 | if( definitions.isEmpty() ) { | |
| 33 | clue( STATUS_DEFINITION_EMPTY ); | |
| 34 | } | |
| 35 | else { | |
| 36 | final var indexes = editor.getCaretWord(); | |
| 37 | final var word = editor.getText( indexes ); | |
| 38 | ||
| 39 | if( word.isBlank() ) { | |
| 40 | clue( STATUS_DEFINITION_BLANK ); | |
| 41 | } | |
| 42 | else { | |
| 43 | final var leaf = findLeaf( definitions, word ); | |
| 44 | ||
| 45 | if( leaf == null ) { | |
| 46 | clue( STATUS_DEFINITION_MISSING, word ); | |
| 47 | } | |
| 48 | else { | |
| 49 | final var entokened = operator.entoken( leaf.toPath() ); | |
| 50 | editor.replaceText( indexes, operator.apply( entokened ) ); | |
| 51 | definitions.expand( leaf ); | |
| 52 | } | |
| 53 | } | |
| 54 | } | |
| 55 | } catch( final Exception ex ) { | |
| 56 | clue( STATUS_DEFINITION_BLANK, ex ); | |
| 57 | } | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Looks for the given word, matching first by exact, next by a starts-with | |
| 62 | * condition with diacritics replaced, then by containment. | |
| 63 | * | |
| 64 | * @param word Match the word by: exact, beginning, containment, or other. | |
| 65 | */ | |
| 66 | @SuppressWarnings( "ConstantConditions" ) | |
| 67 | private static DefinitionTreeItem<String> findLeaf( | |
| 68 | final TextDefinition definition, final String word ) { | |
| 69 | assert word != null; | |
| 70 | ||
| 71 | DefinitionTreeItem<String> leaf = null; | |
| 72 | ||
| 73 | leaf = leaf == null ? definition.findLeafExact( word ) : leaf; | |
| 74 | leaf = leaf == null ? definition.findLeafStartsWith( word ) : leaf; | |
| 75 | leaf = leaf == null ? definition.findLeafContains( word ) : leaf; | |
| 76 | leaf = leaf == null ? definition.findLeafContainsNoCase( word ) : leaf; | |
| 77 | ||
| 78 | return leaf; | |
| 79 | } | |
| 80 | } | |
| 1 | 81 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import java.io.File; | |
| 5 | import java.nio.file.Path; | |
| 6 | ||
| 7 | import static org.apache.commons.io.FilenameUtils.removeExtension; | |
| 8 | ||
| 9 | /** | |
| 10 | * Provides controls for processor behaviour when transforming input documents. | |
| 11 | */ | |
| 12 | public enum ExportFormat { | |
| 13 | ||
| 14 | /** | |
| 15 | * For HTML exports, encode TeX as SVG. Treat image links relatively. | |
| 16 | */ | |
| 17 | HTML_TEX_SVG( ".html" ), | |
| 18 | ||
| 19 | /** | |
| 20 | * For HTML exports, encode TeX using {@code $} delimiters, suitable for | |
| 21 | * rendering by an external TeX typesetting engine (or online with KaTeX). | |
| 22 | * Treat image links relatively. | |
| 23 | */ | |
| 24 | HTML_TEX_DELIMITED( ".html" ), | |
| 25 | ||
| 26 | /** | |
| 27 | * For XHTML exports, encode TeX using {@code $} delimiters. | |
| 28 | */ | |
| 29 | XHTML_TEX( ".xml" ), | |
| 30 | ||
| 31 | /** | |
| 32 | * Indicates that the processors should export to a Markdown format. | |
| 33 | * Treat image links relatively. | |
| 34 | */ | |
| 35 | MARKDOWN_PLAIN( ".out.md" ), | |
| 36 | ||
| 37 | /** | |
| 38 | * Exports as PDF file format. | |
| 39 | */ | |
| 40 | APPLICATION_PDF( ".pdf" ), | |
| 41 | ||
| 42 | /** | |
| 43 | * Indicates no special export format is to be created. No extension is | |
| 44 | * applicable. Image links must use absolute directories. | |
| 45 | */ | |
| 46 | NONE( "" ); | |
| 47 | ||
| 48 | /** | |
| 49 | * Preferred file name extension for the given file type. | |
| 50 | */ | |
| 51 | private final String mExtension; | |
| 52 | ||
| 53 | ExportFormat( final String extension ) { | |
| 54 | mExtension = extension; | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * Returns the given {@link File} with its extension replaced by one that | |
| 59 | * matches this {@link ExportFormat} extension. | |
| 60 | * | |
| 61 | * @param file The file to perform an extension swap. | |
| 62 | * @return The given file with its extension replaced. | |
| 63 | */ | |
| 64 | public File toExportFilename( final File file ) { | |
| 65 | return new File( removeExtension( file.getName() ) + mExtension ); | |
| 66 | } | |
| 67 | ||
| 68 | /** | |
| 69 | * Delegates to {@link #toExportFilename(File)} after converting the given | |
| 70 | * {@link Path} to an instance of {@link File}. | |
| 71 | * | |
| 72 | * @param path The {@link Path} to convert to a {@link File}. | |
| 73 | * @return The given path with its extension replaced. | |
| 74 | */ | |
| 75 | public File toExportFilename( final Path path ) { | |
| 76 | return toExportFilename( path.toFile() ); | |
| 77 | } | |
| 78 | } | |
| 1 | 79 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import java.io.IOException; | |
| 5 | import java.io.InputStream; | |
| 6 | import java.util.Properties; | |
| 7 | ||
| 8 | import static com.keenwrite.Bootstrap.*; | |
| 9 | import static com.keenwrite.PermissiveCertificate.installTrustManager; | |
| 10 | import static java.lang.String.format; | |
| 11 | ||
| 12 | /** | |
| 13 | * Launches the application using the {@link MainApp} class. | |
| 14 | * | |
| 15 | * <p> | |
| 16 | * This is required until modules are implemented, which may never happen | |
| 17 | * because the application should be ported away from Java and JavaFX. | |
| 18 | * </p> | |
| 19 | */ | |
| 20 | public final class Launcher { | |
| 21 | /** | |
| 22 | * Delegates to the application entry point. | |
| 23 | * | |
| 24 | * @param args Command-line arguments. | |
| 25 | */ | |
| 26 | public static void main( final String[] args ) { | |
| 27 | try { | |
| 28 | installTrustManager(); | |
| 29 | showAppInfo(); | |
| 30 | MainApp.main( args ); | |
| 31 | } catch( final Throwable t ) { | |
| 32 | log( t ); | |
| 33 | } | |
| 34 | } | |
| 35 | ||
| 36 | @SuppressWarnings( "RedundantStringFormatCall" ) | |
| 37 | private static void showAppInfo() { | |
| 38 | out( format( "%s version %s", APP_TITLE, APP_VERSION ) ); | |
| 39 | out( format( "Copyright 2016-%s White Magic Software, Ltd.", APP_YEAR ) ); | |
| 40 | out( format( "Portions copyright 2015-2020 Karl Tauber." ) ); | |
| 41 | } | |
| 42 | ||
| 43 | private static void out( final String s ) { | |
| 44 | System.out.println( s ); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Returns the application version number retrieved from the application | |
| 49 | * properties file. The properties file is generated at build time, which | |
| 50 | * keys off the repository. | |
| 51 | * | |
| 52 | * @return The application version number. | |
| 53 | * @throws RuntimeException An {@link IOException} occurred. | |
| 54 | */ | |
| 55 | public static String getVersion() { | |
| 56 | try { | |
| 57 | final var properties = loadProperties( "app.properties" ); | |
| 58 | return properties.getProperty( "application.version" ); | |
| 59 | } catch( final Exception ex ) { | |
| 60 | throw new RuntimeException( ex ); | |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | @SuppressWarnings( "SameParameterValue" ) | |
| 65 | private static Properties loadProperties( final String resource ) | |
| 66 | throws IOException { | |
| 67 | final var properties = new Properties(); | |
| 68 | properties.load( getResourceAsStream( getResourceName( resource ) ) ); | |
| 69 | return properties; | |
| 70 | } | |
| 71 | ||
| 72 | private static String getResourceName( final String resource ) { | |
| 73 | return format( "%s/%s", getPackagePath(), resource ); | |
| 74 | } | |
| 75 | ||
| 76 | private static String getPackagePath() { | |
| 77 | return Launcher.class.getPackageName().replace( '.', '/' ); | |
| 78 | } | |
| 79 | ||
| 80 | private static InputStream getResourceAsStream( final String resource ) { | |
| 81 | return Launcher.class.getClassLoader().getResourceAsStream( resource ); | |
| 82 | } | |
| 83 | ||
| 84 | /** | |
| 85 | * Logs the message of an error to the console. | |
| 86 | * | |
| 87 | * @param error The fatal error that could not be handled. | |
| 88 | */ | |
| 89 | private static void log( final Throwable error ) { | |
| 90 | var message = error.getMessage(); | |
| 91 | ||
| 92 | if( message != null && message.toLowerCase().contains( "javafx" ) ) { | |
| 93 | message = "Re-run using a Java Runtime Environment that includes JavaFX."; | |
| 94 | } | |
| 95 | ||
| 96 | out( format( "ERROR: %s", message ) ); | |
| 97 | } | |
| 98 | } | |
| 1 | 99 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.events.HyperlinkOpenEvent; | |
| 5 | import com.keenwrite.preferences.Workspace; | |
| 6 | import javafx.application.Application; | |
| 7 | import javafx.event.Event; | |
| 8 | import javafx.event.EventType; | |
| 9 | import javafx.scene.input.KeyCode; | |
| 10 | import javafx.scene.input.KeyEvent; | |
| 11 | import javafx.stage.Stage; | |
| 12 | import org.greenrobot.eventbus.Subscribe; | |
| 13 | ||
| 14 | import java.util.function.BooleanSupplier; | |
| 15 | import java.util.logging.LogManager; | |
| 16 | ||
| 17 | import static com.keenwrite.Bootstrap.APP_TITLE; | |
| 18 | import static com.keenwrite.constants.GraphicsConstants.LOGOS; | |
| 19 | import static com.keenwrite.events.Bus.register; | |
| 20 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 21 | import static com.keenwrite.util.FontLoader.initFonts; | |
| 22 | import static javafx.scene.input.KeyCode.ALT; | |
| 23 | import static javafx.scene.input.KeyCode.F11; | |
| 24 | import static javafx.scene.input.KeyEvent.KEY_PRESSED; | |
| 25 | import static javafx.scene.input.KeyEvent.KEY_RELEASED; | |
| 26 | ||
| 27 | /** | |
| 28 | * Application entry point. The application allows users to edit plain text | |
| 29 | * files in a markup notation and see a real-time preview of the formatted | |
| 30 | * output. | |
| 31 | */ | |
| 32 | public final class MainApp extends Application { | |
| 33 | ||
| 34 | private Workspace mWorkspace; | |
| 35 | private MainScene mMainScene; | |
| 36 | ||
| 37 | /** | |
| 38 | * Application entry point. | |
| 39 | * | |
| 40 | * @param args Command-line arguments. | |
| 41 | */ | |
| 42 | public static void main( final String[] args ) { | |
| 43 | disableLogging(); | |
| 44 | launch( args ); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Suppress logging to standard output and standard error. | |
| 49 | */ | |
| 50 | private static void disableLogging() { | |
| 51 | LogManager.getLogManager().reset(); | |
| 52 | System.err.close(); | |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| 56 | * JavaFX entry point. | |
| 57 | * | |
| 58 | * @param stage The primary application stage. | |
| 59 | */ | |
| 60 | @Override | |
| 61 | public void start( final Stage stage ) { | |
| 62 | // Must be instantiated after the UI is initialized (i.e., not in main) | |
| 63 | // because it interacts with GUI properties. | |
| 64 | mWorkspace = new Workspace(); | |
| 65 | ||
| 66 | initFonts(); | |
| 67 | initState( stage ); | |
| 68 | initStage( stage ); | |
| 69 | initIcons( stage ); | |
| 70 | initScene( stage ); | |
| 71 | ||
| 72 | stage.show(); | |
| 73 | register( this ); | |
| 74 | } | |
| 75 | ||
| 76 | private void initState( final Stage stage ) { | |
| 77 | final var enable = createBoundsEnabledSupplier( stage ); | |
| 78 | ||
| 79 | stage.setX( mWorkspace.toDouble( KEY_UI_WINDOW_X ) ); | |
| 80 | stage.setY( mWorkspace.toDouble( KEY_UI_WINDOW_Y ) ); | |
| 81 | stage.setWidth( mWorkspace.toDouble( KEY_UI_WINDOW_W ) ); | |
| 82 | stage.setHeight( mWorkspace.toDouble( KEY_UI_WINDOW_H ) ); | |
| 83 | stage.setMaximized( mWorkspace.toBoolean( KEY_UI_WINDOW_MAX ) ); | |
| 84 | stage.setFullScreen( mWorkspace.toBoolean( KEY_UI_WINDOW_FULL ) ); | |
| 85 | ||
| 86 | mWorkspace.listen( KEY_UI_WINDOW_X, stage.xProperty(), enable ); | |
| 87 | mWorkspace.listen( KEY_UI_WINDOW_Y, stage.yProperty(), enable ); | |
| 88 | mWorkspace.listen( KEY_UI_WINDOW_W, stage.widthProperty(), enable ); | |
| 89 | mWorkspace.listen( KEY_UI_WINDOW_H, stage.heightProperty(), enable ); | |
| 90 | mWorkspace.listen( KEY_UI_WINDOW_MAX, stage.maximizedProperty() ); | |
| 91 | mWorkspace.listen( KEY_UI_WINDOW_FULL, stage.fullScreenProperty() ); | |
| 92 | } | |
| 93 | ||
| 94 | private void initStage( final Stage stage ) { | |
| 95 | stage.setTitle( APP_TITLE ); | |
| 96 | stage.addEventHandler( KEY_PRESSED, event -> { | |
| 97 | if( F11.equals( event.getCode() ) ) { | |
| 98 | stage.setFullScreen( !stage.isFullScreen() ); | |
| 99 | } | |
| 100 | } ); | |
| 101 | ||
| 102 | // After the app loses focus, when the user switches back using Alt+Tab, | |
| 103 | // the menu mnemonic is sometimes engaged, swallowing the first letter that | |
| 104 | // the user types---if it is a menu mnemonic. See MainScene::createScene(). | |
| 105 | // | |
| 106 | // JavaFX Bug: https://bugs.openjdk.java.net/browse/JDK-8090647 | |
| 107 | stage.focusedProperty().addListener( ( c, lost, show ) -> { | |
| 108 | for( final var menu : mMainScene.getMenuBar().getMenus() ) { | |
| 109 | menu.hide(); | |
| 110 | } | |
| 111 | ||
| 112 | for( final var mnemonics : stage.getScene().getMnemonics().values() ) { | |
| 113 | for( final var mnemonic : mnemonics ) { | |
| 114 | mnemonic.getNode().fireEvent( keyUp( ALT ) ); | |
| 115 | } | |
| 116 | } | |
| 117 | } ); | |
| 118 | } | |
| 119 | ||
| 120 | private void initIcons( final Stage stage ) { | |
| 121 | stage.getIcons().addAll( LOGOS ); | |
| 122 | } | |
| 123 | ||
| 124 | private void initScene( final Stage stage ) { | |
| 125 | mMainScene = new MainScene( mWorkspace ); | |
| 126 | stage.setScene( mMainScene.getScene() ); | |
| 127 | } | |
| 128 | ||
| 129 | /** | |
| 130 | * When a hyperlink website URL is clicked, this method is called to launch | |
| 131 | * the default browser to the event's location. | |
| 132 | * | |
| 133 | * @param event The event called when a hyperlink was clicked. | |
| 134 | */ | |
| 135 | @Subscribe | |
| 136 | public void handle( final HyperlinkOpenEvent event ) { | |
| 137 | getHostServices().showDocument( event.getUri().toString() ); | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * When the window is maximized, full screen, or iconified, prevent updating | |
| 142 | * the window bounds. This is used so that if the user exits the application | |
| 143 | * when full screen (or maximized), restarting the application will recall | |
| 144 | * the previous bounds, allowing for continuity of expected behaviour. | |
| 145 | * | |
| 146 | * @param stage The window to check for "normal" status. | |
| 147 | * @return {@code false} when the bounds must not be changed, ergo persisted. | |
| 148 | */ | |
| 149 | private BooleanSupplier createBoundsEnabledSupplier( final Stage stage ) { | |
| 150 | return () -> | |
| 151 | !(stage.isMaximized() || stage.isFullScreen() || stage.isIconified()); | |
| 152 | } | |
| 153 | ||
| 154 | /** | |
| 155 | * Creates an instance of {@link KeyEvent} that represents pressing a key. | |
| 156 | * | |
| 157 | * @param code The key to simulate being pressed down. | |
| 158 | * @param shift Whether shift key modifier shall modify the key code. | |
| 159 | * @return An instance of {@link KeyEvent} that may be used to simulate | |
| 160 | * a key being pressed. | |
| 161 | */ | |
| 162 | public static Event keyDown( final KeyCode code, final boolean shift ) { | |
| 163 | return keyEvent( KEY_PRESSED, code, shift ); | |
| 164 | } | |
| 165 | ||
| 166 | /** | |
| 167 | * Creates an instance of {@link KeyEvent} that represents releasing a key. | |
| 168 | * | |
| 169 | * @param code The key to simulate being released up. | |
| 170 | * @param shift Whether shift key modifier shall modify the key code. | |
| 171 | * @return An instance of {@link KeyEvent} that may be used to simulate | |
| 172 | * a key being released. | |
| 173 | */ | |
| 174 | public static Event keyUp( final KeyCode code, final boolean shift ) { | |
| 175 | return keyEvent( KEY_RELEASED, code, shift ); | |
| 176 | } | |
| 177 | ||
| 178 | /** | |
| 179 | * Creates an instance of {@link KeyEvent} that represents a key released | |
| 180 | * event without any modifier keys held. | |
| 181 | * | |
| 182 | * @param code The key code representing a key to simulate releasing. | |
| 183 | * @return An instance of {@link KeyEvent}. | |
| 184 | */ | |
| 185 | public static Event keyUp( final KeyCode code ) { | |
| 186 | return keyUp( code, false ); | |
| 187 | } | |
| 188 | ||
| 189 | private static Event keyEvent( | |
| 190 | final EventType<KeyEvent> type, final KeyCode code, final boolean shift ) { | |
| 191 | return new KeyEvent( | |
| 192 | type, "", "", code, shift, false, false, false | |
| 193 | ); | |
| 194 | } | |
| 195 | } | |
| 1 | 196 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.editors.TextDefinition; | |
| 5 | import com.keenwrite.editors.TextEditor; | |
| 6 | import com.keenwrite.editors.TextResource; | |
| 7 | import com.keenwrite.editors.definition.DefinitionEditor; | |
| 8 | import com.keenwrite.editors.definition.TreeTransformer; | |
| 9 | import com.keenwrite.editors.definition.yaml.YamlTreeTransformer; | |
| 10 | import com.keenwrite.editors.markdown.MarkdownEditor; | |
| 11 | import com.keenwrite.events.*; | |
| 12 | import com.keenwrite.io.MediaType; | |
| 13 | import com.keenwrite.preferences.Key; | |
| 14 | import com.keenwrite.preferences.Workspace; | |
| 15 | import com.keenwrite.preview.HtmlPanel; | |
| 16 | import com.keenwrite.preview.HtmlPreview; | |
| 17 | import com.keenwrite.processors.Processor; | |
| 18 | import com.keenwrite.processors.ProcessorContext; | |
| 19 | import com.keenwrite.processors.ProcessorFactory; | |
| 20 | import com.keenwrite.processors.markdown.extensions.CaretExtension; | |
| 21 | import com.keenwrite.service.events.Notifier; | |
| 22 | import com.keenwrite.sigils.RSigilOperator; | |
| 23 | import com.keenwrite.sigils.SigilOperator; | |
| 24 | import com.keenwrite.sigils.Tokens; | |
| 25 | import com.keenwrite.sigils.YamlSigilOperator; | |
| 26 | import com.keenwrite.ui.explorer.FilePickerFactory; | |
| 27 | import com.keenwrite.ui.heuristics.DocumentStatistics; | |
| 28 | import com.keenwrite.ui.outline.DocumentOutline; | |
| 29 | import com.panemu.tiwulfx.control.dock.DetachableTab; | |
| 30 | import com.panemu.tiwulfx.control.dock.DetachableTabPane; | |
| 31 | import javafx.application.Platform; | |
| 32 | import javafx.beans.property.*; | |
| 33 | import javafx.collections.ListChangeListener; | |
| 34 | import javafx.concurrent.Task; | |
| 35 | import javafx.event.ActionEvent; | |
| 36 | import javafx.event.Event; | |
| 37 | import javafx.event.EventHandler; | |
| 38 | import javafx.scene.Node; | |
| 39 | import javafx.scene.Scene; | |
| 40 | import javafx.scene.control.*; | |
| 41 | import javafx.scene.control.TreeItem.TreeModificationEvent; | |
| 42 | import javafx.scene.input.KeyEvent; | |
| 43 | import javafx.scene.layout.FlowPane; | |
| 44 | import javafx.stage.Stage; | |
| 45 | import javafx.stage.Window; | |
| 46 | import org.greenrobot.eventbus.Subscribe; | |
| 47 | ||
| 48 | import java.io.File; | |
| 49 | import java.io.FileNotFoundException; | |
| 50 | import java.nio.file.Path; | |
| 51 | import java.util.*; | |
| 52 | import java.util.concurrent.ExecutorService; | |
| 53 | import java.util.concurrent.atomic.AtomicBoolean; | |
| 54 | import java.util.function.Function; | |
| 55 | import java.util.stream.Collectors; | |
| 56 | ||
| 57 | import static com.keenwrite.ExportFormat.NONE; | |
| 58 | import static com.keenwrite.Messages.get; | |
| 59 | import static com.keenwrite.constants.Constants.*; | |
| 60 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG_NODE; | |
| 61 | import static com.keenwrite.events.Bus.register; | |
| 62 | import static com.keenwrite.events.HyperlinkOpenEvent.fireHyperlinkOpenEvent; | |
| 63 | import static com.keenwrite.events.StatusEvent.clue; | |
| 64 | import static com.keenwrite.io.MediaType.*; | |
| 65 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 66 | import static com.keenwrite.processors.IdentityProcessor.IDENTITY; | |
| 67 | import static com.keenwrite.processors.ProcessorFactory.createProcessors; | |
| 68 | import static java.lang.String.format; | |
| 69 | import static java.lang.System.getProperty; | |
| 70 | import static java.util.concurrent.Executors.newFixedThreadPool; | |
| 71 | import static java.util.stream.Collectors.groupingBy; | |
| 72 | import static javafx.application.Platform.runLater; | |
| 73 | import static javafx.scene.control.Alert.AlertType.ERROR; | |
| 74 | import static javafx.scene.control.ButtonType.*; | |
| 75 | import static javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS; | |
| 76 | import static javafx.scene.input.KeyCode.SPACE; | |
| 77 | import static javafx.scene.input.KeyCombination.CONTROL_DOWN; | |
| 78 | import static javafx.util.Duration.millis; | |
| 79 | import static javax.swing.SwingUtilities.invokeLater; | |
| 80 | import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | |
| 81 | ||
| 82 | /** | |
| 83 | * Responsible for wiring together the main application components for a | |
| 84 | * particular workspace (project). These include the definition views, | |
| 85 | * text editors, and preview pane along with any corresponding controllers. | |
| 86 | */ | |
| 87 | public final class MainPane extends SplitPane { | |
| 88 | private static final ExecutorService sExecutor = newFixedThreadPool( 1 ); | |
| 89 | ||
| 90 | private static final Notifier sNotifier = Services.load( Notifier.class ); | |
| 91 | ||
| 92 | /** | |
| 93 | * Used when opening files to determine how each file should be binned and | |
| 94 | * therefore what tab pane to be opened within. | |
| 95 | */ | |
| 96 | private static final Set<MediaType> PLAIN_TEXT_FORMAT = Set.of( | |
| 97 | TEXT_MARKDOWN, TEXT_R_MARKDOWN, UNDEFINED | |
| 98 | ); | |
| 99 | ||
| 100 | /** | |
| 101 | * Prevents re-instantiation of processing classes. | |
| 102 | */ | |
| 103 | private final Map<TextResource, Processor<String>> mProcessors = | |
| 104 | new HashMap<>(); | |
| 105 | ||
| 106 | private final Workspace mWorkspace; | |
| 107 | ||
| 108 | /** | |
| 109 | * Groups similar file type tabs together. | |
| 110 | */ | |
| 111 | private final List<TabPane> mTabPanes = new ArrayList<>(); | |
| 112 | ||
| 113 | /** | |
| 114 | * Stores definition names and values. | |
| 115 | */ | |
| 116 | private final Map<String, String> mResolvedMap = | |
| 117 | new HashMap<>( MAP_SIZE_DEFAULT ); | |
| 118 | ||
| 119 | /** | |
| 120 | * Renders the actively selected plain text editor tab. | |
| 121 | */ | |
| 122 | private final HtmlPreview mPreview; | |
| 123 | ||
| 124 | /** | |
| 125 | * Provides an interactive document outline. | |
| 126 | */ | |
| 127 | private final DocumentOutline mOutline = new DocumentOutline(); | |
| 128 | ||
| 129 | /** | |
| 130 | * Changing the active editor fires the value changed event. This allows | |
| 131 | * refreshes to happen when external definitions are modified and need to | |
| 132 | * trigger the processing chain. | |
| 133 | */ | |
| 134 | private final ObjectProperty<TextEditor> mActiveTextEditor = | |
| 135 | createActiveTextEditor(); | |
| 136 | ||
| 137 | /** | |
| 138 | * Changing the active definition editor fires the value changed event. This | |
| 139 | * allows refreshes to happen when external definitions are modified and need | |
| 140 | * to trigger the processing chain. | |
| 141 | */ | |
| 142 | private final ObjectProperty<TextDefinition> mActiveDefinitionEditor = | |
| 143 | createActiveDefinitionEditor( mActiveTextEditor ); | |
| 144 | ||
| 145 | /** | |
| 146 | * Tracks the number of detached tab panels opened into their own windows, | |
| 147 | * which allows unique identification of subordinate windows by their title. | |
| 148 | * It is doubtful more than 128 windows, much less 256, will be created. | |
| 149 | */ | |
| 150 | private byte mWindowCount; | |
| 151 | ||
| 152 | /** | |
| 153 | * Called when the definition data is changed. | |
| 154 | */ | |
| 155 | private final EventHandler<TreeModificationEvent<Event>> mTreeHandler = | |
| 156 | event -> { | |
| 157 | final var editor = mActiveDefinitionEditor.get(); | |
| 158 | ||
| 159 | resolve( editor ); | |
| 160 | process( getActiveTextEditor() ); | |
| 161 | save( editor ); | |
| 162 | }; | |
| 163 | ||
| 164 | private final DocumentStatistics mStatistics; | |
| 165 | ||
| 166 | /** | |
| 167 | * Adds all content panels to the main user interface. This will load the | |
| 168 | * configuration settings from the workspace to reproduce the settings from | |
| 169 | * a previous session. | |
| 170 | */ | |
| 171 | public MainPane( final Workspace workspace ) { | |
| 172 | mWorkspace = workspace; | |
| 173 | mPreview = new HtmlPreview( workspace ); | |
| 174 | mStatistics = new DocumentStatistics( workspace ); | |
| 175 | mActiveTextEditor.set( new MarkdownEditor( workspace ) ); | |
| 176 | ||
| 177 | open( bin( getRecentFiles() ) ); | |
| 178 | viewPreview(); | |
| 179 | setDividerPositions( calculateDividerPositions() ); | |
| 180 | ||
| 181 | // Once the main scene's window regains focus, update the active definition | |
| 182 | // editor to the currently selected tab. | |
| 183 | runLater( () -> getWindow().setOnCloseRequest( ( event ) -> { | |
| 184 | // Order matters here. We want to close all the tabs to ensure each | |
| 185 | // is saved, but after they are closed, the workspace should still | |
| 186 | // retain the list of files that were open. If this line came after | |
| 187 | // closing, then restarting the application would list no files. | |
| 188 | mWorkspace.save(); | |
| 189 | ||
| 190 | if( closeAll() ) { | |
| 191 | Platform.exit(); | |
| 192 | System.exit( 0 ); | |
| 193 | } | |
| 194 | else { | |
| 195 | event.consume(); | |
| 196 | } | |
| 197 | } ) ); | |
| 198 | ||
| 199 | register( this ); | |
| 200 | } | |
| 201 | ||
| 202 | @Subscribe | |
| 203 | public void handle( final TextEditorFocusEvent event ) { | |
| 204 | mActiveTextEditor.set( event.get() ); | |
| 205 | } | |
| 206 | ||
| 207 | @Subscribe | |
| 208 | public void handle( final TextDefinitionFocusEvent event ) { | |
| 209 | mActiveDefinitionEditor.set( event.get() ); | |
| 210 | } | |
| 211 | ||
| 212 | /** | |
| 213 | * Typically called when a file name is clicked in the {@link HtmlPanel}. | |
| 214 | * | |
| 215 | * @param event The event to process, must contain a valid file reference. | |
| 216 | */ | |
| 217 | @Subscribe | |
| 218 | public void handle( final FileOpenEvent event ) { | |
| 219 | final File eventFile; | |
| 220 | final var eventUri = event.getUri(); | |
| 221 | ||
| 222 | if( eventUri.isAbsolute() ) { | |
| 223 | eventFile = new File( eventUri.getPath() ); | |
| 224 | } | |
| 225 | else { | |
| 226 | final var activeFile = getActiveTextEditor().getFile(); | |
| 227 | final var parent = activeFile.getParentFile(); | |
| 228 | ||
| 229 | if( parent == null ) { | |
| 230 | clue( new FileNotFoundException( eventUri.getPath() ) ); | |
| 231 | return; | |
| 232 | } | |
| 233 | else { | |
| 234 | final var parentPath = parent.getAbsolutePath(); | |
| 235 | eventFile = Path.of( parentPath, eventUri.getPath() ).toFile(); | |
| 236 | } | |
| 237 | } | |
| 238 | ||
| 239 | runLater( () -> open( eventFile ) ); | |
| 240 | } | |
| 241 | ||
| 242 | @Subscribe | |
| 243 | public void handle( final CaretNavigationEvent event ) { | |
| 244 | runLater( () -> { | |
| 245 | final var textArea = getActiveTextEditor().getTextArea(); | |
| 246 | textArea.moveTo( event.getOffset() ); | |
| 247 | textArea.requestFollowCaret(); | |
| 248 | textArea.requestFocus(); | |
| 249 | } ); | |
| 250 | } | |
| 251 | ||
| 252 | @Subscribe | |
| 253 | @SuppressWarnings( "unused" ) | |
| 254 | public void handle( final ExportFailedEvent event ) { | |
| 255 | final var os = getProperty( "os.name" ); | |
| 256 | final var arch = getProperty( "os.arch" ).toLowerCase(); | |
| 257 | final var bits = getProperty( "sun.arch.data.model" ); | |
| 258 | ||
| 259 | final var title = Messages.get( "Alert.typesetter.missing.title" ); | |
| 260 | final var header = Messages.get( "Alert.typesetter.missing.header" ); | |
| 261 | final var version = Messages.get( | |
| 262 | "Alert.typesetter.missing.version", | |
| 263 | os, | |
| 264 | arch | |
| 265 | .replaceAll( "amd.*|i.*|x86.*", "X86" ) | |
| 266 | .replaceAll( "mips.*", "MIPS" ) | |
| 267 | .replaceAll( "armv.*", "ARM" ), | |
| 268 | bits ); | |
| 269 | final var text = Messages.get( "Alert.typesetter.missing.installer.text" ); | |
| 270 | ||
| 271 | // Download and install ConTeXt for {0} {1} {2}-bit | |
| 272 | final var content = format( "%s %s", text, version ); | |
| 273 | final var flowPane = new FlowPane(); | |
| 274 | final var link = new Hyperlink( text ); | |
| 275 | final var label = new Label( version ); | |
| 276 | flowPane.getChildren().addAll( link, label ); | |
| 277 | ||
| 278 | final var alert = new Alert( ERROR, content, OK ); | |
| 279 | alert.setTitle( title ); | |
| 280 | alert.setHeaderText( header ); | |
| 281 | alert.getDialogPane().contentProperty().set( flowPane ); | |
| 282 | alert.setGraphic( ICON_DIALOG_NODE ); | |
| 283 | ||
| 284 | link.setOnAction( ( e ) -> { | |
| 285 | alert.close(); | |
| 286 | final var url = Messages.get( "Alert.typesetter.missing.installer.url" ); | |
| 287 | runLater( () -> fireHyperlinkOpenEvent( url ) ); | |
| 288 | } ); | |
| 289 | ||
| 290 | alert.showAndWait(); | |
| 291 | } | |
| 292 | ||
| 293 | /** | |
| 294 | * TODO: Load divider positions from exported settings, see bin() comment. | |
| 295 | */ | |
| 296 | private double[] calculateDividerPositions() { | |
| 297 | final var ratio = 100f / getItems().size() / 100; | |
| 298 | final var positions = getDividerPositions(); | |
| 299 | ||
| 300 | for( int i = 0; i < positions.length; i++ ) { | |
| 301 | positions[ i ] = ratio * i; | |
| 302 | } | |
| 303 | ||
| 304 | return positions; | |
| 305 | } | |
| 306 | ||
| 307 | /** | |
| 308 | * Opens all the files into the application, provided the paths are unique. | |
| 309 | * This may only be called for any type of files that a user can edit | |
| 310 | * (i.e., update and persist), such as definitions and text files. | |
| 311 | * | |
| 312 | * @param files The list of files to open. | |
| 313 | */ | |
| 314 | public void open( final List<File> files ) { | |
| 315 | files.forEach( this::open ); | |
| 316 | } | |
| 317 | ||
| 318 | /** | |
| 319 | * This opens the given file. Since the preview pane is not a file that | |
| 320 | * can be opened, it is safe to add a listener to the detachable pane. | |
| 321 | * | |
| 322 | * @param file The file to open. | |
| 323 | */ | |
| 324 | private void open( final File file ) { | |
| 325 | final var tab = createTab( file ); | |
| 326 | final var node = tab.getContent(); | |
| 327 | final var mediaType = MediaType.valueFrom( file ); | |
| 328 | final var tabPane = obtainTabPane( mediaType ); | |
| 329 | ||
| 330 | tab.setTooltip( createTooltip( file ) ); | |
| 331 | tabPane.setFocusTraversable( false ); | |
| 332 | tabPane.setTabClosingPolicy( ALL_TABS ); | |
| 333 | tabPane.getTabs().add( tab ); | |
| 334 | ||
| 335 | // Attach the tab scene factory for new tab panes. | |
| 336 | if( !getItems().contains( tabPane ) ) { | |
| 337 | addTabPane( | |
| 338 | node instanceof TextDefinition ? 0 : getItems().size(), tabPane | |
| 339 | ); | |
| 340 | } | |
| 341 | ||
| 342 | getRecentFiles().add( file.getAbsolutePath() ); | |
| 343 | } | |
| 344 | ||
| 345 | /** | |
| 346 | * Opens a new text editor document using the default document file name. | |
| 347 | */ | |
| 348 | public void newTextEditor() { | |
| 349 | open( DOCUMENT_DEFAULT ); | |
| 350 | } | |
| 351 | ||
| 352 | /** | |
| 353 | * Opens a new definition editor document using the default definition | |
| 354 | * file name. | |
| 355 | */ | |
| 356 | public void newDefinitionEditor() { | |
| 357 | open( DEFINITION_DEFAULT ); | |
| 358 | } | |
| 359 | ||
| 360 | /** | |
| 361 | * Iterates over all tab panes to find all {@link TextEditor}s and request | |
| 362 | * that they save themselves. | |
| 363 | */ | |
| 364 | public void saveAll() { | |
| 365 | mTabPanes.forEach( | |
| 366 | ( tp ) -> tp.getTabs().forEach( ( tab ) -> { | |
| 367 | final var node = tab.getContent(); | |
| 368 | if( node instanceof final TextEditor editor ) { | |
| 369 | save( editor ); | |
| 370 | } | |
| 371 | } ) | |
| 372 | ); | |
| 373 | } | |
| 374 | ||
| 375 | /** | |
| 376 | * Requests that the active {@link TextEditor} saves itself. Don't bother | |
| 377 | * checking if modified first because if the user swaps external media from | |
| 378 | * an external source (e.g., USB thumb drive), save should not second-guess | |
| 379 | * the user: save always re-saves. Also, it's less code. | |
| 380 | */ | |
| 381 | public void save() { | |
| 382 | save( getActiveTextEditor() ); | |
| 383 | } | |
| 384 | ||
| 385 | /** | |
| 386 | * Saves the active {@link TextEditor} under a new name. | |
| 387 | * | |
| 388 | * @param files The new active editor {@link File} reference, must contain | |
| 389 | * at least one element. | |
| 390 | */ | |
| 391 | public void saveAs( final List<File> files ) { | |
| 392 | assert files != null; | |
| 393 | assert !files.isEmpty(); | |
| 394 | final var editor = getActiveTextEditor(); | |
| 395 | final var tab = getTab( editor ); | |
| 396 | final var file = files.get( 0 ); | |
| 397 | ||
| 398 | editor.rename( file ); | |
| 399 | tab.ifPresent( t -> { | |
| 400 | t.setText( editor.getFilename() ); | |
| 401 | t.setTooltip( createTooltip( file ) ); | |
| 402 | } ); | |
| 403 | ||
| 404 | save(); | |
| 405 | } | |
| 406 | ||
| 407 | /** | |
| 408 | * Saves the given {@link TextResource} to a file. This is typically used | |
| 409 | * to save either an instance of {@link TextEditor} or {@link TextDefinition}. | |
| 410 | * | |
| 411 | * @param resource The resource to export. | |
| 412 | */ | |
| 413 | private void save( final TextResource resource ) { | |
| 414 | try { | |
| 415 | resource.save(); | |
| 416 | } catch( final Exception ex ) { | |
| 417 | clue( ex ); | |
| 418 | sNotifier.alert( | |
| 419 | getWindow(), resource.getPath(), "TextResource.saveFailed", ex | |
| 420 | ); | |
| 421 | } | |
| 422 | } | |
| 423 | ||
| 424 | /** | |
| 425 | * Closes all open {@link TextEditor}s; all {@link TextDefinition}s stay open. | |
| 426 | * | |
| 427 | * @return {@code true} when all editors, modified or otherwise, were | |
| 428 | * permitted to close; {@code false} when one or more editors were modified | |
| 429 | * and the user requested no closing. | |
| 430 | */ | |
| 431 | public boolean closeAll() { | |
| 432 | var closable = true; | |
| 433 | ||
| 434 | for( final var tabPane : mTabPanes ) { | |
| 435 | final var tabIterator = tabPane.getTabs().iterator(); | |
| 436 | ||
| 437 | while( tabIterator.hasNext() ) { | |
| 438 | final var tab = tabIterator.next(); | |
| 439 | final var resource = tab.getContent(); | |
| 440 | ||
| 441 | // The definition panes auto-save, so being specific here prevents | |
| 442 | // closing the definitions in the situation where the user wants to | |
| 443 | // continue editing (i.e., possibly save unsaved work). | |
| 444 | if( !(resource instanceof TextEditor) ) { | |
| 445 | continue; | |
| 446 | } | |
| 447 | ||
| 448 | if( canClose( (TextEditor) resource ) ) { | |
| 449 | tabIterator.remove(); | |
| 450 | close( tab ); | |
| 451 | } | |
| 452 | else { | |
| 453 | closable = false; | |
| 454 | } | |
| 455 | } | |
| 456 | } | |
| 457 | ||
| 458 | return closable; | |
| 459 | } | |
| 460 | ||
| 461 | /** | |
| 462 | * Calls the tab's {@link Tab#getOnClosed()} handler to carry out a close | |
| 463 | * event. | |
| 464 | * | |
| 465 | * @param tab The {@link Tab} that was closed. | |
| 466 | */ | |
| 467 | private void close( final Tab tab ) { | |
| 468 | final var handler = tab.getOnClosed(); | |
| 469 | ||
| 470 | if( handler != null ) { | |
| 471 | handler.handle( new ActionEvent() ); | |
| 472 | } | |
| 473 | } | |
| 474 | ||
| 475 | /** | |
| 476 | * Closes the active tab; delegates to {@link #canClose(TextResource)}. | |
| 477 | */ | |
| 478 | public void close() { | |
| 479 | final var editor = getActiveTextEditor(); | |
| 480 | ||
| 481 | if( canClose( editor ) ) { | |
| 482 | close( editor ); | |
| 483 | } | |
| 484 | } | |
| 485 | ||
| 486 | /** | |
| 487 | * Closes the given {@link TextResource}. This must not be called from within | |
| 488 | * a loop that iterates over the tab panes using {@code forEach}, lest a | |
| 489 | * concurrent modification exception be thrown. | |
| 490 | * | |
| 491 | * @param resource The {@link TextResource} to close, without confirming with | |
| 492 | * the user. | |
| 493 | */ | |
| 494 | private void close( final TextResource resource ) { | |
| 495 | getTab( resource ).ifPresent( | |
| 496 | ( tab ) -> { | |
| 497 | tab.getTabPane().getTabs().remove( tab ); | |
| 498 | close( tab ); | |
| 499 | } | |
| 500 | ); | |
| 501 | } | |
| 502 | ||
| 503 | /** | |
| 504 | * Answers whether the given {@link TextResource} may be closed. | |
| 505 | * | |
| 506 | * @param editor The {@link TextResource} to try closing. | |
| 507 | * @return {@code true} when the editor may be closed; {@code false} when | |
| 508 | * the user has requested to keep the editor open. | |
| 509 | */ | |
| 510 | private boolean canClose( final TextResource editor ) { | |
| 511 | final var editorTab = getTab( editor ); | |
| 512 | final var canClose = new AtomicBoolean( true ); | |
| 513 | ||
| 514 | if( editor.isModified() ) { | |
| 515 | final var filename = new StringBuilder(); | |
| 516 | editorTab.ifPresent( ( tab ) -> filename.append( tab.getText() ) ); | |
| 517 | ||
| 518 | final var message = sNotifier.createNotification( | |
| 519 | Messages.get( "Alert.file.close.title" ), | |
| 520 | Messages.get( "Alert.file.close.text" ), | |
| 521 | filename.toString() | |
| 522 | ); | |
| 523 | ||
| 524 | final var dialog = sNotifier.createConfirmation( getWindow(), message ); | |
| 525 | ||
| 526 | dialog.showAndWait().ifPresent( | |
| 527 | save -> canClose.set( save == YES ? editor.save() : save == NO ) | |
| 528 | ); | |
| 529 | } | |
| 530 | ||
| 531 | return canClose.get(); | |
| 532 | } | |
| 533 | ||
| 534 | private ObjectProperty<TextEditor> createActiveTextEditor() { | |
| 535 | final var editor = new SimpleObjectProperty<TextEditor>(); | |
| 536 | ||
| 537 | editor.addListener( ( c, o, n ) -> { | |
| 538 | if( n != null ) { | |
| 539 | mPreview.setBaseUri( n.getPath() ); | |
| 540 | process( n ); | |
| 541 | } | |
| 542 | } ); | |
| 543 | ||
| 544 | return editor; | |
| 545 | } | |
| 546 | ||
| 547 | /** | |
| 548 | * Adds the HTML preview tab to its own, singular tab pane. | |
| 549 | */ | |
| 550 | public void viewPreview() { | |
| 551 | viewTab( mPreview, TEXT_HTML, "Pane.preview.title" ); | |
| 552 | } | |
| 553 | ||
| 554 | /** | |
| 555 | * Adds the document outline tab to its own, singular tab pane. | |
| 556 | */ | |
| 557 | public void viewOutline() { | |
| 558 | viewTab( mOutline, APP_DOCUMENT_OUTLINE, "Pane.outline.title" ); | |
| 559 | } | |
| 560 | ||
| 561 | public void viewStatistics() { | |
| 562 | viewTab( mStatistics, APP_DOCUMENT_STATISTICS, "Pane.statistics.title" ); | |
| 563 | } | |
| 564 | ||
| 565 | public void viewFiles() { | |
| 566 | try { | |
| 567 | final var factory = new FilePickerFactory( mWorkspace ); | |
| 568 | final var fileManager = factory.createModeless(); | |
| 569 | viewTab( fileManager, APP_FILE_MANAGER, "Pane.files.title" ); | |
| 570 | } catch( final Exception ex ) { | |
| 571 | clue( ex ); | |
| 572 | } | |
| 573 | } | |
| 574 | ||
| 575 | private void viewTab( | |
| 576 | final Node node, final MediaType mediaType, final String key ) { | |
| 577 | final var tabPane = obtainTabPane( mediaType ); | |
| 578 | ||
| 579 | for( final var tab : tabPane.getTabs() ) { | |
| 580 | if( tab.getContent() == node ) { | |
| 581 | return; | |
| 582 | } | |
| 583 | } | |
| 584 | ||
| 585 | tabPane.getTabs().add( createTab( get( key ), node ) ); | |
| 586 | addTabPane( tabPane ); | |
| 587 | } | |
| 588 | ||
| 589 | public void viewRefresh() { | |
| 590 | mPreview.refresh(); | |
| 591 | } | |
| 592 | ||
| 593 | /** | |
| 594 | * Returns the tab that contains the given {@link TextEditor}. | |
| 595 | * | |
| 596 | * @param editor The {@link TextEditor} instance to find amongst the tabs. | |
| 597 | * @return The first tab having content that matches the given tab. | |
| 598 | */ | |
| 599 | private Optional<Tab> getTab( final TextResource editor ) { | |
| 600 | return mTabPanes.stream() | |
| 601 | .flatMap( pane -> pane.getTabs().stream() ) | |
| 602 | .filter( tab -> editor.equals( tab.getContent() ) ) | |
| 603 | .findFirst(); | |
| 604 | } | |
| 605 | ||
| 606 | /** | |
| 607 | * Creates a new {@link DefinitionEditor} wrapped in a listener that | |
| 608 | * is used to detect when the active {@link DefinitionEditor} has changed. | |
| 609 | * Upon changing, the {@link #mResolvedMap} is updated and the active | |
| 610 | * text editor is refreshed. | |
| 611 | * | |
| 612 | * @param editor Text editor to update with the revised resolved map. | |
| 613 | * @return A newly configured property that represents the active | |
| 614 | * {@link DefinitionEditor}, never null. | |
| 615 | */ | |
| 616 | private ObjectProperty<TextDefinition> createActiveDefinitionEditor( | |
| 617 | final ObjectProperty<TextEditor> editor ) { | |
| 618 | final var definitions = new SimpleObjectProperty<TextDefinition>(); | |
| 619 | definitions.addListener( ( c, o, n ) -> { | |
| 620 | resolve( n == null ? createDefinitionEditor() : n ); | |
| 621 | process( editor.get() ); | |
| 622 | } ); | |
| 623 | ||
| 624 | return definitions; | |
| 625 | } | |
| 626 | ||
| 627 | private Tab createTab( final String filename, final Node node ) { | |
| 628 | return new DetachableTab( filename, node ); | |
| 629 | } | |
| 630 | ||
| 631 | private Tab createTab( final File file ) { | |
| 632 | final var r = createTextResource( file ); | |
| 633 | final var tab = createTab( r.getFilename(), r.getNode() ); | |
| 634 | ||
| 635 | r.modifiedProperty().addListener( | |
| 636 | ( c, o, n ) -> tab.setText( r.getFilename() + (n ? "*" : "") ) | |
| 637 | ); | |
| 638 | ||
| 639 | // This is called when either the tab is closed by the user clicking on | |
| 640 | // the tab's close icon or when closing (all) from the file menu. | |
| 641 | tab.setOnClosed( | |
| 642 | ( __ ) -> getRecentFiles().remove( file.getAbsolutePath() ) | |
| 643 | ); | |
| 644 | ||
| 645 | tab.tabPaneProperty().addListener( ( cPane, oPane, nPane ) -> { | |
| 646 | if( nPane != null ) { | |
| 647 | nPane.focusedProperty().addListener( ( c, o, n ) -> { | |
| 648 | if( n != null && n ) { | |
| 649 | final var selected = nPane.getSelectionModel().getSelectedItem(); | |
| 650 | final var node = selected.getContent(); | |
| 651 | node.requestFocus(); | |
| 652 | } | |
| 653 | } ); | |
| 654 | } | |
| 655 | } ); | |
| 656 | ||
| 657 | return tab; | |
| 658 | } | |
| 659 | ||
| 660 | /** | |
| 661 | * Creates bins for the different {@link MediaType}s, which eventually are | |
| 662 | * added to the UI as separate tab panes. If ever a general-purpose scene | |
| 663 | * exporter is developed to serialize a scene to an FXML file, this could | |
| 664 | * be replaced by such a class. | |
| 665 | * <p> | |
| 666 | * When binning the files, this makes sure that at least one file exists | |
| 667 | * for every type. If the user has opted to close a particular type (such | |
| 668 | * as the definition pane), the view will suppressed elsewhere. | |
| 669 | * </p> | |
| 670 | * <p> | |
| 671 | * The order that the binned files are returned will be reflected in the | |
| 672 | * order that the corresponding panes are rendered in the UI. | |
| 673 | * </p> | |
| 674 | * | |
| 675 | * @param paths The file paths to bin according to their type. | |
| 676 | * @return An in-order list of files, first by structured definition files, | |
| 677 | * then by plain text documents. | |
| 678 | */ | |
| 679 | private List<File> bin( final SetProperty<String> paths ) { | |
| 680 | // Treat all files destined for the text editor as plain text documents | |
| 681 | // so that they are added to the same pane. Grouping by TEXT_PLAIN is a | |
| 682 | // bit arbitrary, but means explicitly capturing TEXT_PLAIN isn't needed. | |
| 683 | final Function<MediaType, MediaType> bin = | |
| 684 | m -> PLAIN_TEXT_FORMAT.contains( m ) ? TEXT_PLAIN : m; | |
| 685 | ||
| 686 | // Create two groups: YAML files and plain text files. | |
| 687 | final var bins = paths | |
| 688 | .stream() | |
| 689 | .collect( | |
| 690 | groupingBy( path -> bin.apply( MediaType.fromFilename( path ) ) ) | |
| 691 | ); | |
| 692 | ||
| 693 | bins.putIfAbsent( TEXT_YAML, List.of( DEFINITION_DEFAULT.toString() ) ); | |
| 694 | bins.putIfAbsent( TEXT_PLAIN, List.of( DOCUMENT_DEFAULT.toString() ) ); | |
| 695 | ||
| 696 | final var result = new ArrayList<File>( paths.size() ); | |
| 697 | ||
| 698 | // Ensure that the same types are listed together (keep insertion order). | |
| 699 | bins.forEach( ( mediaType, files ) -> result.addAll( | |
| 700 | files.stream().map( File::new ).collect( Collectors.toList() ) ) | |
| 701 | ); | |
| 702 | ||
| 703 | return result; | |
| 704 | } | |
| 705 | ||
| 706 | /** | |
| 707 | * Uses the given {@link TextDefinition} instance to update the | |
| 708 | * {@link #mResolvedMap}. | |
| 709 | * | |
| 710 | * @param editor A non-null, possibly empty definition editor. | |
| 711 | */ | |
| 712 | private void resolve( final TextDefinition editor ) { | |
| 713 | assert editor != null; | |
| 714 | ||
| 715 | final var tokens = createDefinitionTokens(); | |
| 716 | final var operator = new YamlSigilOperator( tokens ); | |
| 717 | final var map = new HashMap<String, String>(); | |
| 718 | ||
| 719 | editor.toMap().forEach( ( k, v ) -> map.put( operator.entoken( k ), v ) ); | |
| 720 | ||
| 721 | mResolvedMap.clear(); | |
| 722 | mResolvedMap.putAll( editor.interpolate( map, tokens ) ); | |
| 723 | } | |
| 724 | ||
| 725 | /** | |
| 726 | * Force the active editor to update, which will cause the processor | |
| 727 | * to re-evaluate the interpolated definition map thereby updating the | |
| 728 | * preview pane. | |
| 729 | * | |
| 730 | * @param editor Contains the source document to update in the preview pane. | |
| 731 | */ | |
| 732 | private void process( final TextEditor editor ) { | |
| 733 | // Ensure processing does not run on the JavaFX thread, which frees the | |
| 734 | // text editor immediately for caret movement. The preview will have a | |
| 735 | // slight delay when catching up to the caret position. | |
| 736 | final var task = new Task<Void>() { | |
| 737 | @Override | |
| 738 | public Void call() { | |
| 739 | try { | |
| 740 | final var p = mProcessors.getOrDefault( editor, IDENTITY ); | |
| 741 | p.apply( editor == null ? "" : editor.getText() ); | |
| 742 | } catch( final Exception ex ) { | |
| 743 | clue( ex ); | |
| 744 | } | |
| 745 | ||
| 746 | return null; | |
| 747 | } | |
| 748 | }; | |
| 749 | ||
| 750 | task.setOnSucceeded( | |
| 751 | e -> invokeLater( () -> mPreview.scrollTo( CARET_ID ) ) | |
| 752 | ); | |
| 753 | ||
| 754 | // Prevents multiple process requests from executing simultaneously (due | |
| 755 | // to having a restricted queue size). | |
| 756 | sExecutor.execute( task ); | |
| 757 | } | |
| 758 | ||
| 759 | /** | |
| 760 | * Lazily creates a {@link TabPane} configured to listen for tab select | |
| 761 | * events. The tab pane is associated with a given media type so that | |
| 762 | * similar files can be grouped together. | |
| 763 | * | |
| 764 | * @param mediaType The media type to associate with the tab pane. | |
| 765 | * @return An instance of {@link TabPane} that will handle tab docking. | |
| 766 | */ | |
| 767 | private TabPane obtainTabPane( final MediaType mediaType ) { | |
| 768 | for( final var pane : mTabPanes ) { | |
| 769 | for( final var tab : pane.getTabs() ) { | |
| 770 | final var node = tab.getContent(); | |
| 771 | ||
| 772 | if( node instanceof TextResource r && r.supports( mediaType ) ) { | |
| 773 | return pane; | |
| 774 | } | |
| 775 | } | |
| 776 | } | |
| 777 | ||
| 778 | final var pane = createTabPane(); | |
| 779 | mTabPanes.add( pane ); | |
| 780 | return pane; | |
| 781 | } | |
| 782 | ||
| 783 | /** | |
| 784 | * Creates an initialized {@link TabPane} instance. | |
| 785 | * | |
| 786 | * @return A new {@link TabPane} with all listeners configured. | |
| 787 | */ | |
| 788 | private TabPane createTabPane() { | |
| 789 | final var tabPane = new DetachableTabPane(); | |
| 790 | ||
| 791 | initStageOwnerFactory( tabPane ); | |
| 792 | initTabListener( tabPane ); | |
| 793 | ||
| 794 | return tabPane; | |
| 795 | } | |
| 796 | ||
| 797 | /** | |
| 798 | * When any {@link DetachableTabPane} is detached from the main window, | |
| 799 | * the stage owner factory must be given its parent window, which will | |
| 800 | * own the child window. The parent window is the {@link MainPane}'s | |
| 801 | * {@link Scene}'s {@link Window} instance. | |
| 802 | * | |
| 803 | * <p> | |
| 804 | * This will derives the new title from the main window title, incrementing | |
| 805 | * the window count to help uniquely identify the child windows. | |
| 806 | * </p> | |
| 807 | * | |
| 808 | * @param tabPane A new {@link DetachableTabPane} to configure. | |
| 809 | */ | |
| 810 | private void initStageOwnerFactory( final DetachableTabPane tabPane ) { | |
| 811 | tabPane.setStageOwnerFactory( ( stage ) -> { | |
| 812 | final var title = get( | |
| 813 | "Detach.tab.title", | |
| 814 | ((Stage) getWindow()).getTitle(), ++mWindowCount | |
| 815 | ); | |
| 816 | stage.setTitle( title ); | |
| 817 | ||
| 818 | return getScene().getWindow(); | |
| 819 | } ); | |
| 820 | } | |
| 821 | ||
| 822 | /** | |
| 823 | * Responsible for configuring the content of each {@link DetachableTab} when | |
| 824 | * it is added to the given {@link DetachableTabPane} instance. | |
| 825 | * <p> | |
| 826 | * For {@link TextEditor} contents, an instance of {@link ScrollEventHandler} | |
| 827 | * is initialized to perform synchronized scrolling between the editor and | |
| 828 | * its preview window. Additionally, the last tab in the tab pane's list of | |
| 829 | * tabs is given focus. | |
| 830 | * </p> | |
| 831 | * <p> | |
| 832 | * Note that multiple tabs can be added simultaneously. | |
| 833 | * </p> | |
| 834 | * | |
| 835 | * @param tabPane A new {@link TabPane} to configure. | |
| 836 | */ | |
| 837 | private void initTabListener( final TabPane tabPane ) { | |
| 838 | tabPane.getTabs().addListener( | |
| 839 | ( final ListChangeListener.Change<? extends Tab> listener ) -> { | |
| 840 | while( listener.next() ) { | |
| 841 | if( listener.wasAdded() ) { | |
| 842 | final var tabs = listener.getAddedSubList(); | |
| 843 | ||
| 844 | tabs.forEach( ( tab ) -> { | |
| 845 | final var node = tab.getContent(); | |
| 846 | ||
| 847 | if( node instanceof TextEditor ) { | |
| 848 | initScrollEventListener( tab ); | |
| 849 | } | |
| 850 | } ); | |
| 851 | ||
| 852 | // Select and give focus to the last tab opened. | |
| 853 | final var index = tabs.size() - 1; | |
| 854 | if( index >= 0 ) { | |
| 855 | final var tab = tabs.get( index ); | |
| 856 | tabPane.getSelectionModel().select( tab ); | |
| 857 | tab.getContent().requestFocus(); | |
| 858 | } | |
| 859 | } | |
| 860 | } | |
| 861 | } | |
| 862 | ); | |
| 863 | } | |
| 864 | ||
| 865 | /** | |
| 866 | * Synchronizes scrollbar positions between the given {@link Tab} that | |
| 867 | * contains an instance of {@link TextEditor} and {@link HtmlPreview} pane. | |
| 868 | * | |
| 869 | * @param tab The container for an instance of {@link TextEditor}. | |
| 870 | */ | |
| 871 | private void initScrollEventListener( final Tab tab ) { | |
| 872 | final var editor = (TextEditor) tab.getContent(); | |
| 873 | final var scrollPane = editor.getScrollPane(); | |
| 874 | final var scrollBar = mPreview.getVerticalScrollBar(); | |
| 875 | final var handler = new ScrollEventHandler( scrollPane, scrollBar ); | |
| 876 | handler.enabledProperty().bind( tab.selectedProperty() ); | |
| 877 | } | |
| 878 | ||
| 879 | private void addTabPane( final int index, final TabPane tabPane ) { | |
| 880 | final var items = getItems(); | |
| 881 | if( !items.contains( tabPane ) ) { | |
| 882 | items.add( index, tabPane ); | |
| 883 | } | |
| 884 | } | |
| 885 | ||
| 886 | private void addTabPane( final TabPane tabPane ) { | |
| 887 | addTabPane( getItems().size(), tabPane ); | |
| 888 | } | |
| 889 | ||
| 890 | public ProcessorContext createProcessorContext() { | |
| 891 | return createProcessorContext( null, NONE ); | |
| 892 | } | |
| 893 | ||
| 894 | public ProcessorContext createProcessorContext( | |
| 895 | final Path exportPath, final ExportFormat format ) { | |
| 896 | final var editor = getActiveTextEditor(); | |
| 897 | return createProcessorContext( | |
| 898 | editor.getPath(), exportPath, format, editor.getCaret() ); | |
| 899 | } | |
| 900 | ||
| 901 | private ProcessorContext createProcessorContext( | |
| 902 | final Path path, final Caret caret ) { | |
| 903 | return createProcessorContext( path, null, ExportFormat.NONE, caret ); | |
| 904 | } | |
| 905 | ||
| 906 | /** | |
| 907 | * @param path Used by {@link ProcessorFactory} to determine | |
| 908 | * {@link Processor} type to create based on file type. | |
| 909 | * @param exportPath Used when exporting to a PDF file (binary). | |
| 910 | * @param format Used when processors export to a new text format. | |
| 911 | * @param caret Used by {@link CaretExtension} to add ID attribute into | |
| 912 | * preview document for scrollbar synchronization. | |
| 913 | * @return A new {@link ProcessorContext} to use when creating an instance of | |
| 914 | * {@link Processor}. | |
| 915 | */ | |
| 916 | private ProcessorContext createProcessorContext( | |
| 917 | final Path path, final Path exportPath, final ExportFormat format, | |
| 918 | final Caret caret ) { | |
| 919 | return new ProcessorContext( | |
| 920 | mPreview, mResolvedMap, path, exportPath, format, mWorkspace, caret | |
| 921 | ); | |
| 922 | } | |
| 923 | ||
| 924 | private TextResource createTextResource( final File file ) { | |
| 925 | // TODO: Create PlainTextEditor that's returned by default. | |
| 926 | return MediaType.valueFrom( file ) == TEXT_YAML | |
| 927 | ? createDefinitionEditor( file ) | |
| 928 | : createMarkdownEditor( file ); | |
| 929 | } | |
| 930 | ||
| 931 | /** | |
| 932 | * Creates an instance of {@link MarkdownEditor} that listens for both | |
| 933 | * caret change events and text change events. Text change events must | |
| 934 | * take priority over caret change events because it's possible to change | |
| 935 | * the text without moving the caret (e.g., delete selected text). | |
| 936 | * | |
| 937 | * @param file The file containing contents for the text editor. | |
| 938 | * @return A non-null text editor. | |
| 939 | */ | |
| 940 | private TextResource createMarkdownEditor( final File file ) { | |
| 941 | final var path = file.toPath(); | |
| 942 | final var editor = new MarkdownEditor( file, getWorkspace() ); | |
| 943 | final var caret = editor.getCaret(); | |
| 944 | final var context = createProcessorContext( path, caret ); | |
| 945 | ||
| 946 | mProcessors.computeIfAbsent( editor, p -> createProcessors( context ) ); | |
| 947 | ||
| 948 | editor.addDirtyListener( ( c, o, n ) -> { | |
| 949 | if( n ) { | |
| 950 | // Reset the status to OK after changing the text. | |
| 951 | clue(); | |
| 952 | ||
| 953 | // Processing the text may update the status bar. | |
| 954 | process( getActiveTextEditor() ); | |
| 955 | } | |
| 956 | } ); | |
| 957 | ||
| 958 | editor.addEventListener( | |
| 959 | keyPressed( SPACE, CONTROL_DOWN ), this::autoinsert | |
| 960 | ); | |
| 961 | ||
| 962 | // Set the active editor, which refreshes the preview panel. | |
| 963 | mActiveTextEditor.set( editor ); | |
| 964 | ||
| 965 | return editor; | |
| 966 | } | |
| 967 | ||
| 968 | /** | |
| 969 | * Delegates to {@link #autoinsert()}. | |
| 970 | * | |
| 971 | * @param event Ignored. | |
| 972 | */ | |
| 973 | private void autoinsert( final KeyEvent event ) { | |
| 974 | autoinsert(); | |
| 975 | } | |
| 976 | ||
| 977 | /** | |
| 978 | * Finds a node that matches the word at the caret, then inserts the | |
| 979 | * corresponding definition. The definition token delimiters depend on | |
| 980 | * the type of file being edited. | |
| 981 | */ | |
| 982 | public void autoinsert() { | |
| 983 | final var definitions = getActiveTextDefinition(); | |
| 984 | final var editor = getActiveTextEditor(); | |
| 985 | final var mediaType = editor.getMediaType(); | |
| 986 | final var operator = getSigilOperator( mediaType ); | |
| 987 | ||
| 988 | DefinitionNameInjector.autoinsert( editor, definitions, operator ); | |
| 989 | } | |
| 990 | ||
| 991 | private TextDefinition createDefinitionEditor() { | |
| 992 | return createDefinitionEditor( DEFINITION_DEFAULT ); | |
| 993 | } | |
| 994 | ||
| 995 | private TextDefinition createDefinitionEditor( final File file ) { | |
| 996 | final var editor = new DefinitionEditor( file, createTreeTransformer() ); | |
| 997 | editor.addTreeChangeHandler( mTreeHandler ); | |
| 998 | return editor; | |
| 999 | } | |
| 1000 | ||
| 1001 | private TreeTransformer createTreeTransformer() { | |
| 1002 | return new YamlTreeTransformer(); | |
| 1003 | } | |
| 1004 | ||
| 1005 | private Tooltip createTooltip( final File file ) { | |
| 1006 | final var path = file.toPath(); | |
| 1007 | final var tooltip = new Tooltip( path.toString() ); | |
| 1008 | ||
| 1009 | tooltip.setShowDelay( millis( 200 ) ); | |
| 1010 | return tooltip; | |
| 1011 | } | |
| 1012 | ||
| 1013 | public TextEditor getActiveTextEditor() { | |
| 1014 | return mActiveTextEditor.get(); | |
| 1015 | } | |
| 1016 | ||
| 1017 | public ReadOnlyObjectProperty<TextEditor> activeTextEditorProperty() { | |
| 1018 | return mActiveTextEditor; | |
| 1019 | } | |
| 1020 | ||
| 1021 | public TextDefinition getActiveTextDefinition() { | |
| 1022 | return mActiveDefinitionEditor.get(); | |
| 1023 | } | |
| 1024 | ||
| 1025 | public Window getWindow() { | |
| 1026 | return getScene().getWindow(); | |
| 1027 | } | |
| 1028 | ||
| 1029 | public Workspace getWorkspace() { | |
| 1030 | return mWorkspace; | |
| 1031 | } | |
| 1032 | ||
| 1033 | /** | |
| 1034 | * Returns the sigil operator for the given {@link MediaType}. | |
| 1035 | * | |
| 1036 | * @param mediaType The type of file being edited. | |
| 1037 | */ | |
| 1038 | private SigilOperator getSigilOperator( final MediaType mediaType ) { | |
| 1039 | final var operator = new YamlSigilOperator( createDefinitionTokens() ); | |
| 1040 | ||
| 1041 | return mediaType == TEXT_R_MARKDOWN | |
| 1042 | ? new RSigilOperator( createRTokens(), operator ) | |
| 1043 | : operator; | |
| 1044 | } | |
| 1045 | ||
| 1046 | /** | |
| 1047 | * Returns the set of file names opened in the application. The names must | |
| 1048 | * be converted to {@link File} objects. | |
| 1049 | * | |
| 1050 | * @return A {@link Set} of file names. | |
| 1051 | */ | |
| 1052 | private SetProperty<String> getRecentFiles() { | |
| 1053 | return getWorkspace().setsProperty( KEY_UI_FILES_PATH ); | |
| 1054 | } | |
| 1055 | ||
| 1056 | private StringProperty stringProperty( final Key key ) { | |
| 1057 | return getWorkspace().stringProperty( key ); | |
| 1058 | } | |
| 1059 | ||
| 1060 | private Tokens createRTokens() { | |
| 1061 | return createTokens( KEY_R_DELIM_BEGAN, KEY_R_DELIM_ENDED ); | |
| 1062 | } | |
| 1063 | ||
| 1064 | private Tokens createDefinitionTokens() { | |
| 1065 | return createTokens( KEY_DEF_DELIM_BEGAN, KEY_DEF_DELIM_ENDED ); | |
| 1066 | } | |
| 1067 | ||
| 1068 | private Tokens createTokens( final Key began, final Key ended ) { | |
| 1069 | return new Tokens( stringProperty( began ), stringProperty( ended ) ); | |
| 1070 | } | |
| 1071 | } | |
| 1 | 1072 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.io.FileModifiedListener; | |
| 5 | import com.keenwrite.io.FileWatchService; | |
| 6 | import com.keenwrite.preferences.Workspace; | |
| 7 | import com.keenwrite.ui.actions.ApplicationActions; | |
| 8 | import com.keenwrite.ui.listeners.CaretListener; | |
| 9 | import javafx.scene.Node; | |
| 10 | import javafx.scene.Parent; | |
| 11 | import javafx.scene.Scene; | |
| 12 | import javafx.scene.control.MenuBar; | |
| 13 | import javafx.scene.layout.BorderPane; | |
| 14 | import javafx.scene.layout.VBox; | |
| 15 | import org.controlsfx.control.StatusBar; | |
| 16 | ||
| 17 | import java.io.File; | |
| 18 | ||
| 19 | import static com.keenwrite.Messages.get; | |
| 20 | import static com.keenwrite.constants.Constants.*; | |
| 21 | import static com.keenwrite.events.ScrollLockEvent.fireScrollLockEvent; | |
| 22 | import static com.keenwrite.events.StatusEvent.clue; | |
| 23 | import static com.keenwrite.preferences.SkinProperty.toFilename; | |
| 24 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_UI_SKIN_CUSTOM; | |
| 25 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_UI_SKIN_SELECTION; | |
| 26 | import static com.keenwrite.ui.actions.ApplicationBars.*; | |
| 27 | import static javafx.application.Platform.runLater; | |
| 28 | import static javafx.scene.input.KeyCode.*; | |
| 29 | import static javafx.scene.input.KeyEvent.KEY_PRESSED; | |
| 30 | import static javafx.scene.input.KeyEvent.KEY_RELEASED; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for creating the bar scene: menu bar, tool bar, and status bar. | |
| 34 | */ | |
| 35 | public final class MainScene { | |
| 36 | private final Scene mScene; | |
| 37 | private final MenuBar mMenuBar; | |
| 38 | private final Node mToolBar; | |
| 39 | private final StatusBar mStatusBar; | |
| 40 | private final FileWatchService mFileWatchService = new FileWatchService(); | |
| 41 | private FileModifiedListener mStylesheetFileListener = event -> {}; | |
| 42 | ||
| 43 | public MainScene( final Workspace workspace ) { | |
| 44 | final var mainPane = createMainPane( workspace ); | |
| 45 | final var actions = createApplicationActions( mainPane ); | |
| 46 | final var caretListener = createCaretListener( mainPane ); | |
| 47 | mMenuBar = setManagedLayout( createMenuBar( actions ) ); | |
| 48 | mToolBar = setManagedLayout( createToolBar() ); | |
| 49 | mStatusBar = setManagedLayout( createStatusBar() ); | |
| 50 | ||
| 51 | mStatusBar.getRightItems().add( caretListener ); | |
| 52 | ||
| 53 | final var appPane = new BorderPane(); | |
| 54 | appPane.setTop( new VBox( mMenuBar, mToolBar ) ); | |
| 55 | appPane.setCenter( mainPane ); | |
| 56 | appPane.setBottom( mStatusBar ); | |
| 57 | ||
| 58 | final var watchThread = new Thread( mFileWatchService ); | |
| 59 | watchThread.setDaemon( true ); | |
| 60 | watchThread.start(); | |
| 61 | ||
| 62 | mScene = createScene( appPane ); | |
| 63 | initStylesheets( mScene, workspace ); | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Called by the {@link MainApp} to get a handle on the {@link Scene} | |
| 68 | * created by an instance of {@link MainScene}. | |
| 69 | * | |
| 70 | * @return The {@link Scene} created at construction time. | |
| 71 | */ | |
| 72 | public Scene getScene() { | |
| 73 | return mScene; | |
| 74 | } | |
| 75 | ||
| 76 | public void toggleMenuBar() { | |
| 77 | final var node = mMenuBar; | |
| 78 | node.setVisible( !node.isVisible() ); | |
| 79 | } | |
| 80 | ||
| 81 | public void toggleToolBar() { | |
| 82 | final var node = mToolBar; | |
| 83 | node.setVisible( !node.isVisible() ); | |
| 84 | } | |
| 85 | ||
| 86 | public void toggleStatusBar() { | |
| 87 | final var node = mStatusBar; | |
| 88 | node.setVisible( !node.isVisible() ); | |
| 89 | } | |
| 90 | ||
| 91 | MenuBar getMenuBar() { | |
| 92 | return mMenuBar; | |
| 93 | } | |
| 94 | ||
| 95 | public StatusBar getStatusBar() { return mStatusBar; } | |
| 96 | ||
| 97 | private void initStylesheets( final Scene scene, final Workspace workspace ) { | |
| 98 | final var internal = workspace.skinProperty( KEY_UI_SKIN_SELECTION ); | |
| 99 | final var external = workspace.fileProperty( KEY_UI_SKIN_CUSTOM ); | |
| 100 | final var inSkin = internal.get(); | |
| 101 | final var exSkin = external.get(); | |
| 102 | applyStylesheets( scene, inSkin, exSkin ); | |
| 103 | ||
| 104 | internal.addListener( | |
| 105 | ( c, o, n ) -> { | |
| 106 | if( n != null ) { | |
| 107 | applyStylesheets( scene, n, exSkin ); | |
| 108 | } | |
| 109 | } | |
| 110 | ); | |
| 111 | ||
| 112 | external.addListener( | |
| 113 | ( c, o, n ) -> { | |
| 114 | if( o != null ) { | |
| 115 | mFileWatchService.unregister( o ); | |
| 116 | } | |
| 117 | ||
| 118 | if( n != null ) { | |
| 119 | try { | |
| 120 | applyStylesheets( scene, inSkin, n ); | |
| 121 | } catch( final Exception ex ) { | |
| 122 | // Changes to the CSS file won't autoload, which is okay. | |
| 123 | clue( ex ); | |
| 124 | } | |
| 125 | } | |
| 126 | } | |
| 127 | ); | |
| 128 | ||
| 129 | mFileWatchService.removeListener( mStylesheetFileListener ); | |
| 130 | mStylesheetFileListener = event -> | |
| 131 | runLater( () -> applyStylesheets( scene, inSkin, event.getFile() ) ); | |
| 132 | mFileWatchService.addListener( mStylesheetFileListener ); | |
| 133 | } | |
| 134 | ||
| 135 | private String getStylesheet( final String filename ) { | |
| 136 | return get( STYLESHEET_APPLICATION_SKIN, filename ); | |
| 137 | } | |
| 138 | ||
| 139 | /** | |
| 140 | * Clears then re-applies all the internal stylesheets. | |
| 141 | * | |
| 142 | * @param scene The scene to stylize. | |
| 143 | * @param internal The CSS file name bundled with the application. | |
| 144 | * @param external The (optional) customized CSS file specified by the user. | |
| 145 | */ | |
| 146 | private void applyStylesheets( | |
| 147 | final Scene scene, final String internal, final File external ) { | |
| 148 | final var stylesheets = scene.getStylesheets(); | |
| 149 | stylesheets.clear(); | |
| 150 | stylesheets.add( STYLESHEET_APPLICATION_BASE ); | |
| 151 | stylesheets.add( STYLESHEET_MARKDOWN ); | |
| 152 | stylesheets.add( getStylesheet( toFilename( internal ) ) ); | |
| 153 | ||
| 154 | try { | |
| 155 | if( external != null && external.canRead() && !external.isDirectory() ) { | |
| 156 | stylesheets.add( external.toURI().toURL().toString() ); | |
| 157 | mFileWatchService.register( external ); | |
| 158 | } | |
| 159 | } catch( final Exception ex ) { | |
| 160 | clue( ex ); | |
| 161 | } | |
| 162 | } | |
| 163 | ||
| 164 | private MainPane createMainPane( final Workspace workspace ) { | |
| 165 | return new MainPane( workspace ); | |
| 166 | } | |
| 167 | ||
| 168 | private ApplicationActions createApplicationActions( | |
| 169 | final MainPane mainPane ) { | |
| 170 | return new ApplicationActions( this, mainPane ); | |
| 171 | } | |
| 172 | ||
| 173 | /** | |
| 174 | * Creates the class responsible for updating the UI with the caret position | |
| 175 | * based on the active text editor. | |
| 176 | * | |
| 177 | * @return The {@link CaretListener} responsible for updating the | |
| 178 | * {@link StatusBar} whenever the caret changes position. | |
| 179 | */ | |
| 180 | private CaretListener createCaretListener( final MainPane mainPane ) { | |
| 181 | return new CaretListener( mainPane.activeTextEditorProperty() ); | |
| 182 | } | |
| 183 | ||
| 184 | /** | |
| 185 | * Creates a new scene that is attached to the given {@link Parent}. | |
| 186 | * | |
| 187 | * @param parent The container for the scene. | |
| 188 | * @return A scene to capture user interactions, UI styles, etc. | |
| 189 | */ | |
| 190 | private Scene createScene( final Parent parent ) { | |
| 191 | final var scene = new Scene( parent ); | |
| 192 | ||
| 193 | // After the app loses focus, when the user switches back using Alt+Tab, | |
| 194 | // the menu is sometimes engaged. See MainApp::initStage(). | |
| 195 | // | |
| 196 | // JavaFX Bug: https://bugs.openjdk.java.net/browse/JDK-8090647 | |
| 197 | scene.addEventHandler( KEY_PRESSED, event -> { | |
| 198 | // Only consume lone ALT key press events. If the modifier is used in | |
| 199 | // combination with another key, don't consume the event. First check | |
| 200 | // if ALT is down before getting the key code as a micro-optimization. | |
| 201 | if( event.isAltDown() ) { | |
| 202 | if( event.getCode() == ALT || event.getCode() == ALT_GRAPH ) { | |
| 203 | event.consume(); | |
| 204 | } | |
| 205 | } | |
| 206 | } ); | |
| 207 | ||
| 208 | // Update the synchronized scrolling status when user presses scroll lock. | |
| 209 | scene.addEventHandler( KEY_RELEASED, event -> { | |
| 210 | if( event.getCode() == SCROLL_LOCK ) { | |
| 211 | fireScrollLockEvent(); | |
| 212 | } | |
| 213 | } ); | |
| 214 | ||
| 215 | return scene; | |
| 216 | } | |
| 217 | ||
| 218 | /** | |
| 219 | * Binds the visible property of the node to the managed property so that | |
| 220 | * hiding the node also removes the screen real estate that it occupies. | |
| 221 | * This allows the user to hide the menu bar, tool bar, etc. | |
| 222 | * | |
| 223 | * @param node The node to have its real estate bound to visibility. | |
| 224 | * @return The given node for fluent-like convenience. | |
| 225 | */ | |
| 226 | private <T extends Node> T setManagedLayout( final T node ) { | |
| 227 | node.managedProperty().bind( node.visibleProperty() ); | |
| 228 | return node; | |
| 229 | } | |
| 230 | } | |
| 1 | 231 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.preferences.Key; | |
| 5 | ||
| 6 | import java.text.MessageFormat; | |
| 7 | import java.util.Enumeration; | |
| 8 | import java.util.ResourceBundle; | |
| 9 | import java.util.Stack; | |
| 10 | ||
| 11 | import static com.keenwrite.constants.Constants.APP_BUNDLE_NAME; | |
| 12 | import static java.util.ResourceBundle.getBundle; | |
| 13 | ||
| 14 | /** | |
| 15 | * Recursively resolves message properties. Property values can refer to other | |
| 16 | * properties using a <code>${var}</code> syntax. | |
| 17 | */ | |
| 18 | public final class Messages { | |
| 19 | ||
| 20 | private static final ResourceBundle RESOURCE_BUNDLE = | |
| 21 | getBundle( APP_BUNDLE_NAME ); | |
| 22 | ||
| 23 | private Messages() { | |
| 24 | } | |
| 25 | ||
| 26 | /** | |
| 27 | * Return the value of a resource bundle value after having resolved any | |
| 28 | * references to other bundle variables. | |
| 29 | * | |
| 30 | * @param props The bundle containing resolvable properties. | |
| 31 | * @param s The value for a key to resolve. | |
| 32 | * @return The value of the key with all references recursively dereferenced. | |
| 33 | */ | |
| 34 | @SuppressWarnings( "SameParameterValue" ) | |
| 35 | private static String resolve( final ResourceBundle props, final String s ) { | |
| 36 | final var len = s.length(); | |
| 37 | final var stack = new Stack<StringBuilder>(); | |
| 38 | var sb = new StringBuilder( 256 ); | |
| 39 | var open = false; | |
| 40 | ||
| 41 | for( var i = 0; i < len; i++ ) { | |
| 42 | final var c = s.charAt( i ); | |
| 43 | ||
| 44 | switch( c ) { | |
| 45 | case '$': { | |
| 46 | if( i + 1 < len && s.charAt( i + 1 ) == '{' ) { | |
| 47 | stack.push( sb ); | |
| 48 | ||
| 49 | if( stack.size() > 20 ) { | |
| 50 | final var m = get( "Main.status.error.messages.recursion", s ); | |
| 51 | throw new IllegalArgumentException( m ); | |
| 52 | } | |
| 53 | ||
| 54 | sb = new StringBuilder( 256 ); | |
| 55 | i++; | |
| 56 | open = true; | |
| 57 | } | |
| 58 | ||
| 59 | break; | |
| 60 | } | |
| 61 | ||
| 62 | case '}': { | |
| 63 | if( open ) { | |
| 64 | open = false; | |
| 65 | final var name = sb.toString(); | |
| 66 | ||
| 67 | sb = stack.pop(); | |
| 68 | sb.append( props.getString( name ) ); | |
| 69 | break; | |
| 70 | } | |
| 71 | } | |
| 72 | ||
| 73 | default: { | |
| 74 | sb.append( c ); | |
| 75 | break; | |
| 76 | } | |
| 77 | } | |
| 78 | } | |
| 79 | ||
| 80 | if( open ) { | |
| 81 | final var m = get( "Main.status.error.messages.syntax", s ); | |
| 82 | throw new IllegalArgumentException( m ); | |
| 83 | } | |
| 84 | ||
| 85 | return sb.toString(); | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Returns the value for a key from the message bundle. | |
| 90 | * | |
| 91 | * @param key Retrieve the value for this key. | |
| 92 | * @return The value for the key. | |
| 93 | */ | |
| 94 | public static String get( final String key ) { | |
| 95 | try { | |
| 96 | return resolve( RESOURCE_BUNDLE, RESOURCE_BUNDLE.getString( key ) ); | |
| 97 | } catch( final Exception ignored ) { | |
| 98 | return key; | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | /** | |
| 103 | * Returns the value for a key from the message bundle. | |
| 104 | * | |
| 105 | * @param key Retrieve the value for this key. | |
| 106 | * @return The value for the key. | |
| 107 | */ | |
| 108 | public static String get( final Key key ) { | |
| 109 | return get( key.toString() ); | |
| 110 | } | |
| 111 | ||
| 112 | public static String getLiteral( final String key ) { | |
| 113 | return RESOURCE_BUNDLE.getString( key ); | |
| 114 | } | |
| 115 | ||
| 116 | public static String get( final String key, final boolean interpolate ) { | |
| 117 | return interpolate ? get( key ) : getLiteral( key ); | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 121 | * Returns the value for a key from the message bundle with the arguments | |
| 122 | * replacing <code>{#}</code> place holders. | |
| 123 | * | |
| 124 | * @param key Retrieve the value for this key. | |
| 125 | * @param args The values to substitute for place holders. | |
| 126 | * @return The value for the key. | |
| 127 | */ | |
| 128 | public static String get( final String key, final Object... args ) { | |
| 129 | return MessageFormat.format( get( key ), args ); | |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Answers whether the given key is contained in the application's messages | |
| 134 | * properties file. | |
| 135 | * | |
| 136 | * @param key The key to look for in the {@link ResourceBundle}. | |
| 137 | * @return {@code true} when the key exists as an exact match. | |
| 138 | */ | |
| 139 | public static boolean containsKey( final String key ) { | |
| 140 | return RESOURCE_BUNDLE.containsKey( key ); | |
| 141 | } | |
| 142 | ||
| 143 | /** | |
| 144 | * Returns all key names in the application's messages properties file. | |
| 145 | * | |
| 146 | * @return All key names in the {@link ResourceBundle} encapsulated by | |
| 147 | * this class. | |
| 148 | */ | |
| 149 | public static Enumeration<String> getKeys() { | |
| 150 | return RESOURCE_BUNDLE.getKeys(); | |
| 151 | } | |
| 152 | } | |
| 1 | 153 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import javax.net.ssl.*; | |
| 5 | import java.security.SecureRandom; | |
| 6 | import java.security.cert.X509Certificate; | |
| 7 | ||
| 8 | import static javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier; | |
| 9 | import static javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for trusting all certificate chains. The purpose of this class | |
| 13 | * is to work-around certificate issues caused by software that blocks | |
| 14 | * HTTP requests. For example, zscaler may block HTTP requests to kroki.io | |
| 15 | * when generating diagrams. | |
| 16 | */ | |
| 17 | public final class PermissiveCertificate { | |
| 18 | /** | |
| 19 | * Create a trust manager that does not validate certificate chains. | |
| 20 | */ | |
| 21 | private final static TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{ | |
| 22 | new X509TrustManager() { | |
| 23 | @Override | |
| 24 | public X509Certificate[] getAcceptedIssuers() { | |
| 25 | return new X509Certificate[ 0 ]; | |
| 26 | } | |
| 27 | ||
| 28 | @Override | |
| 29 | public void checkClientTrusted( | |
| 30 | X509Certificate[] certs, String authType ) { | |
| 31 | } | |
| 32 | ||
| 33 | @Override | |
| 34 | public void checkServerTrusted( | |
| 35 | X509Certificate[] certs, String authType ) { | |
| 36 | } | |
| 37 | } | |
| 38 | }; | |
| 39 | ||
| 40 | /** | |
| 41 | * Responsible for permitting all hostnames for making HTTP requests. | |
| 42 | */ | |
| 43 | private static class PermissiveHostNameVerifier implements HostnameVerifier { | |
| 44 | @Override | |
| 45 | public boolean verify( final String hostname, final SSLSession session ) { | |
| 46 | return true; | |
| 47 | } | |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Use {@link #installTrustManager()}. | |
| 52 | */ | |
| 53 | private PermissiveCertificate() { | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Install the all-trusting trust manager. If this fails it means that in | |
| 58 | * certain situations the HTML preview may fail to render diagrams. A way | |
| 59 | * to work-around the issue is to install a local server for generating | |
| 60 | * diagrams. | |
| 61 | */ | |
| 62 | public static boolean installTrustManager() { | |
| 63 | try { | |
| 64 | final var context = SSLContext.getInstance( "SSL" ); | |
| 65 | context.init( null, TRUST_ALL_CERTS, new SecureRandom() ); | |
| 66 | setDefaultSSLSocketFactory( context.getSocketFactory() ); | |
| 67 | setDefaultHostnameVerifier( new PermissiveHostNameVerifier() ); | |
| 68 | return true; | |
| 69 | } catch( final Exception ex ) { | |
| 70 | return false; | |
| 71 | } | |
| 72 | } | |
| 73 | } | |
| 1 | 74 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import com.keenwrite.events.ScrollLockEvent; | |
| 5 | import javafx.beans.property.BooleanProperty; | |
| 6 | import javafx.beans.property.SimpleBooleanProperty; | |
| 7 | import javafx.event.Event; | |
| 8 | import javafx.event.EventHandler; | |
| 9 | import javafx.scene.control.ScrollBar; | |
| 10 | import javafx.scene.control.skin.ScrollBarSkin; | |
| 11 | import javafx.scene.input.MouseEvent; | |
| 12 | import javafx.scene.input.ScrollEvent; | |
| 13 | import javafx.scene.layout.StackPane; | |
| 14 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 15 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 16 | import org.greenrobot.eventbus.Subscribe; | |
| 17 | ||
| 18 | import javax.swing.*; | |
| 19 | import java.util.function.Consumer; | |
| 20 | ||
| 21 | import static com.keenwrite.events.Bus.register; | |
| 22 | import static java.lang.Math.max; | |
| 23 | import static java.lang.Math.min; | |
| 24 | import static javafx.geometry.Orientation.VERTICAL; | |
| 25 | import static javax.swing.SwingUtilities.invokeLater; | |
| 26 | ||
| 27 | /** | |
| 28 | * Converts scroll events from {@link VirtualizedScrollPane} scroll bars to | |
| 29 | * an instance of {@link JScrollBar}. | |
| 30 | * <p> | |
| 31 | * Called to synchronize the scrolling areas for either scrolling with the | |
| 32 | * mouse or scrolling using the scrollbar's thumb. Both are required to avoid | |
| 33 | * scrolling on the estimatedScrollYProperty that occurs when text events | |
| 34 | * fire. Scrolling performed for text events are handled separately to ensure | |
| 35 | * the preview panel scrolls to the same position in the Markdown editor, | |
| 36 | * taking into account things like images, tables, and other potentially | |
| 37 | * long vertical presentation items. | |
| 38 | * </p> | |
| 39 | */ | |
| 40 | public final class ScrollEventHandler implements EventHandler<Event> { | |
| 41 | ||
| 42 | private final class MouseHandler implements EventHandler<MouseEvent> { | |
| 43 | private final EventHandler<? super MouseEvent> mOldHandler; | |
| 44 | ||
| 45 | /** | |
| 46 | * Constructs a new handler for mouse scrolling events. | |
| 47 | * | |
| 48 | * @param oldHandler Receives the event after scrolling takes place. | |
| 49 | */ | |
| 50 | private MouseHandler( final EventHandler<? super MouseEvent> oldHandler ) { | |
| 51 | mOldHandler = oldHandler; | |
| 52 | } | |
| 53 | ||
| 54 | @Override | |
| 55 | public void handle( final MouseEvent event ) { | |
| 56 | ScrollEventHandler.this.handle( event ); | |
| 57 | mOldHandler.handle( event ); | |
| 58 | } | |
| 59 | } | |
| 60 | ||
| 61 | private final class ScrollHandler implements EventHandler<ScrollEvent> { | |
| 62 | @Override | |
| 63 | public void handle( final ScrollEvent event ) { | |
| 64 | ScrollEventHandler.this.handle( event ); | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | private final VirtualizedScrollPane<StyleClassedTextArea> mEditorScrollPane; | |
| 69 | private final JScrollBar mPreviewScrollBar; | |
| 70 | private final BooleanProperty mEnabled = new SimpleBooleanProperty(); | |
| 71 | ||
| 72 | private boolean mLocked; | |
| 73 | ||
| 74 | /** | |
| 75 | * @param editorScrollPane Scroll event source (human movement). | |
| 76 | * @param previewScrollBar Scroll event destination (corresponding movement). | |
| 77 | */ | |
| 78 | public ScrollEventHandler( | |
| 79 | final VirtualizedScrollPane<StyleClassedTextArea> editorScrollPane, | |
| 80 | final JScrollBar previewScrollBar ) { | |
| 81 | mEditorScrollPane = editorScrollPane; | |
| 82 | mPreviewScrollBar = previewScrollBar; | |
| 83 | ||
| 84 | mEditorScrollPane.addEventFilter( ScrollEvent.ANY, new ScrollHandler() ); | |
| 85 | ||
| 86 | initVerticalScrollBarThumb( | |
| 87 | mEditorScrollPane, | |
| 88 | thumb -> { | |
| 89 | final var handler = new MouseHandler( thumb.getOnMouseDragged() ); | |
| 90 | thumb.setOnMouseDragged( handler ); | |
| 91 | } | |
| 92 | ); | |
| 93 | ||
| 94 | register( this ); | |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * Gets a property intended to be bound to selected property of the tab being | |
| 99 | * scrolled. This is required because there's only one preview pane but | |
| 100 | * multiple editor panes. Each editor pane maintains its own scroll position. | |
| 101 | * | |
| 102 | * @return A {@link BooleanProperty} representing whether the scroll | |
| 103 | * events for this tab are to be executed. | |
| 104 | */ | |
| 105 | public BooleanProperty enabledProperty() { | |
| 106 | return mEnabled; | |
| 107 | } | |
| 108 | ||
| 109 | /** | |
| 110 | * Scrolls the preview scrollbar relative to the edit scrollbar. Algorithm | |
| 111 | * is based on Karl Tauber's ratio calculation. | |
| 112 | * | |
| 113 | * @param event Unused; either {@link MouseEvent} or {@link ScrollEvent} | |
| 114 | */ | |
| 115 | @Override | |
| 116 | public void handle( final Event event ) { | |
| 117 | invokeLater( () -> { | |
| 118 | if( isEnabled() ) { | |
| 119 | // e is for editor pane | |
| 120 | final var eScrollPane = getEditorScrollPane(); | |
| 121 | final var eScrollY = | |
| 122 | eScrollPane.estimatedScrollYProperty().getValue().intValue(); | |
| 123 | final var eHeight = (int) | |
| 124 | (eScrollPane.totalHeightEstimateProperty().getValue().intValue() | |
| 125 | - eScrollPane.getHeight()); | |
| 126 | final var eRatio = eHeight > 0 | |
| 127 | ? min( max( eScrollY / (float) eHeight, 0 ), 1 ) : 0; | |
| 128 | ||
| 129 | // p is for preview pane | |
| 130 | final var pScrollBar = getPreviewScrollBar(); | |
| 131 | final var pHeight = pScrollBar.getMaximum() - pScrollBar.getHeight(); | |
| 132 | final var pScrollY = (int) (pHeight * eRatio); | |
| 133 | ||
| 134 | pScrollBar.setValue( pScrollY ); | |
| 135 | pScrollBar.getParent().repaint(); | |
| 136 | } | |
| 137 | } ); | |
| 138 | } | |
| 139 | ||
| 140 | @Subscribe | |
| 141 | public void handle( final ScrollLockEvent event ) { | |
| 142 | mLocked = event.isLocked(); | |
| 143 | } | |
| 144 | ||
| 145 | private void initVerticalScrollBarThumb( | |
| 146 | final VirtualizedScrollPane<StyleClassedTextArea> pane, | |
| 147 | final Consumer<StackPane> consumer ) { | |
| 148 | // When the skin property is set, the stack pane is available (not null). | |
| 149 | getVerticalScrollBar( pane ).skinProperty().addListener( ( c, o, n ) -> { | |
| 150 | for( final var node : ((ScrollBarSkin) n).getChildren() ) { | |
| 151 | // Brittle, but what can you do? | |
| 152 | if( node.getStyleClass().contains( "thumb" ) ) { | |
| 153 | consumer.accept( (StackPane) node ); | |
| 154 | } | |
| 155 | } | |
| 156 | } ); | |
| 157 | } | |
| 158 | ||
| 159 | /** | |
| 160 | * Returns the vertical {@link ScrollBar} instance associated with the | |
| 161 | * given scroll pane. This is {@code null}-safe because the scroll pane | |
| 162 | * initializes its vertical {@link ScrollBar} upon construction. | |
| 163 | * | |
| 164 | * @param pane The scroll pane that contains a vertical {@link ScrollBar}. | |
| 165 | * @return The vertical {@link ScrollBar} associated with the scroll pane. | |
| 166 | * @throws IllegalStateException Could not obtain the vertical scroll bar. | |
| 167 | */ | |
| 168 | private ScrollBar getVerticalScrollBar( | |
| 169 | final VirtualizedScrollPane<StyleClassedTextArea> pane ) { | |
| 170 | ||
| 171 | for( final var node : pane.getChildrenUnmodifiable() ) { | |
| 172 | if( node instanceof final ScrollBar scrollBar && | |
| 173 | scrollBar.getOrientation() == VERTICAL ) { | |
| 174 | return scrollBar; | |
| 175 | } | |
| 176 | } | |
| 177 | ||
| 178 | throw new IllegalStateException( "No vertical scroll bar found." ); | |
| 179 | } | |
| 180 | ||
| 181 | private boolean isEnabled() { | |
| 182 | // TODO: As a minor optimization, when this is set to false, it could remove | |
| 183 | // the MouseHandler and ScrollHandler so that events only dispatch to one | |
| 184 | // object (instead of one per editor tab). | |
| 185 | return mEnabled.get() && !mLocked; | |
| 186 | } | |
| 187 | ||
| 188 | private VirtualizedScrollPane<StyleClassedTextArea> getEditorScrollPane() { | |
| 189 | return mEditorScrollPane; | |
| 190 | } | |
| 191 | ||
| 192 | private JScrollBar getPreviewScrollBar() { | |
| 193 | return mPreviewScrollBar; | |
| 194 | } | |
| 195 | } | |
| 1 | 196 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import java.util.HashMap; | |
| 5 | import java.util.Map; | |
| 6 | import java.util.ServiceLoader; | |
| 7 | ||
| 8 | /** | |
| 9 | * Responsible for loading services. The services are treated as singleton | |
| 10 | * instances. | |
| 11 | */ | |
| 12 | public class Services { | |
| 13 | ||
| 14 | @SuppressWarnings("rawtypes") | |
| 15 | private static final Map<Class, Object> SINGLETONS = new HashMap<>(); | |
| 16 | ||
| 17 | /** | |
| 18 | * Loads a service based on its interface definition. This will return an | |
| 19 | * existing instance if the class has already been instantiated. | |
| 20 | * | |
| 21 | * @param <T> The service to load. | |
| 22 | * @param api The interface definition for the service. | |
| 23 | * @return A class that implements the interface. | |
| 24 | */ | |
| 25 | @SuppressWarnings("unchecked") | |
| 26 | public static <T> T load( final Class<T> api ) { | |
| 27 | final T o = (T) get( api ); | |
| 28 | ||
| 29 | return o == null ? newInstance( api ) : o; | |
| 30 | } | |
| 31 | ||
| 32 | private static <T> T newInstance( final Class<T> api ) { | |
| 33 | final ServiceLoader<T> services = ServiceLoader.load( api ); | |
| 34 | ||
| 35 | for( final T service : services ) { | |
| 36 | if( service != null ) { | |
| 37 | // Re-use the same instance the next time the class is loaded. | |
| 38 | put( api, service ); | |
| 39 | return service; | |
| 40 | } | |
| 41 | } | |
| 42 | ||
| 43 | throw new RuntimeException( "No implementation for: " + api ); | |
| 44 | } | |
| 45 | ||
| 46 | @SuppressWarnings("rawtypes") | |
| 47 | private static void put( final Class key, Object value ) { | |
| 48 | SINGLETONS.put( key, value ); | |
| 49 | } | |
| 50 | ||
| 51 | @SuppressWarnings("rawtypes") | |
| 52 | private static Object get( final Class api ) { | |
| 53 | return SINGLETONS.get( api ); | |
| 54 | } | |
| 55 | } | |
| 1 | 56 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.constants; | |
| 3 | ||
| 4 | import com.keenwrite.Bootstrap; | |
| 5 | import com.keenwrite.Services; | |
| 6 | import com.keenwrite.service.Settings; | |
| 7 | ||
| 8 | import java.io.File; | |
| 9 | import java.nio.charset.Charset; | |
| 10 | import java.nio.file.Path; | |
| 11 | import java.util.Locale; | |
| 12 | ||
| 13 | import static com.keenwrite.Bootstrap.APP_TITLE_LOWERCASE; | |
| 14 | import static com.keenwrite.preferences.LocaleScripts.withScript; | |
| 15 | import static java.io.File.separator; | |
| 16 | import static java.lang.String.format; | |
| 17 | import static java.lang.System.getProperty; | |
| 18 | ||
| 19 | /** | |
| 20 | * Defines application-wide default values. | |
| 21 | */ | |
| 22 | public final class Constants { | |
| 23 | ||
| 24 | /** | |
| 25 | * Used by the default settings to load the {@link Settings} service. This | |
| 26 | * must come before any attempt is made to create a {@link Settings} object. | |
| 27 | * The reference to {@link Bootstrap#APP_TITLE_LOWERCASE} should cause the | |
| 28 | * JVM to load {@link Bootstrap} prior to proceeding. Loading that class | |
| 29 | * beforehand will read the bootstrap properties file to determine the | |
| 30 | * application name, which is then used to locate the settings properties. | |
| 31 | */ | |
| 32 | public static final String PATH_PROPERTIES_SETTINGS = | |
| 33 | format( "/com/%s/settings.properties", APP_TITLE_LOWERCASE ); | |
| 34 | ||
| 35 | /** | |
| 36 | * The {@link Settings} uses {@link #PATH_PROPERTIES_SETTINGS}. | |
| 37 | */ | |
| 38 | public static final Settings sSettings = Services.load( Settings.class ); | |
| 39 | ||
| 40 | public static final double WINDOW_X_DEFAULT = 0; | |
| 41 | public static final double WINDOW_Y_DEFAULT = 0; | |
| 42 | public static final double WINDOW_W_DEFAULT = 1200; | |
| 43 | public static final double WINDOW_H_DEFAULT = 800; | |
| 44 | ||
| 45 | public static final File DOCUMENT_DEFAULT = getFile( "document" ); | |
| 46 | public static final File DEFINITION_DEFAULT = getFile( "definition" ); | |
| 47 | ||
| 48 | public static final String APP_BUNDLE_NAME = get( "application.messages" ); | |
| 49 | ||
| 50 | public static final String STYLESHEET_APPLICATION_BASE = | |
| 51 | get( "file.stylesheet.application.base" ); | |
| 52 | public static final String STYLESHEET_APPLICATION_SKIN = | |
| 53 | get( "file.stylesheet.application.skin" ); | |
| 54 | public static final String STYLESHEET_MARKDOWN = | |
| 55 | get( "file.stylesheet.markdown" ); | |
| 56 | public static final String STYLESHEET_MARKDOWN_LOCALE = | |
| 57 | "file.stylesheet.markdown.locale"; | |
| 58 | public static final String STYLESHEET_PREVIEW = | |
| 59 | get( "file.stylesheet.preview" ); | |
| 60 | public static final String STYLESHEET_PREVIEW_LOCALE = | |
| 61 | "file.stylesheet.preview.locale"; | |
| 62 | ||
| 63 | public static final String FILE_PREFERENCES = getPreferencesFilename(); | |
| 64 | ||
| 65 | /** | |
| 66 | * Refer to file name extension settings in the configuration file. Do not | |
| 67 | * terminate with a period. | |
| 68 | */ | |
| 69 | public static final String GLOB_PREFIX_FILE = "file.ext"; | |
| 70 | ||
| 71 | /** | |
| 72 | * Three parameters: line number, column number, and offset. | |
| 73 | */ | |
| 74 | public static final String STATUS_BAR_LINE = "Main.status.line"; | |
| 75 | ||
| 76 | public static final String STATUS_BAR_OK = "Main.status.state.default"; | |
| 77 | ||
| 78 | /** | |
| 79 | * Used to show an error while parsing, usually syntactical. | |
| 80 | */ | |
| 81 | public static final String STATUS_PARSE_ERROR = "Main.status.error.parse"; | |
| 82 | public static final String STATUS_DEFINITION_BLANK = | |
| 83 | "Main.status.error.def.blank"; | |
| 84 | public static final String STATUS_DEFINITION_EMPTY = | |
| 85 | "Main.status.error.def.empty"; | |
| 86 | ||
| 87 | /** | |
| 88 | * One parameter: the word under the cursor that could not be found. | |
| 89 | */ | |
| 90 | public static final String STATUS_DEFINITION_MISSING = | |
| 91 | "Main.status.error.def.missing"; | |
| 92 | ||
| 93 | /** | |
| 94 | * Used when creating flat maps relating to resolved variables. | |
| 95 | */ | |
| 96 | public static final int MAP_SIZE_DEFAULT = 128; | |
| 97 | ||
| 98 | /** | |
| 99 | * Default image extension order to use when scanning. | |
| 100 | */ | |
| 101 | public static final String PERSIST_IMAGES_DEFAULT = | |
| 102 | get( "file.ext.image.order" ); | |
| 103 | ||
| 104 | /** | |
| 105 | * Default working directory to use for R startup script. | |
| 106 | */ | |
| 107 | public static final File USER_DIRECTORY = | |
| 108 | new File( System.getProperty( "user.dir" ) ); | |
| 109 | ||
| 110 | public static final String NEWLINE = System.lineSeparator(); | |
| 111 | ||
| 112 | /** | |
| 113 | * Default path to use for an untitled (pathless) file. | |
| 114 | */ | |
| 115 | public static final Path DEFAULT_DIRECTORY = USER_DIRECTORY.toPath(); | |
| 116 | ||
| 117 | /** | |
| 118 | * Default character set to use when reading/writing files. | |
| 119 | */ | |
| 120 | public static final Charset DEFAULT_CHARSET = Charset.defaultCharset(); | |
| 121 | ||
| 122 | /** | |
| 123 | * Default starting delimiter for definition variables. This value must | |
| 124 | * not overlap math delimiters, so do not use $ tokens as the first | |
| 125 | * delimiter. | |
| 126 | */ | |
| 127 | public static final String DEF_DELIM_BEGAN_DEFAULT = "{{"; | |
| 128 | ||
| 129 | /** | |
| 130 | * Default ending delimiter for definition variables. | |
| 131 | */ | |
| 132 | public static final String DEF_DELIM_ENDED_DEFAULT = "}}"; | |
| 133 | ||
| 134 | /** | |
| 135 | * Default starting delimiter when inserting R variables. | |
| 136 | */ | |
| 137 | public static final String R_DELIM_BEGAN_DEFAULT = "x( "; | |
| 138 | ||
| 139 | /** | |
| 140 | * Default ending delimiter when inserting R variables. | |
| 141 | */ | |
| 142 | public static final String R_DELIM_ENDED_DEFAULT = " )"; | |
| 143 | ||
| 144 | /** | |
| 145 | * Resource directory where different language lexicons are located. | |
| 146 | */ | |
| 147 | public static final String LEXICONS_DIRECTORY = "lexicons"; | |
| 148 | ||
| 149 | /** | |
| 150 | * Absolute location of true type font files within the Java archive file. | |
| 151 | */ | |
| 152 | public static final String FONT_DIRECTORY = "/fonts"; | |
| 153 | ||
| 154 | /** | |
| 155 | * Default text editor font name. | |
| 156 | */ | |
| 157 | public static final String FONT_NAME_EDITOR_DEFAULT = "Noto Sans Regular"; | |
| 158 | ||
| 159 | /** | |
| 160 | * Default text editor font size, in points. | |
| 161 | */ | |
| 162 | public static final float FONT_SIZE_EDITOR_DEFAULT = 12f; | |
| 163 | ||
| 164 | /** | |
| 165 | * Default preview font name. | |
| 166 | */ | |
| 167 | public static final String FONT_NAME_PREVIEW_DEFAULT = "Source Serif 4"; | |
| 168 | ||
| 169 | /** | |
| 170 | * Default preview font size, in points. | |
| 171 | */ | |
| 172 | public static final float FONT_SIZE_PREVIEW_DEFAULT = 13f; | |
| 173 | ||
| 174 | /** | |
| 175 | * Default monospace preview font name. | |
| 176 | */ | |
| 177 | public static final String FONT_NAME_PREVIEW_MONO_NAME_DEFAULT = | |
| 178 | "Source Code Pro"; | |
| 179 | ||
| 180 | /** | |
| 181 | * Default monospace preview font size, in points. | |
| 182 | */ | |
| 183 | public static final float FONT_SIZE_PREVIEW_MONO_SIZE_DEFAULT = 13f; | |
| 184 | ||
| 185 | /** | |
| 186 | * Default locale for font loading, including ISO 15924 alpha-4 script code. | |
| 187 | */ | |
| 188 | public static final Locale LOCALE_DEFAULT = withScript( Locale.getDefault() ); | |
| 189 | ||
| 190 | /** | |
| 191 | * Default CSS to apply (resolves to a minimal implementation). | |
| 192 | */ | |
| 193 | public static final String SKIN_DEFAULT = "Modena Light"; | |
| 194 | ||
| 195 | /** | |
| 196 | * Custom CSS to apply. | |
| 197 | */ | |
| 198 | public static final File SKIN_CUSTOM_DEFAULT = null; | |
| 199 | ||
| 200 | /** | |
| 201 | * Default identifier to use for synchronized scrolling. | |
| 202 | */ | |
| 203 | public static final String CARET_ID = "caret"; | |
| 204 | ||
| 205 | /** | |
| 206 | * Default spacing for UI items (e.g., toolbars). | |
| 207 | */ | |
| 208 | public static final int UI_CONTROL_SPACING = 10; | |
| 209 | ||
| 210 | /** | |
| 211 | * Default server name for rendering diagrams. | |
| 212 | */ | |
| 213 | public static final String DIAGRAM_SERVER_NAME = "kroki.io"; | |
| 214 | ||
| 215 | /** | |
| 216 | * Application action messages properties prefix. | |
| 217 | */ | |
| 218 | public static final String ACTION_PREFIX = "Action."; | |
| 219 | ||
| 220 | /** | |
| 221 | * Restrict theme names when displaying. | |
| 222 | */ | |
| 223 | public static final byte THEME_NAME_LENGTH = 30; | |
| 224 | ||
| 225 | /** | |
| 226 | * Prevent instantiation. | |
| 227 | */ | |
| 228 | private Constants() { | |
| 229 | } | |
| 230 | ||
| 231 | /** | |
| 232 | * Converts from points to pixels because FlyingSaucer cannot handle points | |
| 233 | * properly. This is used to convert font sizes. | |
| 234 | * | |
| 235 | * @param points The points to convert to pixels. | |
| 236 | * @return The given number of points in equivalent pixels. | |
| 237 | */ | |
| 238 | public static int toPixels( final double points ) { | |
| 239 | return (int) (points * (1 + 1 / 3f)); | |
| 240 | } | |
| 241 | ||
| 242 | static String get( final String key ) { | |
| 243 | return sSettings.getSetting( key, "" ); | |
| 244 | } | |
| 245 | ||
| 246 | /** | |
| 247 | * Returns a default {@link File} instance based on the given key suffix. | |
| 248 | * | |
| 249 | * @param suffix Appended to {@code "file.default."}. | |
| 250 | * @return A new {@link File} instance that references the settings file name. | |
| 251 | */ | |
| 252 | private static File getFile( final String suffix ) { | |
| 253 | return new File( get( "file.default." + suffix ) ); | |
| 254 | } | |
| 255 | ||
| 256 | /** | |
| 257 | * Returns the equivalent of {@code $HOME/.filename.xml}. | |
| 258 | */ | |
| 259 | private static String getPreferencesFilename() { | |
| 260 | return format( | |
| 261 | "%s%s.%s.xml", | |
| 262 | getProperty( "user.home" ), | |
| 263 | separator, | |
| 264 | APP_TITLE_LOWERCASE | |
| 265 | ); | |
| 266 | } | |
| 267 | } | |
| 1 | 268 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.constants; | |
| 3 | ||
| 4 | import javafx.scene.image.Image; | |
| 5 | import javafx.scene.image.ImageView; | |
| 6 | ||
| 7 | import java.util.ArrayList; | |
| 8 | import java.util.List; | |
| 9 | ||
| 10 | import static com.keenwrite.constants.Constants.get; | |
| 11 | ||
| 12 | /** | |
| 13 | * Defines application-wide default values for GUI-related items. This helps | |
| 14 | * ensure that unit tests that have no graphical dependencies will pass. | |
| 15 | */ | |
| 16 | public class GraphicsConstants { | |
| 17 | public static final List<Image> LOGOS = createImages( | |
| 18 | "file.logo.16", | |
| 19 | "file.logo.32", | |
| 20 | "file.logo.128", | |
| 21 | "file.logo.256", | |
| 22 | "file.logo.512" | |
| 23 | ); | |
| 24 | ||
| 25 | public static final Image ICON_DIALOG = LOGOS.get( 1 ); | |
| 26 | ||
| 27 | public static final ImageView ICON_DIALOG_NODE = new ImageView( ICON_DIALOG ); | |
| 28 | ||
| 29 | /** | |
| 30 | * Converts the given file names to images, such as application icons. | |
| 31 | * | |
| 32 | * @param keys The file names to convert to images. | |
| 33 | * @return The images loaded from the file name references. | |
| 34 | */ | |
| 35 | private static List<Image> createImages( final String... keys ) { | |
| 36 | final List<Image> images = new ArrayList<>( keys.length ); | |
| 37 | ||
| 38 | for( final var key : keys ) { | |
| 39 | images.add( new Image( get( key ) ) ); | |
| 40 | } | |
| 41 | ||
| 42 | return images; | |
| 43 | } | |
| 44 | } | |
| 1 | 45 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.dom; | |
| 3 | ||
| 4 | import org.jsoup.helper.W3CDom; | |
| 5 | import org.jsoup.nodes.Node; | |
| 6 | import org.jsoup.nodes.TextNode; | |
| 7 | import org.jsoup.select.NodeVisitor; | |
| 8 | import org.w3c.dom.Document; | |
| 9 | ||
| 10 | import java.util.LinkedHashMap; | |
| 11 | import java.util.Map; | |
| 12 | ||
| 13 | import static com.keenwrite.dom.DocumentParser.sDomImplementation; | |
| 14 | import static com.keenwrite.processors.text.TextReplacementFactory.replace; | |
| 15 | ||
| 16 | /** | |
| 17 | * Responsible for converting JSoup document object model (DOM) to a W3C DOM. | |
| 18 | * Provides a lighter implementation than the superclass by overriding the | |
| 19 | * {@link #fromJsoup(org.jsoup.nodes.Document)} method to reuse factories, | |
| 20 | * builders, and implementations. | |
| 21 | */ | |
| 22 | public final class DocumentConverter extends W3CDom { | |
| 23 | /** | |
| 24 | * Retain insertion order using an instance of {@link LinkedHashMap} so | |
| 25 | * that ligature substitution uses longer ligatures ahead of shorter | |
| 26 | * ligatures. The word "ruffian" should use the "ffi" ligature, not the "ff" | |
| 27 | * ligature. | |
| 28 | */ | |
| 29 | private static final Map<String, String> LIGATURES = new LinkedHashMap<>(); | |
| 30 | ||
| 31 | static { | |
| 32 | LIGATURES.put( "ffi", "\uFB03" ); | |
| 33 | LIGATURES.put( "ffl", "\uFB04" ); | |
| 34 | LIGATURES.put( "ff", "\uFB00" ); | |
| 35 | LIGATURES.put( "fi", "\uFB01" ); | |
| 36 | LIGATURES.put( "fl", "\uFB02" ); | |
| 37 | } | |
| 38 | ||
| 39 | private static final NodeVisitor LIGATURE_VISITOR = new NodeVisitor() { | |
| 40 | @Override | |
| 41 | public void head( final Node node, final int depth ) { | |
| 42 | if( node instanceof final TextNode textNode ) { | |
| 43 | final var parent = node.parentNode(); | |
| 44 | final var name = parent == null ? "root" : parent.nodeName(); | |
| 45 | ||
| 46 | if( !("pre".equalsIgnoreCase( name ) || | |
| 47 | "code".equalsIgnoreCase( name ) || | |
| 48 | "kbd".equalsIgnoreCase( name ) || | |
| 49 | "var".equalsIgnoreCase( name ) || | |
| 50 | "tt".equalsIgnoreCase( name )) ) { | |
| 51 | // Calling getWholeText() will return newlines, which must be kept | |
| 52 | // to ensure that preformatted text maintains its formatting. | |
| 53 | textNode.text( replace( textNode.getWholeText(), LIGATURES ) ); | |
| 54 | } | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | @Override | |
| 59 | public void tail( final Node node, final int depth ) { | |
| 60 | } | |
| 61 | }; | |
| 62 | ||
| 63 | @Override | |
| 64 | public Document fromJsoup( final org.jsoup.nodes.Document in ) { | |
| 65 | assert in != null; | |
| 66 | ||
| 67 | final var out = DocumentParser.newDocument(); | |
| 68 | final org.jsoup.nodes.DocumentType doctype = in.documentType(); | |
| 69 | ||
| 70 | if( doctype != null ) { | |
| 71 | out.appendChild( | |
| 72 | sDomImplementation.createDocumentType( | |
| 73 | doctype.name(), | |
| 74 | doctype.publicId(), | |
| 75 | doctype.systemId() | |
| 76 | ) | |
| 77 | ); | |
| 78 | } | |
| 79 | ||
| 80 | out.setXmlStandalone( true ); | |
| 81 | in.traverse( LIGATURE_VISITOR ); | |
| 82 | convert( in, out ); | |
| 83 | ||
| 84 | return out; | |
| 85 | } | |
| 86 | } | |
| 1 | 87 |
| 1 | package com.keenwrite.dom; | |
| 2 | ||
| 3 | import org.w3c.dom.*; | |
| 4 | import org.xml.sax.InputSource; | |
| 5 | import org.xml.sax.SAXException; | |
| 6 | ||
| 7 | import javax.xml.parsers.DocumentBuilder; | |
| 8 | import javax.xml.parsers.DocumentBuilderFactory; | |
| 9 | import javax.xml.transform.Transformer; | |
| 10 | import javax.xml.transform.TransformerException; | |
| 11 | import javax.xml.transform.TransformerFactory; | |
| 12 | import javax.xml.transform.dom.DOMSource; | |
| 13 | import javax.xml.transform.stream.StreamResult; | |
| 14 | import javax.xml.xpath.XPath; | |
| 15 | import javax.xml.xpath.XPathExpression; | |
| 16 | import javax.xml.xpath.XPathExpressionException; | |
| 17 | import javax.xml.xpath.XPathFactory; | |
| 18 | import java.io.IOException; | |
| 19 | import java.io.InputStream; | |
| 20 | import java.io.StringReader; | |
| 21 | import java.io.StringWriter; | |
| 22 | import java.nio.file.Path; | |
| 23 | import java.util.HashMap; | |
| 24 | import java.util.Map; | |
| 25 | import java.util.function.Consumer; | |
| 26 | ||
| 27 | import static com.keenwrite.events.StatusEvent.clue; | |
| 28 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 29 | import static javax.xml.transform.OutputKeys.*; | |
| 30 | import static javax.xml.xpath.XPathConstants.NODESET; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for initializing an XML parser. | |
| 34 | */ | |
| 35 | public class DocumentParser { | |
| 36 | private static final String LOAD_EXTERNAL_DTD = | |
| 37 | "http://apache.org/xml/features/nonvalidating/load-external-dtd"; | |
| 38 | ||
| 39 | /** | |
| 40 | * Caches {@link XPathExpression}s to avoid re-compiling. | |
| 41 | */ | |
| 42 | private static final Map<String, XPathExpression> sXpaths = new HashMap<>(); | |
| 43 | ||
| 44 | private static final DocumentBuilderFactory sDocumentFactory; | |
| 45 | private static DocumentBuilder sDocumentBuilder; | |
| 46 | public static DOMImplementation sDomImplementation; | |
| 47 | public static Transformer sTransformer; | |
| 48 | private static final XPath sXpath = XPathFactory.newInstance().newXPath(); | |
| 49 | ||
| 50 | static { | |
| 51 | sDocumentFactory = DocumentBuilderFactory.newInstance(); | |
| 52 | ||
| 53 | sDocumentFactory.setValidating( false ); | |
| 54 | sDocumentFactory.setAttribute( LOAD_EXTERNAL_DTD, false ); | |
| 55 | sDocumentFactory.setNamespaceAware( true ); | |
| 56 | sDocumentFactory.setIgnoringComments( true ); | |
| 57 | sDocumentFactory.setIgnoringElementContentWhitespace( true ); | |
| 58 | ||
| 59 | try { | |
| 60 | sDocumentBuilder = sDocumentFactory.newDocumentBuilder(); | |
| 61 | sDomImplementation = sDocumentBuilder.getDOMImplementation(); | |
| 62 | sTransformer = TransformerFactory.newInstance().newTransformer(); | |
| 63 | ||
| 64 | sTransformer.setOutputProperty( OMIT_XML_DECLARATION, "yes" ); | |
| 65 | sTransformer.setOutputProperty( METHOD, "xml" ); | |
| 66 | sTransformer.setOutputProperty( INDENT, "no" ); | |
| 67 | sTransformer.setOutputProperty( ENCODING, UTF_8.toString() ); | |
| 68 | } catch( final Exception ex ) { | |
| 69 | clue( ex ); | |
| 70 | } | |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Use the {@code static} constants and methods, not an instance, at least | |
| 75 | * until an iterable sub-interface is written. | |
| 76 | */ | |
| 77 | private DocumentParser() {} | |
| 78 | ||
| 79 | public static Document newDocument() { | |
| 80 | return sDocumentBuilder.newDocument(); | |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Creates a new document object model based on the given XML document | |
| 85 | * string. This will return an empty document if the document could not | |
| 86 | * be parsed. | |
| 87 | * | |
| 88 | * @param xml The document text to convert into a DOM. | |
| 89 | * @return The DOM that represents the given XML data. | |
| 90 | */ | |
| 91 | public static Document parse( final String xml ) { | |
| 92 | final var input = new InputSource(); | |
| 93 | ||
| 94 | try( final var reader = new StringReader( xml ) ) { | |
| 95 | input.setEncoding( UTF_8.toString() ); | |
| 96 | input.setCharacterStream( reader ); | |
| 97 | return sDocumentBuilder.parse( input ); | |
| 98 | } catch( final Exception ex ) { | |
| 99 | clue( ex ); | |
| 100 | return sDocumentBuilder.newDocument(); | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | public static Document parse( final InputStream doc ) | |
| 105 | throws IOException, SAXException { | |
| 106 | return sDocumentBuilder.parse( doc ); | |
| 107 | } | |
| 108 | ||
| 109 | /** | |
| 110 | * Allows an operation to be applied for every node in the document that | |
| 111 | * matches a given tag name pattern. | |
| 112 | * | |
| 113 | * @param document Document to traverse. | |
| 114 | * @param xpath Document elements to find via {@link XPath} expression. | |
| 115 | * @param consumer The consumer to call for each matching document node. | |
| 116 | */ | |
| 117 | public static void walk( | |
| 118 | final Document document, final String xpath, | |
| 119 | final Consumer<Node> consumer ) { | |
| 120 | assert document != null; | |
| 121 | assert consumer != null; | |
| 122 | ||
| 123 | try { | |
| 124 | final var expr = lookupXPathExpression( xpath ); | |
| 125 | final var nodes = (NodeList) expr.evaluate( document, NODESET ); | |
| 126 | ||
| 127 | if( nodes != null ) { | |
| 128 | for( int i = 0, len = nodes.getLength(); i < len; i++ ) { | |
| 129 | consumer.accept( nodes.item( i ) ); | |
| 130 | } | |
| 131 | } | |
| 132 | } catch( final Exception ex ) { | |
| 133 | clue( ex ); | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | public static Node createMeta( | |
| 138 | final Document document, final Map.Entry<String, String> entry ) { | |
| 139 | final var node = document.createElement( "meta" ); | |
| 140 | node.setAttribute( "name", entry.getKey() ); | |
| 141 | node.setAttribute( "content", entry.getValue() ); | |
| 142 | ||
| 143 | return node; | |
| 144 | } | |
| 145 | ||
| 146 | public static String toString( final Document xhtml ) { | |
| 147 | try( final var writer = new StringWriter() ) { | |
| 148 | final var domSource = new DOMSource( xhtml ); | |
| 149 | final var result = new StreamResult( writer ); | |
| 150 | ||
| 151 | sTransformer.transform( domSource, result ); | |
| 152 | return writer.toString(); | |
| 153 | } catch( final Exception ex ) { | |
| 154 | clue( ex ); | |
| 155 | return ""; | |
| 156 | } | |
| 157 | } | |
| 158 | ||
| 159 | public static String transform( final Element root ) | |
| 160 | throws IOException, TransformerException { | |
| 161 | try( final var writer = new StringWriter() ) { | |
| 162 | sTransformer.transform( | |
| 163 | new DOMSource( root ), new StreamResult( writer ) ); | |
| 164 | return writer.toString(); | |
| 165 | } | |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * Remove whitespace, comments, and XML/DOCTYPE declarations to make | |
| 170 | * processing work with ConTeXt. | |
| 171 | * | |
| 172 | * @param path The SVG file to process. | |
| 173 | * @throws Exception The file could not be processed. | |
| 174 | */ | |
| 175 | public static void sanitize( final Path path ) | |
| 176 | throws Exception { | |
| 177 | final var file = path.toFile(); | |
| 178 | ||
| 179 | sTransformer.transform( | |
| 180 | new DOMSource( sDocumentBuilder.parse( file ) ), new StreamResult( file ) | |
| 181 | ); | |
| 182 | } | |
| 183 | ||
| 184 | /** | |
| 185 | * Adorns the given document with {@code html}, {@code head}, and | |
| 186 | * {@code body} elements. | |
| 187 | * | |
| 188 | * @param html The document to decorate. | |
| 189 | * @return A document with a typical HTML structure. | |
| 190 | */ | |
| 191 | public static String decorate( final String html ) { | |
| 192 | return | |
| 193 | "<html><head><title> </title></head><body>" + html + "</body></html>"; | |
| 194 | } | |
| 195 | ||
| 196 | private static XPathExpression lookupXPathExpression( final String xpath ) { | |
| 197 | return sXpaths.computeIfAbsent( xpath, k -> { | |
| 198 | try { | |
| 199 | return sXpath.compile( xpath ); | |
| 200 | } catch( final XPathExpressionException ex ) { | |
| 201 | clue( ex ); | |
| 202 | return null; | |
| 203 | } | |
| 204 | } ); | |
| 205 | } | |
| 206 | } | |
| 1 | 207 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors; | |
| 3 | ||
| 4 | import com.keenwrite.editors.definition.DefinitionEditor; | |
| 5 | import com.keenwrite.editors.definition.DefinitionTreeItem; | |
| 6 | import com.keenwrite.editors.markdown.MarkdownEditor; | |
| 7 | import com.keenwrite.sigils.Tokens; | |
| 8 | import javafx.scene.control.TreeItem; | |
| 9 | ||
| 10 | import java.util.Map; | |
| 11 | ||
| 12 | /** | |
| 13 | * Differentiates an instance of {@link TextResource} from an instance of | |
| 14 | * {@link DefinitionEditor} or {@link MarkdownEditor}. | |
| 15 | */ | |
| 16 | public interface TextDefinition extends TextResource { | |
| 17 | /** | |
| 18 | * Converts the definitions into a map, ready for interpolation. | |
| 19 | * | |
| 20 | * @return The list of key value pairs delimited with tokens. | |
| 21 | */ | |
| 22 | Map<String, String> toMap(); | |
| 23 | ||
| 24 | /** | |
| 25 | * Performs string interpolation on the values in the given map. This will | |
| 26 | * change any value in the map that contains a variable that matches | |
| 27 | * the definition regex pattern against the given {@link Tokens}. | |
| 28 | * | |
| 29 | * @param map Contains values that represent references to keys. | |
| 30 | * @param tokens The beginning and ending tokens that delimit variables. | |
| 31 | */ | |
| 32 | Map<String, String> interpolate( Map<String, String> map, Tokens tokens ); | |
| 33 | ||
| 34 | /** | |
| 35 | * Requests that the visual representation be expanded to the given | |
| 36 | * node. | |
| 37 | * | |
| 38 | * @param node Request expansion to this node. | |
| 39 | */ | |
| 40 | <T> void expand( TreeItem<T> node ); | |
| 41 | ||
| 42 | /** | |
| 43 | * Adds a new item to the definition hierarchy. | |
| 44 | */ | |
| 45 | void createDefinition(); | |
| 46 | ||
| 47 | /** | |
| 48 | * Edits the currently selected definition in the hierarchy. | |
| 49 | */ | |
| 50 | void renameDefinition(); | |
| 51 | ||
| 52 | /** | |
| 53 | * Removes the currently selected definition in the hierarchy. | |
| 54 | */ | |
| 55 | void deleteDefinitions(); | |
| 56 | ||
| 57 | /** | |
| 58 | * Finds the definition that exact matches the given text. | |
| 59 | * | |
| 60 | * @param text The value to find, never {@code null}. | |
| 61 | * @return The leaf that contains the given value. | |
| 62 | */ | |
| 63 | DefinitionTreeItem<String> findLeafExact( String text ); | |
| 64 | ||
| 65 | /** | |
| 66 | * Finds the definition that starts with the given text. | |
| 67 | * | |
| 68 | * @param text The value to find, never {@code null}. | |
| 69 | * @return The leaf that starts with the given value. | |
| 70 | */ | |
| 71 | DefinitionTreeItem<String> findLeafStartsWith( String text ); | |
| 72 | ||
| 73 | /** | |
| 74 | * Finds the definition that contains the given text, matching case. | |
| 75 | * | |
| 76 | * @param text The value to find, never {@code null}. | |
| 77 | * @return The leaf that contains the exact given value. | |
| 78 | */ | |
| 79 | DefinitionTreeItem<String> findLeafContains( String text ); | |
| 80 | ||
| 81 | /** | |
| 82 | * Finds the definition that contains the given text, ignoring case. | |
| 83 | * | |
| 84 | * @param text The value to find, never {@code null}. | |
| 85 | * @return The leaf that contains the given value, regardless of case. | |
| 86 | */ | |
| 87 | DefinitionTreeItem<String> findLeafContainsNoCase( String text ); | |
| 88 | ||
| 89 | /** | |
| 90 | * Answers whether there are any definitions written. | |
| 91 | * | |
| 92 | * @return {@code true} when there are no definitions. | |
| 93 | */ | |
| 94 | boolean isEmpty(); | |
| 95 | } | |
| 1 | 96 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors; | |
| 3 | ||
| 4 | import com.keenwrite.Caret; | |
| 5 | import javafx.scene.control.IndexRange; | |
| 6 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 7 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 8 | ||
| 9 | /** | |
| 10 | * Responsible for differentiating an instance of {@link TextResource} from | |
| 11 | * other {@link TextResource} subtypes, such as a {@link TextDefinition}. | |
| 12 | * This is primarily used as a marker interface, but also defines a minimal | |
| 13 | * set of functionality required by all {@link TextEditor} instances, which | |
| 14 | * includes scrolling facilities. | |
| 15 | */ | |
| 16 | public interface TextEditor extends TextResource { | |
| 17 | ||
| 18 | /** | |
| 19 | * Returns the scrollbars associated with the editor's view so that they | |
| 20 | * can be moved for synchronized scrolling. | |
| 21 | * | |
| 22 | * @return The initialized horizontal and vertical scrollbars. | |
| 23 | */ | |
| 24 | VirtualizedScrollPane<StyleClassedTextArea> getScrollPane(); | |
| 25 | ||
| 26 | StyleClassedTextArea getTextArea(); | |
| 27 | ||
| 28 | /** | |
| 29 | * Requests that styling be added to the document between the given | |
| 30 | * integer values. | |
| 31 | * | |
| 32 | * @param indexes Document offset where style is to start and end. | |
| 33 | * @param style The style class to apply between the given offset indexes. | |
| 34 | */ | |
| 35 | default void stylize( final IndexRange indexes, final String style ) { | |
| 36 | } | |
| 37 | ||
| 38 | /** | |
| 39 | * Requests that the most recent styling for the given style class be | |
| 40 | * removed from the document between the given integer values. | |
| 41 | */ | |
| 42 | default void unstylize( final String style ) { | |
| 43 | } | |
| 44 | ||
| 45 | /** | |
| 46 | * Returns the complete text for the specified paragraph index. | |
| 47 | * | |
| 48 | * @param paragraph The zero-based paragraph index. | |
| 49 | * @throws IndexOutOfBoundsException The paragraph index is less than zero | |
| 50 | * or greater than the number of | |
| 51 | * paragraphs in the document. | |
| 52 | */ | |
| 53 | String getText( int paragraph ) throws IndexOutOfBoundsException; | |
| 54 | ||
| 55 | /** | |
| 56 | * Returns the text between the indexes specified by the given | |
| 57 | * {@link IndexRange}. | |
| 58 | * | |
| 59 | * @param indexes The start and end document indexes to reference. | |
| 60 | * @return The text between the specified indexes. | |
| 61 | * @throws IndexOutOfBoundsException The indexes are invalid. | |
| 62 | */ | |
| 63 | String getText( IndexRange indexes ) throws IndexOutOfBoundsException; | |
| 64 | ||
| 65 | /** | |
| 66 | * Moves the caret to the given document offset. | |
| 67 | * | |
| 68 | * @param offset The absolute offset into the document, zero-based. | |
| 69 | */ | |
| 70 | void moveTo( final int offset ); | |
| 71 | ||
| 72 | /** | |
| 73 | * Returns an object that can be used to track the current caret position | |
| 74 | * within the document. | |
| 75 | * | |
| 76 | * @return The caret's position, which is updated continuously. | |
| 77 | */ | |
| 78 | Caret getCaret(); | |
| 79 | ||
| 80 | /** | |
| 81 | * Replaces the text within the given range with the given string. | |
| 82 | * | |
| 83 | * @param indexes The starting and ending document indexes that represent | |
| 84 | * the range of text to replace. | |
| 85 | * @param s The text to replace, which can be shorter or longer than the | |
| 86 | * specified range. | |
| 87 | */ | |
| 88 | void replaceText( IndexRange indexes, String s ); | |
| 89 | ||
| 90 | /** | |
| 91 | * Returns the starting and ending indexes into the document for the | |
| 92 | * word at the current caret position. | |
| 93 | * <p> | |
| 94 | * Finds the start and end indexes for the word in the current document, | |
| 95 | * where the caret is located. There are a few different scenarios, where | |
| 96 | * the caret can be at: the start, end, or middle of a word; also, the | |
| 97 | * caret can be at the end or beginning of a punctuated word; as well, the | |
| 98 | * caret could be at the beginning or end of the line or document. | |
| 99 | * </p> | |
| 100 | * | |
| 101 | * @return The start and ending index into the current document that | |
| 102 | * represent the word boundaries of the word under the caret. | |
| 103 | */ | |
| 104 | IndexRange getCaretWord(); | |
| 105 | ||
| 106 | /** | |
| 107 | * Convenience method to get the word at the current caret position. | |
| 108 | * | |
| 109 | * @return This will return the empty string if the caret is out of bounds. | |
| 110 | */ | |
| 111 | default String getCaretWordText() { | |
| 112 | return getText( getCaretWord() ); | |
| 113 | } | |
| 114 | ||
| 115 | /** | |
| 116 | * Requests undoing the last text-changing action. | |
| 117 | */ | |
| 118 | void undo(); | |
| 119 | ||
| 120 | /** | |
| 121 | * Requests redoing the last text-changing action that was undone. | |
| 122 | */ | |
| 123 | void redo(); | |
| 124 | ||
| 125 | /** | |
| 126 | * Requests cutting the selected text, or the current line if none selected. | |
| 127 | */ | |
| 128 | void cut(); | |
| 129 | ||
| 130 | /** | |
| 131 | * Requests copying the selected text, or no operation if none selected. | |
| 132 | */ | |
| 133 | void copy(); | |
| 134 | ||
| 135 | /** | |
| 136 | * Requests pasting from the clipboard into the editor. This will replace | |
| 137 | * text if selected, otherwise the clipboard contents are inserted at the | |
| 138 | * cursor. | |
| 139 | */ | |
| 140 | void paste(); | |
| 141 | ||
| 142 | /** | |
| 143 | * Requests selecting the entire document. This will replace the existing | |
| 144 | * selection, if any. | |
| 145 | */ | |
| 146 | void selectAll(); | |
| 147 | ||
| 148 | /** | |
| 149 | * Requests making the selected text, or word at caret, bold. | |
| 150 | */ | |
| 151 | default void bold() { } | |
| 152 | ||
| 153 | /** | |
| 154 | * Requests making the selected text, or word at caret, italic. | |
| 155 | */ | |
| 156 | default void italic() { } | |
| 157 | ||
| 158 | /** | |
| 159 | * Requests making the selected text, or word at caret, monospace. | |
| 160 | */ | |
| 161 | default void monospace() { } | |
| 162 | ||
| 163 | /** | |
| 164 | * Requests making the selected text, or word at caret, a superscript. | |
| 165 | */ | |
| 166 | default void superscript() { } | |
| 167 | ||
| 168 | /** | |
| 169 | * Requests making the selected text, or word at caret, a subscript. | |
| 170 | */ | |
| 171 | default void subscript() { } | |
| 172 | ||
| 173 | /** | |
| 174 | * Requests making the selected text, or word at caret, struck. | |
| 175 | */ | |
| 176 | default void strikethrough() { } | |
| 177 | ||
| 178 | /** | |
| 179 | * Requests making the selected text, or word at caret, a blockquote block. | |
| 180 | */ | |
| 181 | default void blockquote() { } | |
| 182 | ||
| 183 | /** | |
| 184 | * Requests making the selected text, or word at caret, inline code. | |
| 185 | */ | |
| 186 | default void code() { } | |
| 187 | ||
| 188 | /** | |
| 189 | * Requests making the selected text, or word at caret, a fenced code block. | |
| 190 | */ | |
| 191 | default void fencedCodeBlock() { } | |
| 192 | ||
| 193 | /** | |
| 194 | * Requests making the selected text, or word at caret, a heading. | |
| 195 | * | |
| 196 | * @param level The heading level to apply (typically 1 through 3). | |
| 197 | */ | |
| 198 | default void heading( final int level ) { } | |
| 199 | ||
| 200 | /** | |
| 201 | * Requests making the selected text, or word at caret, an unordered list | |
| 202 | * block. | |
| 203 | */ | |
| 204 | default void unorderedList() { } | |
| 205 | ||
| 206 | /** | |
| 207 | * Requests making the selected text, or word at caret, an ordered list block. | |
| 208 | */ | |
| 209 | default void orderedList() { } | |
| 210 | ||
| 211 | /** | |
| 212 | * Requests making the selected text, or inserting at the caret, a | |
| 213 | * horizontal rule. | |
| 214 | */ | |
| 215 | default void horizontalRule() { } | |
| 216 | } | |
| 1 | 217 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors; | |
| 3 | ||
| 4 | import com.keenwrite.io.MediaType; | |
| 5 | import javafx.beans.property.ReadOnlyBooleanProperty; | |
| 6 | import javafx.scene.Node; | |
| 7 | import org.mozilla.universalchardet.UniversalDetector; | |
| 8 | ||
| 9 | import java.io.File; | |
| 10 | import java.nio.charset.Charset; | |
| 11 | import java.nio.file.Path; | |
| 12 | ||
| 13 | import static com.keenwrite.constants.Constants.DEFAULT_CHARSET; | |
| 14 | import static com.keenwrite.events.StatusEvent.clue; | |
| 15 | import static java.nio.charset.Charset.forName; | |
| 16 | import static java.nio.file.Files.readAllBytes; | |
| 17 | import static java.nio.file.Files.write; | |
| 18 | import static java.util.Arrays.asList; | |
| 19 | import static java.util.Locale.ENGLISH; | |
| 20 | ||
| 21 | /** | |
| 22 | * A text resource can be persisted and retrieved from its persisted location. | |
| 23 | */ | |
| 24 | public interface TextResource { | |
| 25 | /** | |
| 26 | * Sets the text string that to be changed through some graphical user | |
| 27 | * interface. For example, a YAML document must be parsed from the given | |
| 28 | * text string into a tree view with which the user may interact. | |
| 29 | * | |
| 30 | * @param text The new content for the resource. | |
| 31 | */ | |
| 32 | void setText( String text ); | |
| 33 | ||
| 34 | /** | |
| 35 | * Returns the text string that may have been modified by the user through | |
| 36 | * some graphical user interface. | |
| 37 | * | |
| 38 | * @return The text value, based on the value set from | |
| 39 | * {@link #setText(String)}, but possibly mutated. | |
| 40 | */ | |
| 41 | String getText(); | |
| 42 | ||
| 43 | /** | |
| 44 | * Return the character encoding for this file. | |
| 45 | * | |
| 46 | * @return A non-null character set, primarily detected from file contents. | |
| 47 | */ | |
| 48 | Charset getEncoding(); | |
| 49 | ||
| 50 | /** | |
| 51 | * Renames the current file to the given fully qualified file name. | |
| 52 | * | |
| 53 | * @param file The new file name. | |
| 54 | */ | |
| 55 | void rename( final File file ); | |
| 56 | ||
| 57 | /** | |
| 58 | * Returns the file name, without any directory components, for this instance. | |
| 59 | * Useful for showing as a tab title. | |
| 60 | * | |
| 61 | * @return The file name value returned from {@link #getFile()}. | |
| 62 | */ | |
| 63 | default String getFilename() { | |
| 64 | final var filename = getFile().toPath().getFileName(); | |
| 65 | return filename == null ? "" : filename.toString(); | |
| 66 | } | |
| 67 | ||
| 68 | /** | |
| 69 | * Returns the fully qualified {@link File} to the editable text resource. | |
| 70 | * Useful for showing as a tab tooltip, saving the file, or reading it. | |
| 71 | * | |
| 72 | * @return A non-null {@link File} instance. | |
| 73 | */ | |
| 74 | File getFile(); | |
| 75 | ||
| 76 | /** | |
| 77 | * Returns the {@link MediaType} associated with the file being edited. | |
| 78 | * | |
| 79 | * @return The {@link MediaType} for the editor's file. | |
| 80 | */ | |
| 81 | default MediaType getMediaType() { | |
| 82 | return MediaType.valueFrom( getFile() ); | |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * Answers whether this instance is an editor for at least one of the given | |
| 87 | * {@link MediaType} references. | |
| 88 | * | |
| 89 | * @param mediaTypes The {@link MediaType} references to compare against. | |
| 90 | * @return {@code true} if the given list of media types contains the | |
| 91 | * {@link MediaType} for this editor. | |
| 92 | */ | |
| 93 | default boolean isMediaType( final MediaType... mediaTypes ) { | |
| 94 | return asList( mediaTypes ).contains( getMediaType() ); | |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * Returns the fully qualified {@link Path} to the editable text resource. | |
| 99 | * This delegates to {@link #getFile()}. | |
| 100 | * | |
| 101 | * @return A non-null {@link Path} instance. | |
| 102 | */ | |
| 103 | default Path getPath() { | |
| 104 | return getFile().toPath(); | |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Read the file contents and update the text accordingly. If the file | |
| 109 | * cannot be read then no changes will happen to the text. Fails silently. | |
| 110 | * | |
| 111 | * @param path The fully qualified {@link Path}, including a file name, to | |
| 112 | * fully read into the editor. | |
| 113 | * @return The character encoding for the file at the given {@link Path}. | |
| 114 | */ | |
| 115 | default Charset open( final Path path ) { | |
| 116 | final var file = path.toFile(); | |
| 117 | Charset encoding = DEFAULT_CHARSET; | |
| 118 | ||
| 119 | try { | |
| 120 | if( file.exists() ) { | |
| 121 | if( file.canWrite() && file.canRead() ) { | |
| 122 | final var bytes = readAllBytes( path ); | |
| 123 | encoding = detectEncoding( bytes ); | |
| 124 | ||
| 125 | setText( asString( bytes, encoding ) ); | |
| 126 | } | |
| 127 | else { | |
| 128 | clue( "TextResource.load.error.permissions", file.toString() ); | |
| 129 | } | |
| 130 | } | |
| 131 | else { | |
| 132 | clue( "TextResource.load.error.unsaved", file.toString() ); | |
| 133 | } | |
| 134 | } catch( final Exception ex ) { | |
| 135 | clue( ex ); | |
| 136 | } | |
| 137 | ||
| 138 | return encoding; | |
| 139 | } | |
| 140 | ||
| 141 | /** | |
| 142 | * Read the file contents and update the text accordingly. If the file | |
| 143 | * cannot be read then no changes will happen to the text. This delegates | |
| 144 | * to {@link #open(Path)}. | |
| 145 | * | |
| 146 | * @param file The {@link File} to fully read into the editor. | |
| 147 | * @return The file's character encoding. | |
| 148 | */ | |
| 149 | default Charset open( final File file ) { | |
| 150 | return open( file.toPath() ); | |
| 151 | } | |
| 152 | ||
| 153 | /** | |
| 154 | * Save the file contents and clear the modified flag. If the file cannot | |
| 155 | * be saved, the exception is swallowed and this method returns {@code false}. | |
| 156 | * | |
| 157 | * @return {@code true} the file was saved; {@code false} if upon exception. | |
| 158 | */ | |
| 159 | default boolean save() { | |
| 160 | try { | |
| 161 | write( getPath(), asBytes( getText() ) ); | |
| 162 | clearModifiedProperty(); | |
| 163 | return true; | |
| 164 | } catch( final Exception ex ) { | |
| 165 | clue( ex ); | |
| 166 | } | |
| 167 | ||
| 168 | return false; | |
| 169 | } | |
| 170 | ||
| 171 | /** | |
| 172 | * Returns the node associated with this {@link TextResource}. | |
| 173 | * | |
| 174 | * @return The view component for the {@link TextResource}. | |
| 175 | */ | |
| 176 | Node getNode(); | |
| 177 | ||
| 178 | /** | |
| 179 | * Answers whether the resource has been modified. | |
| 180 | * | |
| 181 | * @return {@code true} the resource has changed; {@code false} means that | |
| 182 | * no changes to the resource have been made. | |
| 183 | */ | |
| 184 | default boolean isModified() { | |
| 185 | return modifiedProperty().get(); | |
| 186 | } | |
| 187 | ||
| 188 | /** | |
| 189 | * Returns a property that answers whether this text resource has been | |
| 190 | * changed from the original text that was opened. | |
| 191 | * | |
| 192 | * @return A property representing the modified state of this | |
| 193 | * {@link TextResource}. | |
| 194 | */ | |
| 195 | ReadOnlyBooleanProperty modifiedProperty(); | |
| 196 | ||
| 197 | /** | |
| 198 | * Lowers the modified flag such that listeners to the modified property | |
| 199 | * will be informed that the text that's being edited no longer differs | |
| 200 | * from what's persisted. | |
| 201 | */ | |
| 202 | void clearModifiedProperty(); | |
| 203 | ||
| 204 | private String asString( final byte[] text, final Charset encoding ) { | |
| 205 | return new String( text, encoding ); | |
| 206 | } | |
| 207 | ||
| 208 | /** | |
| 209 | * Converts the given string to an array of bytes using the encoding that was | |
| 210 | * originally detected (if any) and associated with this file. | |
| 211 | * | |
| 212 | * @param text The text to convert into the original file encoding. | |
| 213 | * @return A series of bytes ready for writing to a file. | |
| 214 | */ | |
| 215 | private byte[] asBytes( final String text ) { | |
| 216 | return text.getBytes( getEncoding() ); | |
| 217 | } | |
| 218 | ||
| 219 | private Charset detectEncoding( final byte[] bytes ) { | |
| 220 | final var detector = new UniversalDetector( null ); | |
| 221 | detector.handleData( bytes, 0, bytes.length ); | |
| 222 | detector.dataEnd(); | |
| 223 | ||
| 224 | final var charset = detector.getDetectedCharset(); | |
| 225 | ||
| 226 | return charset == null | |
| 227 | ? DEFAULT_CHARSET | |
| 228 | : forName( charset.toUpperCase( ENGLISH ) ); | |
| 229 | } | |
| 230 | ||
| 231 | /** | |
| 232 | * Answers whether the given resource are of the same conceptual type. This | |
| 233 | * method is intended to be overridden by subclasses. | |
| 234 | * | |
| 235 | * @param mediaType The type to compare. | |
| 236 | * @return {@code true} if the {@link TextResource} is compatible with the | |
| 237 | * given {@link MediaType}. | |
| 238 | */ | |
| 239 | default boolean supports( final MediaType mediaType ) { | |
| 240 | return isMediaType( mediaType ); | |
| 241 | } | |
| 242 | } | |
| 1 | 243 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors.definition; | |
| 3 | ||
| 4 | import com.keenwrite.constants.Constants; | |
| 5 | import com.keenwrite.editors.TextDefinition; | |
| 6 | import com.keenwrite.sigils.Tokens; | |
| 7 | import com.keenwrite.ui.tree.AltTreeView; | |
| 8 | import com.keenwrite.ui.tree.TreeItemConverter; | |
| 9 | import javafx.beans.property.BooleanProperty; | |
| 10 | import javafx.beans.property.ReadOnlyBooleanProperty; | |
| 11 | import javafx.beans.property.SimpleBooleanProperty; | |
| 12 | import javafx.beans.value.ObservableValue; | |
| 13 | import javafx.collections.ObservableList; | |
| 14 | import javafx.event.ActionEvent; | |
| 15 | import javafx.event.Event; | |
| 16 | import javafx.event.EventHandler; | |
| 17 | import javafx.scene.Node; | |
| 18 | import javafx.scene.control.*; | |
| 19 | import javafx.scene.input.KeyEvent; | |
| 20 | import javafx.scene.layout.BorderPane; | |
| 21 | import javafx.scene.layout.HBox; | |
| 22 | ||
| 23 | import java.io.File; | |
| 24 | import java.nio.charset.Charset; | |
| 25 | import java.util.*; | |
| 26 | import java.util.regex.Pattern; | |
| 27 | ||
| 28 | import static com.keenwrite.constants.Constants.*; | |
| 29 | import static com.keenwrite.Messages.get; | |
| 30 | import static com.keenwrite.events.StatusEvent.clue; | |
| 31 | import static com.keenwrite.events.TextDefinitionFocusEvent.fireTextDefinitionFocus; | |
| 32 | import static com.keenwrite.ui.fonts.IconFactory.createGraphic; | |
| 33 | import static java.lang.String.format; | |
| 34 | import static java.util.regex.Pattern.compile; | |
| 35 | import static java.util.regex.Pattern.quote; | |
| 36 | import static javafx.geometry.Pos.CENTER; | |
| 37 | import static javafx.geometry.Pos.TOP_CENTER; | |
| 38 | import static javafx.scene.control.SelectionMode.MULTIPLE; | |
| 39 | import static javafx.scene.control.TreeItem.childrenModificationEvent; | |
| 40 | import static javafx.scene.control.TreeItem.valueChangedEvent; | |
| 41 | import static javafx.scene.input.KeyEvent.KEY_PRESSED; | |
| 42 | ||
| 43 | /** | |
| 44 | * Provides the user interface that holds a {@link TreeView}, which | |
| 45 | * allows users to interact with key/value pairs loaded from the | |
| 46 | * document parser and adapted using a {@link TreeTransformer}. | |
| 47 | */ | |
| 48 | public final class DefinitionEditor extends BorderPane | |
| 49 | implements TextDefinition { | |
| 50 | private static final int GROUP_DELIMITED = 1; | |
| 51 | ||
| 52 | /** | |
| 53 | * Contains the root that is added to the view. | |
| 54 | */ | |
| 55 | private final DefinitionTreeItem<String> mTreeRoot = createRootTreeItem(); | |
| 56 | ||
| 57 | /** | |
| 58 | * Contains a view of the definitions. | |
| 59 | */ | |
| 60 | private final TreeView<String> mTreeView = | |
| 61 | new AltTreeView<>( mTreeRoot, new TreeItemConverter() ); | |
| 62 | ||
| 63 | /** | |
| 64 | * Used to adapt the structured document into a {@link TreeView}. | |
| 65 | */ | |
| 66 | private final TreeTransformer mTreeTransformer; | |
| 67 | ||
| 68 | /** | |
| 69 | * Handlers for key press events. | |
| 70 | */ | |
| 71 | private final Set<EventHandler<? super KeyEvent>> mKeyEventHandlers | |
| 72 | = new HashSet<>(); | |
| 73 | ||
| 74 | /** | |
| 75 | * File being edited by this editor instance. | |
| 76 | */ | |
| 77 | private File mFile; | |
| 78 | ||
| 79 | /** | |
| 80 | * Opened file's character encoding, or {@link Constants#DEFAULT_CHARSET} if | |
| 81 | * either no encoding could be determined or this is a new (empty) file. | |
| 82 | */ | |
| 83 | private final Charset mEncoding; | |
| 84 | ||
| 85 | /** | |
| 86 | * Tracks whether the in-memory definitions have changed with respect to the | |
| 87 | * persisted definitions. | |
| 88 | */ | |
| 89 | private final BooleanProperty mModified = new SimpleBooleanProperty(); | |
| 90 | ||
| 91 | /** | |
| 92 | * This is provided for unit tests that are not backed by files. | |
| 93 | * | |
| 94 | * @param treeTransformer Responsible for transforming the definitions into | |
| 95 | * {@link TreeItem} instances. | |
| 96 | */ | |
| 97 | public DefinitionEditor( | |
| 98 | final TreeTransformer treeTransformer ) { | |
| 99 | this( DEFINITION_DEFAULT, treeTransformer ); | |
| 100 | } | |
| 101 | ||
| 102 | /** | |
| 103 | * Constructs a definition pane with a given tree view root. | |
| 104 | * | |
| 105 | * @param file The file of definitions to maintain through the UI. | |
| 106 | */ | |
| 107 | public DefinitionEditor( | |
| 108 | final File file, | |
| 109 | final TreeTransformer treeTransformer ) { | |
| 110 | assert file != null; | |
| 111 | assert treeTransformer != null; | |
| 112 | ||
| 113 | mFile = file; | |
| 114 | mTreeTransformer = treeTransformer; | |
| 115 | ||
| 116 | //mTreeView.setCellFactory( new TreeCellFactory() ); | |
| 117 | mTreeView.setContextMenu( createContextMenu() ); | |
| 118 | mTreeView.addEventFilter( KEY_PRESSED, this::keyEventFilter ); | |
| 119 | mTreeView.focusedProperty().addListener( this::focused ); | |
| 120 | getSelectionModel().setSelectionMode( MULTIPLE ); | |
| 121 | ||
| 122 | final var buttonBar = new HBox(); | |
| 123 | buttonBar.getChildren().addAll( | |
| 124 | createButton( "create", e -> createDefinition() ), | |
| 125 | createButton( "rename", e -> renameDefinition() ), | |
| 126 | createButton( "delete", e -> deleteDefinitions() ) | |
| 127 | ); | |
| 128 | buttonBar.setAlignment( CENTER ); | |
| 129 | buttonBar.setSpacing( UI_CONTROL_SPACING ); | |
| 130 | ||
| 131 | setTop( buttonBar ); | |
| 132 | setCenter( mTreeView ); | |
| 133 | setAlignment( buttonBar, TOP_CENTER ); | |
| 134 | mEncoding = open( mFile ); | |
| 135 | ||
| 136 | // After the file is opened, watch for changes, not before. Otherwise, | |
| 137 | // upon saving, users will be prompted to save a file that hasn't had | |
| 138 | // any modifications (from their perspective). | |
| 139 | addTreeChangeHandler( event -> mModified.set( true ) ); | |
| 140 | } | |
| 141 | ||
| 142 | @Override | |
| 143 | public void setText( final String document ) { | |
| 144 | final var foster = mTreeTransformer.transform( document ); | |
| 145 | final var biological = getTreeRoot(); | |
| 146 | ||
| 147 | for( final var child : foster.getChildren() ) { | |
| 148 | biological.getChildren().add( child ); | |
| 149 | } | |
| 150 | ||
| 151 | getTreeView().refresh(); | |
| 152 | } | |
| 153 | ||
| 154 | @Override | |
| 155 | public String getText() { | |
| 156 | final var result = new StringBuilder( 32768 ); | |
| 157 | ||
| 158 | try { | |
| 159 | final var root = getTreeView().getRoot(); | |
| 160 | final var problem = isTreeWellFormed(); | |
| 161 | ||
| 162 | problem.ifPresentOrElse( | |
| 163 | ( node ) -> clue( "yaml.error.tree.form", node ), | |
| 164 | () -> result.append( mTreeTransformer.transform( root ) ) | |
| 165 | ); | |
| 166 | } catch( final Exception ex ) { | |
| 167 | // Catch errors while checking for a well-formed tree (e.g., stack smash). | |
| 168 | // Also catch any transformation exceptions (e.g., Json processing). | |
| 169 | clue( ex ); | |
| 170 | } | |
| 171 | ||
| 172 | return result.toString(); | |
| 173 | } | |
| 174 | ||
| 175 | @Override | |
| 176 | public File getFile() { | |
| 177 | return mFile; | |
| 178 | } | |
| 179 | ||
| 180 | @Override | |
| 181 | public void rename( final File file ) { | |
| 182 | mFile = file; | |
| 183 | } | |
| 184 | ||
| 185 | @Override | |
| 186 | public Charset getEncoding() { | |
| 187 | return mEncoding; | |
| 188 | } | |
| 189 | ||
| 190 | @Override | |
| 191 | public Node getNode() { | |
| 192 | return this; | |
| 193 | } | |
| 194 | ||
| 195 | @Override | |
| 196 | public ReadOnlyBooleanProperty modifiedProperty() { | |
| 197 | return mModified; | |
| 198 | } | |
| 199 | ||
| 200 | @Override | |
| 201 | public void clearModifiedProperty() { | |
| 202 | mModified.setValue( false ); | |
| 203 | } | |
| 204 | ||
| 205 | private Button createButton( | |
| 206 | final String msgKey, final EventHandler<ActionEvent> eventHandler ) { | |
| 207 | final var keyPrefix = Constants.ACTION_PREFIX + "definition." + msgKey; | |
| 208 | final var button = new Button( get( keyPrefix + ".text" ) ); | |
| 209 | final var graphic = createGraphic( get( keyPrefix + ".icon" ) ); | |
| 210 | ||
| 211 | button.setOnAction( eventHandler ); | |
| 212 | button.setGraphic( graphic ); | |
| 213 | button.setTooltip( new Tooltip( get( keyPrefix + ".tooltip" ) ) ); | |
| 214 | ||
| 215 | return button; | |
| 216 | } | |
| 217 | ||
| 218 | @Override | |
| 219 | public Map<String, String> toMap() { | |
| 220 | return new TreeItemMapper().toMap( getTreeView().getRoot() ); | |
| 221 | } | |
| 222 | ||
| 223 | @Override | |
| 224 | public Map<String, String> interpolate( | |
| 225 | final Map<String, String> map, final Tokens tokens ) { | |
| 226 | ||
| 227 | // Non-greedy match of key names delimited by definition tokens. | |
| 228 | final var pattern = compile( | |
| 229 | format( "(%s.*?%s)", | |
| 230 | quote( tokens.getBegan() ), | |
| 231 | quote( tokens.getEnded() ) | |
| 232 | ) | |
| 233 | ); | |
| 234 | ||
| 235 | map.replaceAll( ( k, v ) -> resolve( map, v, pattern ) ); | |
| 236 | return map; | |
| 237 | } | |
| 238 | ||
| 239 | /** | |
| 240 | * Given a value with zero or more key references, this will resolve all | |
| 241 | * the values, recursively. If a key cannot be de-referenced, the value will | |
| 242 | * contain the key name. | |
| 243 | * | |
| 244 | * @param map Map to search for keys when resolving key references. | |
| 245 | * @param value Value containing zero or more key references. | |
| 246 | * @param pattern The regular expression pattern to match variable key names. | |
| 247 | * @return The given value with all embedded key references interpolated. | |
| 248 | */ | |
| 249 | private String resolve( | |
| 250 | final Map<String, String> map, String value, final Pattern pattern ) { | |
| 251 | final var matcher = pattern.matcher( value ); | |
| 252 | ||
| 253 | while( matcher.find() ) { | |
| 254 | final var keyName = matcher.group( GROUP_DELIMITED ); | |
| 255 | final var mapValue = map.get( keyName ); | |
| 256 | final var keyValue = mapValue == null | |
| 257 | ? keyName | |
| 258 | : resolve( map, mapValue, pattern ); | |
| 259 | ||
| 260 | value = value.replace( keyName, keyValue ); | |
| 261 | } | |
| 262 | ||
| 263 | return value; | |
| 264 | } | |
| 265 | ||
| 266 | ||
| 267 | /** | |
| 268 | * Informs the caller of whenever any {@link TreeItem} in the {@link TreeView} | |
| 269 | * is modified. The modifications include: item value changes, item additions, | |
| 270 | * and item removals. | |
| 271 | * <p> | |
| 272 | * Safe to call multiple times; if a handler is already registered, the | |
| 273 | * old handler is used. | |
| 274 | * </p> | |
| 275 | * | |
| 276 | * @param handler The handler to call whenever any {@link TreeItem} changes. | |
| 277 | */ | |
| 278 | public void addTreeChangeHandler( | |
| 279 | final EventHandler<TreeItem.TreeModificationEvent<Event>> handler ) { | |
| 280 | final var root = getTreeView().getRoot(); | |
| 281 | root.addEventHandler( valueChangedEvent(), handler ); | |
| 282 | root.addEventHandler( childrenModificationEvent(), handler ); | |
| 283 | } | |
| 284 | ||
| 285 | /** | |
| 286 | * Answers whether the {@link TreeItem}s in the {@link TreeView} are suitably | |
| 287 | * well-formed for export. A tree is considered well-formed if the following | |
| 288 | * conditions are met: | |
| 289 | * | |
| 290 | * <ul> | |
| 291 | * <li>The root node contains at least one child node having a leaf.</li> | |
| 292 | * <li>There are no leaf nodes with sibling leaf nodes.</li> | |
| 293 | * </ul> | |
| 294 | * | |
| 295 | * @return {@code null} if the document is well-formed, otherwise the | |
| 296 | * problematic child {@link TreeItem}. | |
| 297 | */ | |
| 298 | public Optional<TreeItem<String>> isTreeWellFormed() { | |
| 299 | final var root = getTreeView().getRoot(); | |
| 300 | ||
| 301 | for( final var child : root.getChildren() ) { | |
| 302 | final var problemChild = isWellFormed( child ); | |
| 303 | ||
| 304 | if( child.isLeaf() || problemChild != null ) { | |
| 305 | return Optional.ofNullable( problemChild ); | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | return Optional.empty(); | |
| 310 | } | |
| 311 | ||
| 312 | /** | |
| 313 | * Determines whether the document is well-formed by ensuring that | |
| 314 | * child branches do not contain multiple leaves. | |
| 315 | * | |
| 316 | * @param item The sub-tree to check for well-formedness. | |
| 317 | * @return {@code null} when the tree is well-formed, otherwise the | |
| 318 | * problematic {@link TreeItem}. | |
| 319 | */ | |
| 320 | private TreeItem<String> isWellFormed( final TreeItem<String> item ) { | |
| 321 | int childLeafs = 0; | |
| 322 | int childBranches = 0; | |
| 323 | ||
| 324 | for( final var child : item.getChildren() ) { | |
| 325 | if( child.isLeaf() ) { | |
| 326 | childLeafs++; | |
| 327 | } | |
| 328 | else { | |
| 329 | childBranches++; | |
| 330 | } | |
| 331 | ||
| 332 | final var problemChild = isWellFormed( child ); | |
| 333 | ||
| 334 | if( problemChild != null ) { | |
| 335 | return problemChild; | |
| 336 | } | |
| 337 | } | |
| 338 | ||
| 339 | return ((childBranches > 0 && childLeafs == 0) || | |
| 340 | (childBranches == 0 && childLeafs <= 1)) ? null : item; | |
| 341 | } | |
| 342 | ||
| 343 | @Override | |
| 344 | public DefinitionTreeItem<String> findLeafExact( final String text ) { | |
| 345 | return getTreeRoot().findLeafExact( text ); | |
| 346 | } | |
| 347 | ||
| 348 | @Override | |
| 349 | public DefinitionTreeItem<String> findLeafContains( final String text ) { | |
| 350 | return getTreeRoot().findLeafContains( text ); | |
| 351 | } | |
| 352 | ||
| 353 | @Override | |
| 354 | public DefinitionTreeItem<String> findLeafContainsNoCase( | |
| 355 | final String text ) { | |
| 356 | return getTreeRoot().findLeafContainsNoCase( text ); | |
| 357 | } | |
| 358 | ||
| 359 | @Override | |
| 360 | public DefinitionTreeItem<String> findLeafStartsWith( final String text ) { | |
| 361 | return getTreeRoot().findLeafStartsWith( text ); | |
| 362 | } | |
| 363 | ||
| 364 | public void select( final TreeItem<String> item ) { | |
| 365 | getSelectionModel().clearSelection(); | |
| 366 | getSelectionModel().select( getTreeView().getRow( item ) ); | |
| 367 | } | |
| 368 | ||
| 369 | /** | |
| 370 | * Collapses the tree, recursively. | |
| 371 | */ | |
| 372 | public void collapse() { | |
| 373 | collapse( getTreeRoot().getChildren() ); | |
| 374 | } | |
| 375 | ||
| 376 | /** | |
| 377 | * Collapses the tree, recursively. | |
| 378 | * | |
| 379 | * @param <T> The type of tree item to expand (usually String). | |
| 380 | * @param nodes The nodes to collapse. | |
| 381 | */ | |
| 382 | private <T> void collapse( final ObservableList<TreeItem<T>> nodes ) { | |
| 383 | for( final var node : nodes ) { | |
| 384 | node.setExpanded( false ); | |
| 385 | collapse( node.getChildren() ); | |
| 386 | } | |
| 387 | } | |
| 388 | ||
| 389 | /** | |
| 390 | * @return {@code true} when the user is editing a {@link TreeItem}. | |
| 391 | */ | |
| 392 | private boolean isEditingTreeItem() { | |
| 393 | return getTreeView().editingItemProperty().getValue() != null; | |
| 394 | } | |
| 395 | ||
| 396 | /** | |
| 397 | * Changes to edit mode for the selected item. | |
| 398 | */ | |
| 399 | @Override | |
| 400 | public void renameDefinition() { | |
| 401 | getTreeView().edit( getSelectedItem() ); | |
| 402 | } | |
| 403 | ||
| 404 | /** | |
| 405 | * Removes all selected items from the {@link TreeView}. | |
| 406 | */ | |
| 407 | @Override | |
| 408 | public void deleteDefinitions() { | |
| 409 | for( final var item : getSelectedItems() ) { | |
| 410 | final var parent = item.getParent(); | |
| 411 | ||
| 412 | if( parent != null ) { | |
| 413 | parent.getChildren().remove( item ); | |
| 414 | } | |
| 415 | } | |
| 416 | } | |
| 417 | ||
| 418 | /** | |
| 419 | * Deletes the selected item. | |
| 420 | */ | |
| 421 | private void deleteSelectedItem() { | |
| 422 | final var c = getSelectedItem(); | |
| 423 | getSiblings( c ).remove( c ); | |
| 424 | } | |
| 425 | ||
| 426 | /** | |
| 427 | * Adds a new item under the selected item (or root if nothing is selected). | |
| 428 | * There are a few conditions to consider: when adding to the root, | |
| 429 | * when adding to a leaf, and when adding to a non-leaf. Items added to the | |
| 430 | * root must contain two items: a key and a value. | |
| 431 | */ | |
| 432 | @Override | |
| 433 | public void createDefinition() { | |
| 434 | final var value = createDefinitionTreeItem(); | |
| 435 | getSelectedItem().getChildren().add( value ); | |
| 436 | expand( value ); | |
| 437 | select( value ); | |
| 438 | } | |
| 439 | ||
| 440 | private ContextMenu createContextMenu() { | |
| 441 | final var menu = new ContextMenu(); | |
| 442 | final var items = menu.getItems(); | |
| 443 | ||
| 444 | addMenuItem( items, ACTION_PREFIX + "definition.create.text" ) | |
| 445 | .setOnAction( e -> createDefinition() ); | |
| 446 | addMenuItem( items, ACTION_PREFIX + "definition.rename.text" ) | |
| 447 | .setOnAction( e -> renameDefinition() ); | |
| 448 | addMenuItem( items, ACTION_PREFIX + "definition.delete.text" ) | |
| 449 | .setOnAction( e -> deleteSelectedItem() ); | |
| 450 | ||
| 451 | return menu; | |
| 452 | } | |
| 453 | ||
| 454 | /** | |
| 455 | * Executes hot-keys for edits to the definition tree. | |
| 456 | * | |
| 457 | * @param event Contains the key code of the key that was pressed. | |
| 458 | */ | |
| 459 | private void keyEventFilter( final KeyEvent event ) { | |
| 460 | if( !isEditingTreeItem() ) { | |
| 461 | switch( event.getCode() ) { | |
| 462 | case ENTER -> { | |
| 463 | expand( getSelectedItem() ); | |
| 464 | event.consume(); | |
| 465 | } | |
| 466 | ||
| 467 | case DELETE -> deleteDefinitions(); | |
| 468 | case INSERT -> createDefinition(); | |
| 469 | ||
| 470 | case R -> { | |
| 471 | if( event.isControlDown() ) { | |
| 472 | renameDefinition(); | |
| 473 | } | |
| 474 | } | |
| 475 | } | |
| 476 | ||
| 477 | for( final var handler : getKeyEventHandlers() ) { | |
| 478 | handler.handle( event ); | |
| 479 | } | |
| 480 | } | |
| 481 | } | |
| 482 | ||
| 483 | /** | |
| 484 | * Called when the editor's input focus changes. This will fire an event | |
| 485 | * for subscribers. | |
| 486 | * | |
| 487 | * @param ignored Not used. | |
| 488 | * @param o The old input focus property value. | |
| 489 | * @param n The new input focus property value. | |
| 490 | */ | |
| 491 | private void focused( | |
| 492 | final ObservableValue<? extends Boolean> ignored, | |
| 493 | final Boolean o, | |
| 494 | final Boolean n ) { | |
| 495 | if( n != null && n ) { | |
| 496 | fireTextDefinitionFocus( this ); | |
| 497 | } | |
| 498 | } | |
| 499 | ||
| 500 | /** | |
| 501 | * Adds a menu item to a list of menu items. | |
| 502 | * | |
| 503 | * @param items The list of menu items to append to. | |
| 504 | * @param labelKey The resource bundle key name for the menu item's label. | |
| 505 | * @return The menu item added to the list of menu items. | |
| 506 | */ | |
| 507 | private MenuItem addMenuItem( | |
| 508 | final List<MenuItem> items, final String labelKey ) { | |
| 509 | final MenuItem menuItem = createMenuItem( labelKey ); | |
| 510 | items.add( menuItem ); | |
| 511 | return menuItem; | |
| 512 | } | |
| 513 | ||
| 514 | private MenuItem createMenuItem( final String labelKey ) { | |
| 515 | return new MenuItem( get( labelKey ) ); | |
| 516 | } | |
| 517 | ||
| 518 | /** | |
| 519 | * Creates a new {@link TreeItem} that is intended to be the root-level item | |
| 520 | * added to the {@link TreeView}. This allows the root item to be | |
| 521 | * distinguished from the other items so that reference keys do not include | |
| 522 | * "Definition" as part of their name. | |
| 523 | * | |
| 524 | * @return A new {@link TreeItem}, never {@code null}. | |
| 525 | */ | |
| 526 | private RootTreeItem<String> createRootTreeItem() { | |
| 527 | return new RootTreeItem<>( get( "Pane.definition.node.root.title" ) ); | |
| 528 | } | |
| 529 | ||
| 530 | private DefinitionTreeItem<String> createDefinitionTreeItem() { | |
| 531 | return new DefinitionTreeItem<>( get( "Definition.menu.add.default" ) ); | |
| 532 | } | |
| 533 | ||
| 534 | @Override | |
| 535 | public void requestFocus() { | |
| 536 | //super.requestFocus(); | |
| 537 | getTreeView().requestFocus(); | |
| 538 | } | |
| 539 | ||
| 540 | /** | |
| 541 | * Expands the node to the root, recursively. | |
| 542 | * | |
| 543 | * @param <T> The type of tree item to expand (usually String). | |
| 544 | * @param node The node to expand. | |
| 545 | */ | |
| 546 | @Override | |
| 547 | public <T> void expand( final TreeItem<T> node ) { | |
| 548 | if( node != null ) { | |
| 549 | expand( node.getParent() ); | |
| 550 | node.setExpanded( !node.isLeaf() ); | |
| 551 | } | |
| 552 | } | |
| 553 | ||
| 554 | /** | |
| 555 | * Answers whether there are any definitions in the tree. | |
| 556 | * | |
| 557 | * @return {@code true} when there are no definitions; {@code false} when | |
| 558 | * there's at least one definition. | |
| 559 | */ | |
| 560 | @Override | |
| 561 | public boolean isEmpty() { | |
| 562 | return getTreeRoot().isEmpty(); | |
| 563 | } | |
| 564 | ||
| 565 | /** | |
| 566 | * Returns the actively selected item in the tree. | |
| 567 | * | |
| 568 | * @return The selected item, or the tree root item if no item is selected. | |
| 569 | */ | |
| 570 | public TreeItem<String> getSelectedItem() { | |
| 571 | final var item = getSelectionModel().getSelectedItem(); | |
| 572 | return item == null ? getTreeRoot() : item; | |
| 573 | } | |
| 574 | ||
| 575 | /** | |
| 576 | * Returns the {@link TreeView} that contains the definition hierarchy. | |
| 577 | * | |
| 578 | * @return A non-null instance. | |
| 579 | */ | |
| 580 | private TreeView<String> getTreeView() { | |
| 581 | return mTreeView; | |
| 582 | } | |
| 583 | ||
| 584 | /** | |
| 585 | * Returns the root of the tree. | |
| 586 | * | |
| 587 | * @return The first node added to the definition tree. | |
| 588 | */ | |
| 589 | private DefinitionTreeItem<String> getTreeRoot() { | |
| 590 | return mTreeRoot; | |
| 591 | } | |
| 592 | ||
| 593 | private ObservableList<TreeItem<String>> getSiblings( | |
| 594 | final TreeItem<String> item ) { | |
| 595 | final var root = getTreeView().getRoot(); | |
| 596 | final var parent = (item == null || item == root) ? root : item.getParent(); | |
| 597 | ||
| 598 | return parent.getChildren(); | |
| 599 | } | |
| 600 | ||
| 601 | private MultipleSelectionModel<TreeItem<String>> getSelectionModel() { | |
| 602 | return getTreeView().getSelectionModel(); | |
| 603 | } | |
| 604 | ||
| 605 | /** | |
| 606 | * Returns a copy of all the selected items. | |
| 607 | * | |
| 608 | * @return A list, possibly empty, containing all selected items in the | |
| 609 | * {@link TreeView}. | |
| 610 | */ | |
| 611 | private List<TreeItem<String>> getSelectedItems() { | |
| 612 | return new ArrayList<>( getSelectionModel().getSelectedItems() ); | |
| 613 | } | |
| 614 | ||
| 615 | private Set<EventHandler<? super KeyEvent>> getKeyEventHandlers() { | |
| 616 | return mKeyEventHandlers; | |
| 617 | } | |
| 618 | } | |
| 1 | 619 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors.definition; | |
| 3 | ||
| 4 | import javafx.scene.control.TreeItem; | |
| 5 | ||
| 6 | import java.util.Stack; | |
| 7 | import java.util.function.BiFunction; | |
| 8 | ||
| 9 | import static java.text.Normalizer.Form.NFD; | |
| 10 | import static java.text.Normalizer.normalize; | |
| 11 | ||
| 12 | /** | |
| 13 | * Provides behaviour afforded to definition keys and corresponding value. | |
| 14 | * | |
| 15 | * @param <T> The type of {@link TreeItem} (usually string). | |
| 16 | */ | |
| 17 | public class DefinitionTreeItem<T> extends TreeItem<T> { | |
| 18 | ||
| 19 | /** | |
| 20 | * Constructs a new item with a default value. | |
| 21 | * | |
| 22 | * @param value Passed up to superclass. | |
| 23 | */ | |
| 24 | public DefinitionTreeItem( final T value ) { | |
| 25 | super( value ); | |
| 26 | } | |
| 27 | ||
| 28 | /** | |
| 29 | * Finds a leaf starting at the current node with text that matches the given | |
| 30 | * value. Search is performed case-sensitively. | |
| 31 | * | |
| 32 | * @param text The text to match against each leaf in the tree. | |
| 33 | * @return The leaf that has a value exactly matching the given text. | |
| 34 | */ | |
| 35 | public DefinitionTreeItem<T> findLeafExact( final String text ) { | |
| 36 | return findLeaf( text, DefinitionTreeItem::valueEquals ); | |
| 37 | } | |
| 38 | ||
| 39 | /** | |
| 40 | * Finds a leaf starting at the current node with text that matches the given | |
| 41 | * value. Search is performed case-sensitively. | |
| 42 | * | |
| 43 | * @param text The text to match against each leaf in the tree. | |
| 44 | * @return The leaf that has a value that contains the given text. | |
| 45 | */ | |
| 46 | public DefinitionTreeItem<T> findLeafContains( final String text ) { | |
| 47 | return findLeaf( text, DefinitionTreeItem::valueContains ); | |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Finds a leaf starting at the current node with text that matches the given | |
| 52 | * value. Search is performed case-insensitively. | |
| 53 | * | |
| 54 | * @param text The text to match against each leaf in the tree. | |
| 55 | * @return The leaf that has a value that contains the given text. | |
| 56 | */ | |
| 57 | public DefinitionTreeItem<T> findLeafContainsNoCase( final String text ) { | |
| 58 | return findLeaf( text, DefinitionTreeItem::valueContainsNoCase ); | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * Finds a leaf starting at the current node with text that matches the given | |
| 63 | * value. Search is performed case-sensitively. | |
| 64 | * | |
| 65 | * @param text The text to match against each leaf in the tree. | |
| 66 | * @return The leaf that has a value that starts with the given text. | |
| 67 | */ | |
| 68 | public DefinitionTreeItem<T> findLeafStartsWith( final String text ) { | |
| 69 | return findLeaf( text, DefinitionTreeItem::valueStartsWith ); | |
| 70 | } | |
| 71 | ||
| 72 | /** | |
| 73 | * Finds a leaf starting at the current node with text that matches the given | |
| 74 | * value. | |
| 75 | * | |
| 76 | * @param text The text to match against each leaf in the tree. | |
| 77 | * @param findMode What algorithm is used to match the given text. | |
| 78 | * @return The leaf that has a value starting with the given text, or {@code | |
| 79 | * null} if there was no match found. | |
| 80 | */ | |
| 81 | public DefinitionTreeItem<T> findLeaf( | |
| 82 | final String text, | |
| 83 | final BiFunction<DefinitionTreeItem<T>, String, Boolean> findMode ) { | |
| 84 | final var stack = new Stack<DefinitionTreeItem<T>>(); | |
| 85 | stack.push( this ); | |
| 86 | ||
| 87 | // Don't hunt for blank (empty) keys. | |
| 88 | boolean found = text.isBlank(); | |
| 89 | ||
| 90 | while( !found && !stack.isEmpty() ) { | |
| 91 | final var node = stack.pop(); | |
| 92 | ||
| 93 | for( final var child : node.getChildren() ) { | |
| 94 | final var result = (DefinitionTreeItem<T>) child; | |
| 95 | ||
| 96 | if( result.isLeaf() ) { | |
| 97 | if( found = findMode.apply( result, text ) ) { | |
| 98 | return result; | |
| 99 | } | |
| 100 | } | |
| 101 | else { | |
| 102 | stack.push( result ); | |
| 103 | } | |
| 104 | } | |
| 105 | } | |
| 106 | ||
| 107 | return null; | |
| 108 | } | |
| 109 | ||
| 110 | /** | |
| 111 | * Returns the value of the string without diacritic marks. | |
| 112 | * | |
| 113 | * @return A non-null, possibly empty string. | |
| 114 | */ | |
| 115 | private String getDiacriticlessValue() { | |
| 116 | return normalize( getValue().toString(), NFD ) | |
| 117 | .replaceAll( "\\p{M}", "" ); | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 121 | * Returns true if this node is a leaf and its value equals the given text. | |
| 122 | * | |
| 123 | * @param s The text to compare against the node value. | |
| 124 | * @return true Node is a leaf and its value equals the given value. | |
| 125 | */ | |
| 126 | private boolean valueEquals( final String s ) { | |
| 127 | return isLeaf() && getValue().equals( s ); | |
| 128 | } | |
| 129 | ||
| 130 | /** | |
| 131 | * Returns true if this node is a leaf and its value contains the given text. | |
| 132 | * | |
| 133 | * @param s The text to compare against the node value. | |
| 134 | * @return true Node is a leaf and its value contains the given value. | |
| 135 | */ | |
| 136 | private boolean valueContains( final String s ) { | |
| 137 | return isLeaf() && getDiacriticlessValue().contains( s ); | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Returns true if this node is a leaf and its value contains the given text. | |
| 142 | * | |
| 143 | * @param s The text to compare against the node value. | |
| 144 | * @return true Node is a leaf and its value contains the given value. | |
| 145 | */ | |
| 146 | private boolean valueContainsNoCase( final String s ) { | |
| 147 | return isLeaf() && | |
| 148 | getDiacriticlessValue().toLowerCase().contains( s.toLowerCase() ); | |
| 149 | } | |
| 150 | ||
| 151 | /** | |
| 152 | * Returns true if this node is a leaf and its value starts with the given | |
| 153 | * text. | |
| 154 | * | |
| 155 | * @param s The text to compare against the node value. | |
| 156 | * @return true Node is a leaf and its value starts with the given value. | |
| 157 | */ | |
| 158 | private boolean valueStartsWith( final String s ) { | |
| 159 | return isLeaf() && getDiacriticlessValue().startsWith( s ); | |
| 160 | } | |
| 161 | ||
| 162 | /** | |
| 163 | * Returns the path for this node, with nodes made distinct using the | |
| 164 | * separator character. This uses two loops: one for pushing nodes onto a | |
| 165 | * stack and one for popping them off to create the path in desired order. | |
| 166 | * | |
| 167 | * @return A non-null string, possibly empty. | |
| 168 | */ | |
| 169 | public String toPath() { | |
| 170 | return new TreeItemMapper().toPath( getParent() ); | |
| 171 | } | |
| 172 | ||
| 173 | /** | |
| 174 | * Answers whether there are any definitions in this tree. | |
| 175 | * | |
| 176 | * @return {@code true} when there are no definitions in the tree; {@code | |
| 177 | * false} when there is at least one definition present. | |
| 178 | */ | |
| 179 | public boolean isEmpty() { | |
| 180 | return getChildren().isEmpty(); | |
| 181 | } | |
| 182 | } | |
| 1 | 183 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors.definition; | |
| 3 | ||
| 4 | import javafx.scene.control.TreeItem; | |
| 5 | import javafx.scene.control.TreeView; | |
| 6 | ||
| 7 | /** | |
| 8 | * Marker interface for top-most {@link TreeItem}. This class allows the | |
| 9 | * {@link TreeItemMapper} to ignore the topmost definition. Such contortions | |
| 10 | * are necessary because {@link TreeView} requires a root item that isn't part | |
| 11 | * of the user's definition file. | |
| 12 | * <p> | |
| 13 | * Another approach would be to associate object pairs per {@link TreeItem}, | |
| 14 | * but that would be a waste of memory since the only "exception" case is | |
| 15 | * the root {@link TreeItem}. | |
| 16 | * </p> | |
| 17 | * | |
| 18 | * @param <T> The type of {@link TreeItem} to store in the {@link TreeView}. | |
| 19 | */ | |
| 20 | public final class RootTreeItem<T> extends DefinitionTreeItem<T> { | |
| 21 | /** | |
| 22 | * Default constructor, calls the superclass, no other behaviour. | |
| 23 | * | |
| 24 | * @param value The {@link TreeItem} node name to construct the superclass. | |
| 25 | * @see TreeItemMapper#toMap(TreeItem) for details on how this | |
| 26 | * class is used. | |
| 27 | */ | |
| 28 | public RootTreeItem( final T value ) { | |
| 29 | super( value ); | |
| 30 | } | |
| 31 | } | |
| 1 | 32 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors.definition; | |
| 3 | ||
| 4 | import com.fasterxml.jackson.databind.JsonNode; | |
| 5 | import com.keenwrite.preview.HtmlPreview; | |
| 6 | import javafx.scene.control.TreeItem; | |
| 7 | import javafx.scene.control.TreeView; | |
| 8 | ||
| 9 | import java.util.HashMap; | |
| 10 | import java.util.Iterator; | |
| 11 | import java.util.Map; | |
| 12 | import java.util.Stack; | |
| 13 | ||
| 14 | import static com.keenwrite.constants.Constants.MAP_SIZE_DEFAULT; | |
| 15 | ||
| 16 | /** | |
| 17 | * Given a {@link TreeItem}, this will generate a flat map with all the | |
| 18 | * values in the tree recursively interpolated. The application integrates | |
| 19 | * definition files as follows: | |
| 20 | * <ol> | |
| 21 | * <li>Load YAML file into {@link JsonNode} hierarchy.</li> | |
| 22 | * <li>Convert JsonNode to a {@link TreeItem} hierarchy.</li> | |
| 23 | * <li>Interpolate {@link TreeItem} hierarchy as a flat map.</li> | |
| 24 | * <li>Substitute flat map variables into document as required.</li> | |
| 25 | * </ol> | |
| 26 | * | |
| 27 | * <p> | |
| 28 | * This class is responsible for producing the interpolated flat map. This | |
| 29 | * allows dynamic edits of the {@link TreeView} to be displayed in the | |
| 30 | * {@link HtmlPreview} without having to reload the definition file. | |
| 31 | * Reloading the definition file would work, but has a number of drawbacks. | |
| 32 | * </p> | |
| 33 | */ | |
| 34 | public final class TreeItemMapper { | |
| 35 | /** | |
| 36 | * Separates definition keys (e.g., the dots in {@code $root.node.var$}). | |
| 37 | */ | |
| 38 | public static final String SEPARATOR = "."; | |
| 39 | ||
| 40 | /** | |
| 41 | * Default buffer length for keys ({@link StringBuilder} has 16 character | |
| 42 | * buffer) that should be large enough for most keys to avoid reallocating | |
| 43 | * memory to increase the {@link StringBuilder}'s buffer. | |
| 44 | */ | |
| 45 | public static final int DEFAULT_KEY_LENGTH = 64; | |
| 46 | ||
| 47 | /** | |
| 48 | * In-order traversal of a {@link TreeItem} hierarchy, exposing each item | |
| 49 | * as a consecutive list. | |
| 50 | */ | |
| 51 | private static final class TreeIterator | |
| 52 | implements Iterator<TreeItem<String>> { | |
| 53 | private final Stack<TreeItem<String>> mStack = new Stack<>(); | |
| 54 | ||
| 55 | public TreeIterator( final TreeItem<String> root ) { | |
| 56 | if( root != null ) { | |
| 57 | mStack.push( root ); | |
| 58 | } | |
| 59 | } | |
| 60 | ||
| 61 | @Override | |
| 62 | public boolean hasNext() { | |
| 63 | return !mStack.isEmpty(); | |
| 64 | } | |
| 65 | ||
| 66 | @Override | |
| 67 | public TreeItem<String> next() { | |
| 68 | final TreeItem<String> next = mStack.pop(); | |
| 69 | next.getChildren().forEach( mStack::push ); | |
| 70 | ||
| 71 | return next; | |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | public TreeItemMapper() { | |
| 76 | } | |
| 77 | ||
| 78 | /** | |
| 79 | * Iterate over a given root node (at any level of the tree) and process each | |
| 80 | * leaf node into a flat map. Values must be interpolated separately. | |
| 81 | */ | |
| 82 | public Map<String, String> toMap( final TreeItem<String> root ) { | |
| 83 | final var map = new HashMap<String, String>( MAP_SIZE_DEFAULT ); | |
| 84 | final var iterator = new TreeIterator( root ); | |
| 85 | ||
| 86 | iterator.forEachRemaining( item -> { | |
| 87 | if( item.isLeaf() ) { | |
| 88 | map.put( toPath( item.getParent() ), item.getValue() ); | |
| 89 | } | |
| 90 | } ); | |
| 91 | ||
| 92 | return map; | |
| 93 | } | |
| 94 | ||
| 95 | /** | |
| 96 | * For a given node, this will ascend the tree to generate a key name | |
| 97 | * that is associated with the leaf node's value. | |
| 98 | * | |
| 99 | * @param node Ascendants represent the key to this node's value. | |
| 100 | * @param <T> Data type that the {@link TreeItem} contains. | |
| 101 | * @return The string representation of the node's unique key. | |
| 102 | */ | |
| 103 | public <T> String toPath( TreeItem<T> node ) { | |
| 104 | assert node != null; | |
| 105 | ||
| 106 | final var key = new StringBuilder( DEFAULT_KEY_LENGTH ); | |
| 107 | final var stack = new Stack<TreeItem<T>>(); | |
| 108 | ||
| 109 | while( node != null && !(node instanceof RootTreeItem) ) { | |
| 110 | stack.push( node ); | |
| 111 | node = node.getParent(); | |
| 112 | } | |
| 113 | ||
| 114 | // Gets set at end of first iteration (to avoid an if condition). | |
| 115 | var separator = ""; | |
| 116 | ||
| 117 | while( !stack.empty() ) { | |
| 118 | final T subkey = stack.pop().getValue(); | |
| 119 | key.append( separator ); | |
| 120 | key.append( subkey ); | |
| 121 | separator = SEPARATOR; | |
| 122 | } | |
| 123 | ||
| 124 | return key.toString(); | |
| 125 | } | |
| 126 | } | |
| 1 | 127 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors.definition; | |
| 3 | ||
| 4 | import javafx.scene.control.TreeItem; | |
| 5 | ||
| 6 | /** | |
| 7 | * Responsible for converting an object hierarchy into a {@link TreeItem} | |
| 8 | * hierarchy. | |
| 9 | */ | |
| 10 | public interface TreeTransformer { | |
| 11 | /** | |
| 12 | * Adapts the document produced by the given parser into a {@link TreeItem} | |
| 13 | * object that can be presented to the user within a GUI. The root of the | |
| 14 | * tree must be merged by the view layer. | |
| 15 | * | |
| 16 | * @param document The document to transform into a viewable hierarchy. | |
| 17 | */ | |
| 18 | TreeItem<String> transform( String document ); | |
| 19 | ||
| 20 | /** | |
| 21 | * Exports the given root node to the given path. | |
| 22 | * | |
| 23 | * @param root The root node to export. | |
| 24 | */ | |
| 25 | String transform( TreeItem<String> root ); | |
| 26 | } | |
| 1 | 27 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | ||
| 3 | /** | |
| 4 | * This package contains classes that pertain to hierarchical, structured | |
| 5 | * data formats, which can be used as interpolated variables. | |
| 6 | */ | |
| 7 | package com.keenwrite.editors.definition; | |
| 1 | 8 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors.definition.yaml; | |
| 3 | ||
| 4 | import com.fasterxml.jackson.databind.JsonNode; | |
| 5 | import com.fasterxml.jackson.databind.ObjectMapper; | |
| 6 | import com.fasterxml.jackson.databind.node.ObjectNode; | |
| 7 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | |
| 8 | import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; | |
| 9 | import com.keenwrite.editors.definition.DefinitionTreeItem; | |
| 10 | import com.keenwrite.editors.definition.TreeTransformer; | |
| 11 | import javafx.scene.control.TreeItem; | |
| 12 | import javafx.scene.control.TreeView; | |
| 13 | ||
| 14 | import java.util.Map.Entry; | |
| 15 | ||
| 16 | import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.MINIMIZE_QUOTES; | |
| 17 | import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.SPLIT_LINES; | |
| 18 | import static com.keenwrite.events.StatusEvent.clue; | |
| 19 | ||
| 20 | /** | |
| 21 | * Transforms a JsonNode hierarchy into a tree that can be displayed in a user | |
| 22 | * interface and vice-versa. | |
| 23 | */ | |
| 24 | public final class YamlTreeTransformer implements TreeTransformer { | |
| 25 | private static final YAMLFactory sFactory; | |
| 26 | private static final YAMLMapper sMapper; | |
| 27 | ||
| 28 | static { | |
| 29 | sFactory = new YAMLFactory(); | |
| 30 | sFactory.configure( MINIMIZE_QUOTES, true ); | |
| 31 | sFactory.configure( SPLIT_LINES, false ); | |
| 32 | sMapper = new YAMLMapper( sFactory ); | |
| 33 | } | |
| 34 | ||
| 35 | /** | |
| 36 | * Constructs a new instance that will use the given path to read the object | |
| 37 | * hierarchy from a data source. | |
| 38 | */ | |
| 39 | public YamlTreeTransformer() { | |
| 40 | } | |
| 41 | ||
| 42 | @Override | |
| 43 | public String transform( final TreeItem<String> treeItem ) { | |
| 44 | try { | |
| 45 | final var root = sMapper.createObjectNode(); | |
| 46 | ||
| 47 | // Iterate over the root item's children. The root item is used by the | |
| 48 | // application to ensure definitions can always be added to a tree, as | |
| 49 | // such it is not meant to be exported, only its children. | |
| 50 | for( final var child : treeItem.getChildren() ) { | |
| 51 | transform( child, root ); | |
| 52 | } | |
| 53 | ||
| 54 | return sMapper.writeValueAsString( root ); | |
| 55 | } catch( final Exception ex ) { | |
| 56 | clue( ex ); | |
| 57 | throw new RuntimeException( ex ); | |
| 58 | } | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * Converts a YAML document to a {@link TreeItem} based on the document | |
| 63 | * keys. | |
| 64 | * | |
| 65 | * @param document The YAML document to convert to a hierarchy of | |
| 66 | * {@link TreeItem} instances. | |
| 67 | * @throws StackOverflowError If infinite recursion is encountered. | |
| 68 | */ | |
| 69 | @Override | |
| 70 | public TreeItem<String> transform( final String document ) { | |
| 71 | final var jsonNode = toJson( document ); | |
| 72 | final var rootItem = createTreeItem( "root" ); | |
| 73 | ||
| 74 | transform( jsonNode, rootItem ); | |
| 75 | ||
| 76 | return rootItem; | |
| 77 | } | |
| 78 | ||
| 79 | private JsonNode toJson( final String yaml ) { | |
| 80 | try { | |
| 81 | return new ObjectMapper( sFactory ).readTree( yaml ); | |
| 82 | } catch( final Exception ex ) { | |
| 83 | // Ensure that a document root node exists. | |
| 84 | return new ObjectMapper().createObjectNode(); | |
| 85 | } | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Recursive method to generate an object hierarchy that represents the | |
| 90 | * given {@link TreeItem} hierarchy. | |
| 91 | * | |
| 92 | * @param item The {@link TreeItem} to reproduce as an object hierarchy. | |
| 93 | * @param node The {@link ObjectNode} to update to reflect the | |
| 94 | * {@link TreeItem} hierarchy. | |
| 95 | */ | |
| 96 | private void transform( final TreeItem<String> item, ObjectNode node ) { | |
| 97 | final var children = item.getChildren(); | |
| 98 | ||
| 99 | // If the current item has more than one non-leaf child, it's an | |
| 100 | // object node and must become a new nested object. | |
| 101 | if( !(children.size() == 1 && children.get( 0 ).isLeaf()) ) { | |
| 102 | node = node.putObject( item.getValue() ); | |
| 103 | } | |
| 104 | ||
| 105 | for( final var child : children ) { | |
| 106 | if( child.isLeaf() ) { | |
| 107 | node.put( item.getValue(), child.getValue() ); | |
| 108 | } | |
| 109 | else { | |
| 110 | transform( child, node ); | |
| 111 | } | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | /** | |
| 116 | * Iterate over a given root node (at any level of the tree) and adapt each | |
| 117 | * leaf node. | |
| 118 | * | |
| 119 | * @param node A JSON node (YAML node) to adapt. | |
| 120 | * @param item The tree item to use as the root when processing the node. | |
| 121 | * @throws StackOverflowError If infinite recursion is encountered. | |
| 122 | */ | |
| 123 | private void transform( final JsonNode node, final TreeItem<String> item ) { | |
| 124 | node.fields().forEachRemaining( leaf -> transform( leaf, item ) ); | |
| 125 | } | |
| 126 | ||
| 127 | /** | |
| 128 | * Recursively adapt each rootNode to a corresponding rootItem. | |
| 129 | * | |
| 130 | * @param node The node to adapt. | |
| 131 | * @param item The item to adapt using the node's key. | |
| 132 | * @throws StackOverflowError If infinite recursion is encountered. | |
| 133 | */ | |
| 134 | private void transform( | |
| 135 | final Entry<String, JsonNode> node, final TreeItem<String> item ) { | |
| 136 | final var leafNode = node.getValue(); | |
| 137 | final var key = node.getKey(); | |
| 138 | final var leaf = createTreeItem( key ); | |
| 139 | ||
| 140 | if( leafNode.isValueNode() ) { | |
| 141 | leaf.getChildren().add( createTreeItem( node.getValue().asText() ) ); | |
| 142 | } | |
| 143 | ||
| 144 | item.getChildren().add( leaf ); | |
| 145 | ||
| 146 | if( leafNode.isObject() ) { | |
| 147 | transform( leafNode, leaf ); | |
| 148 | } | |
| 149 | } | |
| 150 | ||
| 151 | /** | |
| 152 | * Creates a new {@link TreeItem} that can be added to the {@link TreeView}. | |
| 153 | * | |
| 154 | * @param value The node's value. | |
| 155 | * @return A new {@link TreeItem}, never {@code null}. | |
| 156 | */ | |
| 157 | private TreeItem<String> createTreeItem( final String value ) { | |
| 158 | return new DefinitionTreeItem<>( value ); | |
| 159 | } | |
| 160 | } | |
| 1 | 161 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | ||
| 3 | /** | |
| 4 | * This package contains classes that can parse YAML documents into a GUI | |
| 5 | * representation. | |
| 6 | */ | |
| 7 | package com.keenwrite.editors.definition.yaml; | |
| 1 | 8 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors.markdown; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.ast.Link; | |
| 5 | ||
| 6 | /** | |
| 7 | * Represents the model for a hyperlink: text, url, and title. | |
| 8 | */ | |
| 9 | public final class HyperlinkModel { | |
| 10 | ||
| 11 | private String text; | |
| 12 | private String url; | |
| 13 | private String title; | |
| 14 | ||
| 15 | /** | |
| 16 | * Constructs a new hyperlink model in Markdown format by default with no | |
| 17 | * title (i.e., tooltip). | |
| 18 | * | |
| 19 | * @param text The hyperlink text displayed (e.g., displayed to the user). | |
| 20 | * @param url The destination URL (e.g., when clicked). | |
| 21 | */ | |
| 22 | public HyperlinkModel( final String text, final String url ) { | |
| 23 | this( text, url, null ); | |
| 24 | } | |
| 25 | ||
| 26 | /** | |
| 27 | * Constructs a new hyperlink model for the given AST link. | |
| 28 | * | |
| 29 | * @param link A Markdown link. | |
| 30 | */ | |
| 31 | public HyperlinkModel( final Link link ) { | |
| 32 | this( | |
| 33 | link.getText().toString(), | |
| 34 | link.getUrl().toString(), | |
| 35 | link.getTitle().toString() | |
| 36 | ); | |
| 37 | } | |
| 38 | ||
| 39 | /** | |
| 40 | * Constructs a new hyperlink model in Markdown format by default. | |
| 41 | * | |
| 42 | * @param text The hyperlink text displayed (e.g., displayed to the user). | |
| 43 | * @param url The destination URL (e.g., when clicked). | |
| 44 | * @param title The hyperlink title (e.g., shown as a tooltip). | |
| 45 | */ | |
| 46 | public HyperlinkModel( | |
| 47 | final String text, final String url, final String title ) { | |
| 48 | setText( text ); | |
| 49 | setUrl( url ); | |
| 50 | setTitle( title ); | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Returns the string in Markdown format by default. | |
| 55 | * | |
| 56 | * @return A Markdown version of the hyperlink. | |
| 57 | */ | |
| 58 | @Override | |
| 59 | public String toString() { | |
| 60 | String format = "%s%s%s"; | |
| 61 | ||
| 62 | if( hasText() ) { | |
| 63 | format = "[%s]" + (hasTitle() ? "(%s \"%s\")" : "(%s%s)"); | |
| 64 | } | |
| 65 | ||
| 66 | // Becomes ""+URL+"" if no text is set. | |
| 67 | // Becomes [TITLE]+(URL)+"" if no title is set. | |
| 68 | // Becomes [TITLE]+(URL+ \"TITLE\") if title is set. | |
| 69 | return String.format( format, getText(), getUrl(), getTitle() ); | |
| 70 | } | |
| 71 | ||
| 72 | public final void setText( final String text ) { | |
| 73 | this.text = sanitize( text ); | |
| 74 | } | |
| 75 | ||
| 76 | public final void setUrl( final String url ) { | |
| 77 | this.url = sanitize( url ); | |
| 78 | } | |
| 79 | ||
| 80 | public final void setTitle( final String title ) { | |
| 81 | this.title = sanitize( title ); | |
| 82 | } | |
| 83 | ||
| 84 | /** | |
| 85 | * Answers whether text has been set for the hyperlink. | |
| 86 | * | |
| 87 | * @return true This is a text link. | |
| 88 | */ | |
| 89 | public boolean hasText() { | |
| 90 | return !getText().isEmpty(); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Answers whether a title (tooltip) has been set for the hyperlink. | |
| 95 | * | |
| 96 | * @return true There is a title. | |
| 97 | */ | |
| 98 | public boolean hasTitle() { | |
| 99 | return !getTitle().isEmpty(); | |
| 100 | } | |
| 101 | ||
| 102 | public String getText() { | |
| 103 | return this.text; | |
| 104 | } | |
| 105 | ||
| 106 | public String getUrl() { | |
| 107 | return this.url; | |
| 108 | } | |
| 109 | ||
| 110 | public String getTitle() { | |
| 111 | return this.title; | |
| 112 | } | |
| 113 | ||
| 114 | private String sanitize( final String s ) { | |
| 115 | return s == null ? "" : s; | |
| 116 | } | |
| 117 | } | |
| 1 | 118 |
| 1 | /* | |
| 2 | * Copyright 2020-2021 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.keenwrite.editors.markdown; | |
| 29 | ||
| 30 | import com.vladsch.flexmark.ast.Link; | |
| 31 | import com.vladsch.flexmark.util.ast.Node; | |
| 32 | import com.vladsch.flexmark.util.ast.NodeVisitor; | |
| 33 | import com.vladsch.flexmark.util.ast.VisitHandler; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for extracting a hyperlink from the document so that the user | |
| 37 | * can edit the link within a dialog. | |
| 38 | */ | |
| 39 | public final class LinkVisitor { | |
| 40 | ||
| 41 | private NodeVisitor mVisitor; | |
| 42 | private Link mLink; | |
| 43 | private final int mOffset; | |
| 44 | ||
| 45 | /** | |
| 46 | * Creates a hyperlink given an offset into a paragraph and the Markdown AST | |
| 47 | * link node. | |
| 48 | * | |
| 49 | * @param index Index into the paragraph that indicates the hyperlink to | |
| 50 | * change. | |
| 51 | */ | |
| 52 | public LinkVisitor( final int index ) { | |
| 53 | mOffset = index; | |
| 54 | } | |
| 55 | ||
| 56 | public Link process( final Node root ) { | |
| 57 | getVisitor().visit( root ); | |
| 58 | return getLink(); | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * @param link Not null. | |
| 63 | */ | |
| 64 | private void visit( final Link link ) { | |
| 65 | final int began = link.getStartOffset(); | |
| 66 | final int ended = link.getEndOffset(); | |
| 67 | final int index = getOffset(); | |
| 68 | ||
| 69 | if( index >= began && index <= ended ) { | |
| 70 | setLink( link ); | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | private synchronized NodeVisitor getVisitor() { | |
| 75 | if( mVisitor == null ) { | |
| 76 | mVisitor = createVisitor(); | |
| 77 | } | |
| 78 | ||
| 79 | return mVisitor; | |
| 80 | } | |
| 81 | ||
| 82 | protected NodeVisitor createVisitor() { | |
| 83 | return new NodeVisitor( | |
| 84 | new VisitHandler<>( Link.class, LinkVisitor.this::visit ) ); | |
| 85 | } | |
| 86 | ||
| 87 | private Link getLink() { | |
| 88 | return mLink; | |
| 89 | } | |
| 90 | ||
| 91 | private void setLink( final Link link ) { | |
| 92 | mLink = link; | |
| 93 | } | |
| 94 | ||
| 95 | public int getOffset() { | |
| 96 | return mOffset; | |
| 97 | } | |
| 98 | } | |
| 1 | 99 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.editors.markdown; | |
| 3 | ||
| 4 | import com.keenwrite.Caret; | |
| 5 | import com.keenwrite.constants.Constants; | |
| 6 | import com.keenwrite.editors.TextEditor; | |
| 7 | import com.keenwrite.io.MediaType; | |
| 8 | import com.keenwrite.preferences.LocaleProperty; | |
| 9 | import com.keenwrite.preferences.Workspace; | |
| 10 | import com.keenwrite.spelling.impl.TextEditorSpeller; | |
| 11 | import javafx.beans.binding.Bindings; | |
| 12 | import javafx.beans.property.*; | |
| 13 | import javafx.beans.value.ChangeListener; | |
| 14 | import javafx.event.Event; | |
| 15 | import javafx.scene.Node; | |
| 16 | import javafx.scene.control.IndexRange; | |
| 17 | import javafx.scene.input.KeyEvent; | |
| 18 | import javafx.scene.layout.BorderPane; | |
| 19 | import org.fxmisc.flowless.VirtualizedScrollPane; | |
| 20 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 21 | import org.fxmisc.richtext.model.StyleSpans; | |
| 22 | import org.fxmisc.undo.UndoManager; | |
| 23 | import org.fxmisc.wellbehaved.event.EventPattern; | |
| 24 | import org.fxmisc.wellbehaved.event.Nodes; | |
| 25 | ||
| 26 | import java.io.File; | |
| 27 | import java.nio.charset.Charset; | |
| 28 | import java.text.BreakIterator; | |
| 29 | import java.util.*; | |
| 30 | import java.util.function.Consumer; | |
| 31 | import java.util.function.Supplier; | |
| 32 | import java.util.regex.Pattern; | |
| 33 | ||
| 34 | import static com.keenwrite.MainApp.keyDown; | |
| 35 | import static com.keenwrite.Messages.get; | |
| 36 | import static com.keenwrite.constants.Constants.*; | |
| 37 | import static com.keenwrite.events.StatusEvent.clue; | |
| 38 | import static com.keenwrite.events.TextEditorFocusEvent.fireTextEditorFocus; | |
| 39 | import static com.keenwrite.io.MediaType.TEXT_MARKDOWN; | |
| 40 | import static com.keenwrite.io.MediaType.TEXT_R_MARKDOWN; | |
| 41 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 42 | import static java.lang.Character.isWhitespace; | |
| 43 | import static java.lang.String.format; | |
| 44 | import static java.util.Collections.singletonList; | |
| 45 | import static javafx.application.Platform.runLater; | |
| 46 | import static javafx.scene.control.ScrollPane.ScrollBarPolicy.ALWAYS; | |
| 47 | import static javafx.scene.input.KeyCode.*; | |
| 48 | import static javafx.scene.input.KeyCombination.CONTROL_DOWN; | |
| 49 | import static javafx.scene.input.KeyCombination.SHIFT_DOWN; | |
| 50 | import static org.apache.commons.lang3.StringUtils.stripEnd; | |
| 51 | import static org.apache.commons.lang3.StringUtils.stripStart; | |
| 52 | import static org.fxmisc.richtext.model.StyleSpans.singleton; | |
| 53 | import static org.fxmisc.wellbehaved.event.EventPattern.keyPressed; | |
| 54 | import static org.fxmisc.wellbehaved.event.InputMap.consume; | |
| 55 | ||
| 56 | /** | |
| 57 | * Responsible for editing Markdown documents. | |
| 58 | */ | |
| 59 | public final class MarkdownEditor extends BorderPane implements TextEditor { | |
| 60 | /** | |
| 61 | * Regular expression that matches the type of markup block. This is used | |
| 62 | * when Enter is pressed to continue the block environment. | |
| 63 | */ | |
| 64 | private static final Pattern PATTERN_AUTO_INDENT = Pattern.compile( | |
| 65 | "(\\s*[*+-]\\s+|\\s*[0-9]+\\.\\s+|\\s+)(.*)" ); | |
| 66 | ||
| 67 | /** | |
| 68 | * The text editor. | |
| 69 | */ | |
| 70 | private final StyleClassedTextArea mTextArea = | |
| 71 | new StyleClassedTextArea( false ); | |
| 72 | ||
| 73 | /** | |
| 74 | * Wraps the text editor in scrollbars. | |
| 75 | */ | |
| 76 | private final VirtualizedScrollPane<StyleClassedTextArea> mScrollPane = | |
| 77 | new VirtualizedScrollPane<>( mTextArea ); | |
| 78 | ||
| 79 | private final Workspace mWorkspace; | |
| 80 | ||
| 81 | /** | |
| 82 | * Tracks where the caret is located in this document. This offers observable | |
| 83 | * properties for caret position changes. | |
| 84 | */ | |
| 85 | private final Caret mCaret = createCaret( mTextArea ); | |
| 86 | ||
| 87 | /** | |
| 88 | * File being edited by this editor instance. | |
| 89 | */ | |
| 90 | private File mFile; | |
| 91 | ||
| 92 | /** | |
| 93 | * Set to {@code true} upon text or caret position changes. Value is {@code | |
| 94 | * false} by default. | |
| 95 | */ | |
| 96 | private final BooleanProperty mDirty = new SimpleBooleanProperty(); | |
| 97 | ||
| 98 | /** | |
| 99 | * Opened file's character encoding, or {@link Constants#DEFAULT_CHARSET} if | |
| 100 | * either no encoding could be determined or this is a new (empty) file. | |
| 101 | */ | |
| 102 | private final Charset mEncoding; | |
| 103 | ||
| 104 | /** | |
| 105 | * Tracks whether the in-memory definitions have changed with respect to the | |
| 106 | * persisted definitions. | |
| 107 | */ | |
| 108 | private final BooleanProperty mModified = new SimpleBooleanProperty(); | |
| 109 | ||
| 110 | public MarkdownEditor( final Workspace workspace ) { | |
| 111 | this( DOCUMENT_DEFAULT, workspace ); | |
| 112 | } | |
| 113 | ||
| 114 | public MarkdownEditor( final File file, final Workspace workspace ) { | |
| 115 | mEncoding = open( mFile = file ); | |
| 116 | mWorkspace = workspace; | |
| 117 | ||
| 118 | initTextArea( mTextArea ); | |
| 119 | initStyle( mTextArea ); | |
| 120 | initScrollPane( mScrollPane ); | |
| 121 | initSpellchecker( mTextArea ); | |
| 122 | initHotKeys(); | |
| 123 | initUndoManager(); | |
| 124 | } | |
| 125 | ||
| 126 | private void initTextArea( final StyleClassedTextArea textArea ) { | |
| 127 | textArea.setWrapText( true ); | |
| 128 | textArea.requestFollowCaret(); | |
| 129 | textArea.moveTo( 0 ); | |
| 130 | ||
| 131 | textArea.textProperty().addListener( ( c, o, n ) -> { | |
| 132 | // Fire, regardless of whether the caret position has changed. | |
| 133 | mDirty.set( false ); | |
| 134 | ||
| 135 | // Prevent a caret position change from raising the dirty bits. | |
| 136 | mDirty.set( true ); | |
| 137 | } ); | |
| 138 | ||
| 139 | textArea.caretPositionProperty().addListener( ( c, o, n ) -> { | |
| 140 | // Fire when the caret position has changed and the text has not. | |
| 141 | mDirty.set( true ); | |
| 142 | mDirty.set( false ); | |
| 143 | } ); | |
| 144 | ||
| 145 | textArea.focusedProperty().addListener( ( c, o, n ) -> { | |
| 146 | if( n != null && n ) { | |
| 147 | fireTextEditorFocus( this ); | |
| 148 | } | |
| 149 | } ); | |
| 150 | } | |
| 151 | ||
| 152 | private void initStyle( final StyleClassedTextArea textArea ) { | |
| 153 | textArea.getStyleClass().add( "markdown" ); | |
| 154 | ||
| 155 | final var stylesheets = textArea.getStylesheets(); | |
| 156 | stylesheets.add( getStylesheetPath( getLocale() ) ); | |
| 157 | ||
| 158 | localeProperty().addListener( ( c, o, n ) -> { | |
| 159 | if( n != null ) { | |
| 160 | stylesheets.clear(); | |
| 161 | stylesheets.add( getStylesheetPath( getLocale() ) ); | |
| 162 | } | |
| 163 | } ); | |
| 164 | ||
| 165 | fontNameProperty().addListener( | |
| 166 | ( c, o, n ) -> | |
| 167 | setFont( mTextArea, getFontName(), getFontSize() ) | |
| 168 | ); | |
| 169 | ||
| 170 | fontSizeProperty().addListener( | |
| 171 | ( c, o, n ) -> | |
| 172 | setFont( mTextArea, getFontName(), getFontSize() ) | |
| 173 | ); | |
| 174 | ||
| 175 | setFont( mTextArea, getFontName(), getFontSize() ); | |
| 176 | } | |
| 177 | ||
| 178 | private void initScrollPane( | |
| 179 | final VirtualizedScrollPane<StyleClassedTextArea> scrollpane ) { | |
| 180 | scrollpane.setVbarPolicy( ALWAYS ); | |
| 181 | setCenter( scrollpane ); | |
| 182 | } | |
| 183 | ||
| 184 | private void initSpellchecker( final StyleClassedTextArea textarea ) { | |
| 185 | final var speller = new TextEditorSpeller(); | |
| 186 | speller.checkDocument( textarea ); | |
| 187 | speller.checkParagraphs( textarea ); | |
| 188 | } | |
| 189 | ||
| 190 | private void initHotKeys() { | |
| 191 | addEventListener( keyPressed( ENTER ), this::onEnterPressed ); | |
| 192 | addEventListener( keyPressed( X, CONTROL_DOWN ), this::cut ); | |
| 193 | addEventListener( keyPressed( TAB ), this::tab ); | |
| 194 | addEventListener( keyPressed( TAB, SHIFT_DOWN ), this::untab ); | |
| 195 | addEventListener( keyPressed( INSERT ), this::onInsertPressed ); | |
| 196 | } | |
| 197 | ||
| 198 | private void initUndoManager() { | |
| 199 | final var undoManager = getUndoManager(); | |
| 200 | final var markedPosition = undoManager.atMarkedPositionProperty(); | |
| 201 | ||
| 202 | undoManager.forgetHistory(); | |
| 203 | undoManager.mark(); | |
| 204 | mModified.bind( Bindings.not( markedPosition ) ); | |
| 205 | } | |
| 206 | ||
| 207 | @Override | |
| 208 | public void moveTo( final int offset ) { | |
| 209 | assert 0 <= offset && offset <= mTextArea.getLength(); | |
| 210 | mTextArea.moveTo( offset ); | |
| 211 | mTextArea.requestFollowCaret(); | |
| 212 | } | |
| 213 | ||
| 214 | /** | |
| 215 | * Delegate the focus request to the text area itself. | |
| 216 | */ | |
| 217 | @Override | |
| 218 | public void requestFocus() { | |
| 219 | mTextArea.requestFocus(); | |
| 220 | } | |
| 221 | ||
| 222 | @Override | |
| 223 | public void setText( final String text ) { | |
| 224 | mTextArea.clear(); | |
| 225 | mTextArea.appendText( text ); | |
| 226 | mTextArea.getUndoManager().mark(); | |
| 227 | } | |
| 228 | ||
| 229 | @Override | |
| 230 | public String getText() { | |
| 231 | return mTextArea.getText(); | |
| 232 | } | |
| 233 | ||
| 234 | @Override | |
| 235 | public Charset getEncoding() { | |
| 236 | return mEncoding; | |
| 237 | } | |
| 238 | ||
| 239 | @Override | |
| 240 | public File getFile() { | |
| 241 | return mFile; | |
| 242 | } | |
| 243 | ||
| 244 | @Override | |
| 245 | public void rename( final File file ) { | |
| 246 | mFile = file; | |
| 247 | } | |
| 248 | ||
| 249 | @Override | |
| 250 | public void undo() { | |
| 251 | final var manager = getUndoManager(); | |
| 252 | xxdo( manager::isUndoAvailable, manager::undo, "Main.status.error.undo" ); | |
| 253 | } | |
| 254 | ||
| 255 | @Override | |
| 256 | public void redo() { | |
| 257 | final var manager = getUndoManager(); | |
| 258 | xxdo( manager::isRedoAvailable, manager::redo, "Main.status.error.redo" ); | |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Performs an undo or redo action, if possible, otherwise displays an error | |
| 263 | * message to the user. | |
| 264 | * | |
| 265 | * @param ready Answers whether the action can be executed. | |
| 266 | * @param action The action to execute. | |
| 267 | * @param key The informational message key having a value to display if | |
| 268 | * the {@link Supplier} is not ready. | |
| 269 | */ | |
| 270 | private void xxdo( | |
| 271 | final Supplier<Boolean> ready, final Runnable action, final String key ) { | |
| 272 | if( ready.get() ) { | |
| 273 | action.run(); | |
| 274 | } | |
| 275 | else { | |
| 276 | clue( key ); | |
| 277 | } | |
| 278 | } | |
| 279 | ||
| 280 | @Override | |
| 281 | public void cut() { | |
| 282 | final var selected = mTextArea.getSelectedText(); | |
| 283 | ||
| 284 | // Emulate selecting the current line by firing Home then Shift+Down Arrow. | |
| 285 | if( selected == null || selected.isEmpty() ) { | |
| 286 | // Note: mTextArea.selectLine() does not select empty lines. | |
| 287 | mTextArea.fireEvent( keyDown( HOME, false ) ); | |
| 288 | mTextArea.fireEvent( keyDown( DOWN, true ) ); | |
| 289 | } | |
| 290 | ||
| 291 | mTextArea.cut(); | |
| 292 | } | |
| 293 | ||
| 294 | @Override | |
| 295 | public void copy() { | |
| 296 | mTextArea.copy(); | |
| 297 | } | |
| 298 | ||
| 299 | @Override | |
| 300 | public void paste() { | |
| 301 | mTextArea.paste(); | |
| 302 | } | |
| 303 | ||
| 304 | @Override | |
| 305 | public void selectAll() { | |
| 306 | mTextArea.selectAll(); | |
| 307 | } | |
| 308 | ||
| 309 | @Override | |
| 310 | public void bold() { | |
| 311 | enwrap( "**" ); | |
| 312 | } | |
| 313 | ||
| 314 | @Override | |
| 315 | public void italic() { | |
| 316 | enwrap( "*" ); | |
| 317 | } | |
| 318 | ||
| 319 | @Override | |
| 320 | public void monospace() { | |
| 321 | enwrap( "`" ); | |
| 322 | } | |
| 323 | ||
| 324 | @Override | |
| 325 | public void superscript() { | |
| 326 | enwrap( "^" ); | |
| 327 | } | |
| 328 | ||
| 329 | @Override | |
| 330 | public void subscript() { | |
| 331 | enwrap( "~" ); | |
| 332 | } | |
| 333 | ||
| 334 | @Override | |
| 335 | public void strikethrough() { | |
| 336 | enwrap( "~~" ); | |
| 337 | } | |
| 338 | ||
| 339 | @Override | |
| 340 | public void blockquote() { | |
| 341 | block( "> " ); | |
| 342 | } | |
| 343 | ||
| 344 | @Override | |
| 345 | public void code() { | |
| 346 | enwrap( "`" ); | |
| 347 | } | |
| 348 | ||
| 349 | @Override | |
| 350 | public void fencedCodeBlock() { | |
| 351 | enwrap( "\n\n```\n", "\n```\n\n" ); | |
| 352 | } | |
| 353 | ||
| 354 | @Override | |
| 355 | public void heading( final int level ) { | |
| 356 | final var hashes = new String( new char[ level ] ).replace( "\0", "#" ); | |
| 357 | block( format( "%s ", hashes ) ); | |
| 358 | } | |
| 359 | ||
| 360 | @Override | |
| 361 | public void unorderedList() { | |
| 362 | block( "* " ); | |
| 363 | } | |
| 364 | ||
| 365 | @Override | |
| 366 | public void orderedList() { | |
| 367 | block( "1. " ); | |
| 368 | } | |
| 369 | ||
| 370 | @Override | |
| 371 | public void horizontalRule() { | |
| 372 | block( format( "---%n%n" ) ); | |
| 373 | } | |
| 374 | ||
| 375 | @Override | |
| 376 | public Node getNode() { | |
| 377 | return this; | |
| 378 | } | |
| 379 | ||
| 380 | @Override | |
| 381 | public ReadOnlyBooleanProperty modifiedProperty() { | |
| 382 | return mModified; | |
| 383 | } | |
| 384 | ||
| 385 | @Override | |
| 386 | public void clearModifiedProperty() { | |
| 387 | getUndoManager().mark(); | |
| 388 | } | |
| 389 | ||
| 390 | @Override | |
| 391 | public VirtualizedScrollPane<StyleClassedTextArea> getScrollPane() { | |
| 392 | return mScrollPane; | |
| 393 | } | |
| 394 | ||
| 395 | @Override | |
| 396 | public StyleClassedTextArea getTextArea() { | |
| 397 | return mTextArea; | |
| 398 | } | |
| 399 | ||
| 400 | private final Map<String, IndexRange> mStyles = new HashMap<>(); | |
| 401 | ||
| 402 | @Override | |
| 403 | public void stylize( final IndexRange range, final String style ) { | |
| 404 | final var began = range.getStart(); | |
| 405 | final var ended = range.getEnd() + 1; | |
| 406 | ||
| 407 | assert 0 <= began && began <= ended; | |
| 408 | assert style != null; | |
| 409 | ||
| 410 | // TODO: Ensure spell check and find highlights can coexist. | |
| 411 | // final var spans = mTextArea.getStyleSpans( range ); | |
| 412 | // System.out.println( "SPANS: " + spans ); | |
| 413 | ||
| 414 | // final var spans = mTextArea.getStyleSpans( range ); | |
| 415 | // mTextArea.setStyleSpans( began, merge( spans, range.getLength(), style | |
| 416 | // ) ); | |
| 417 | ||
| 418 | // final var builder = new StyleSpansBuilder<Collection<String>>(); | |
| 419 | // builder.add( singleton( style ), range.getLength() + 1 ); | |
| 420 | // mTextArea.setStyleSpans( began, builder.create() ); | |
| 421 | ||
| 422 | // final var s = mTextArea.getStyleSpans( began, ended ); | |
| 423 | // System.out.println( "STYLES: " +s ); | |
| 424 | ||
| 425 | mStyles.put( style, range ); | |
| 426 | mTextArea.setStyleClass( began, ended, style ); | |
| 427 | ||
| 428 | // Ensure that whenever the user interacts with the text that the found | |
| 429 | // word will have its highlighting removed. The handler removes itself. | |
| 430 | // This won't remove the highlighting if the caret position moves by mouse. | |
| 431 | final var handler = mTextArea.getOnKeyPressed(); | |
| 432 | mTextArea.setOnKeyPressed( ( event ) -> { | |
| 433 | mTextArea.setOnKeyPressed( handler ); | |
| 434 | unstylize( style ); | |
| 435 | } ); | |
| 436 | ||
| 437 | //mTextArea.setStyleSpans(began, ended, s); | |
| 438 | } | |
| 439 | ||
| 440 | private static StyleSpans<Collection<String>> merge( | |
| 441 | StyleSpans<Collection<String>> spans, int len, String style ) { | |
| 442 | spans = spans.overlay( | |
| 443 | singleton( singletonList( style ), len ), | |
| 444 | ( bottomSpan, list ) -> { | |
| 445 | final List<String> l = | |
| 446 | new ArrayList<>( bottomSpan.size() + list.size() ); | |
| 447 | l.addAll( bottomSpan ); | |
| 448 | l.addAll( list ); | |
| 449 | return l; | |
| 450 | } ); | |
| 451 | ||
| 452 | return spans; | |
| 453 | } | |
| 454 | ||
| 455 | @Override | |
| 456 | public void unstylize( final String style ) { | |
| 457 | final var indexes = mStyles.remove( style ); | |
| 458 | if( indexes != null ) { | |
| 459 | mTextArea.clearStyle( indexes.getStart(), indexes.getEnd() + 1 ); | |
| 460 | } | |
| 461 | } | |
| 462 | ||
| 463 | @Override | |
| 464 | public Caret getCaret() { | |
| 465 | return mCaret; | |
| 466 | } | |
| 467 | ||
| 468 | private Caret createCaret( final StyleClassedTextArea editor ) { | |
| 469 | return Caret | |
| 470 | .builder() | |
| 471 | .with( Caret.Mutator::setEditor, editor ) | |
| 472 | .build(); | |
| 473 | } | |
| 474 | ||
| 475 | /** | |
| 476 | * This method adds listeners to editor events. | |
| 477 | * | |
| 478 | * @param <T> The event type. | |
| 479 | * @param <U> The consumer type for the given event type. | |
| 480 | * @param event The event of interest. | |
| 481 | * @param consumer The method to call when the event happens. | |
| 482 | */ | |
| 483 | public <T extends Event, U extends T> void addEventListener( | |
| 484 | final EventPattern<? super T, ? extends U> event, | |
| 485 | final Consumer<? super U> consumer ) { | |
| 486 | Nodes.addInputMap( mTextArea, consume( event, consumer ) ); | |
| 487 | } | |
| 488 | ||
| 489 | private void onEnterPressed( final KeyEvent ignored ) { | |
| 490 | final var currentLine = getCaretParagraph(); | |
| 491 | final var matcher = PATTERN_AUTO_INDENT.matcher( currentLine ); | |
| 492 | ||
| 493 | // By default, insert a new line by itself. | |
| 494 | String newText = NEWLINE; | |
| 495 | ||
| 496 | // If the pattern was matched then determine what block type to continue. | |
| 497 | if( matcher.matches() ) { | |
| 498 | if( matcher.group( 2 ).isEmpty() ) { | |
| 499 | final var pos = mTextArea.getCaretPosition(); | |
| 500 | mTextArea.selectRange( pos - currentLine.length(), pos ); | |
| 501 | } | |
| 502 | else { | |
| 503 | // Indent the new line with the same whitespace characters and | |
| 504 | // list markers as current line. This ensures that the indentation | |
| 505 | // is propagated. | |
| 506 | newText = newText.concat( matcher.group( 1 ) ); | |
| 507 | } | |
| 508 | } | |
| 509 | ||
| 510 | mTextArea.replaceSelection( newText ); | |
| 511 | } | |
| 512 | ||
| 513 | /** | |
| 514 | * TODO: 105 - Insert key toggle overwrite (typeover) mode | |
| 515 | * | |
| 516 | * @param ignored Unused. | |
| 517 | */ | |
| 518 | private void onInsertPressed( final KeyEvent ignored ) { | |
| 519 | } | |
| 520 | ||
| 521 | private void cut( final KeyEvent event ) { | |
| 522 | cut(); | |
| 523 | } | |
| 524 | ||
| 525 | private void tab( final KeyEvent event ) { | |
| 526 | final var range = mTextArea.selectionProperty().getValue(); | |
| 527 | final var sb = new StringBuilder( 1024 ); | |
| 528 | ||
| 529 | if( range.getLength() > 0 ) { | |
| 530 | final var selection = mTextArea.getSelectedText(); | |
| 531 | ||
| 532 | selection.lines().forEach( | |
| 533 | ( l ) -> sb.append( "\t" ).append( l ).append( NEWLINE ) | |
| 534 | ); | |
| 535 | } | |
| 536 | else { | |
| 537 | sb.append( "\t" ); | |
| 538 | } | |
| 539 | ||
| 540 | mTextArea.replaceSelection( sb.toString() ); | |
| 541 | } | |
| 542 | ||
| 543 | private void untab( final KeyEvent event ) { | |
| 544 | final var range = mTextArea.selectionProperty().getValue(); | |
| 545 | ||
| 546 | if( range.getLength() > 0 ) { | |
| 547 | final var selection = mTextArea.getSelectedText(); | |
| 548 | final var sb = new StringBuilder( selection.length() ); | |
| 549 | ||
| 550 | selection.lines().forEach( | |
| 551 | ( l ) -> sb.append( l.startsWith( "\t" ) ? l.substring( 1 ) : l ) | |
| 552 | .append( NEWLINE ) | |
| 553 | ); | |
| 554 | ||
| 555 | mTextArea.replaceSelection( sb.toString() ); | |
| 556 | } | |
| 557 | else { | |
| 558 | final var p = getCaretParagraph(); | |
| 559 | ||
| 560 | if( p.startsWith( "\t" ) ) { | |
| 561 | mTextArea.selectParagraph(); | |
| 562 | mTextArea.replaceSelection( p.substring( 1 ) ); | |
| 563 | } | |
| 564 | } | |
| 565 | } | |
| 566 | ||
| 567 | /** | |
| 568 | * Observers may listen for changes to the property returned from this method | |
| 569 | * to receive notifications when either the text or caret have changed. This | |
| 570 | * should not be used to track whether the text has been modified. | |
| 571 | */ | |
| 572 | public void addDirtyListener( ChangeListener<Boolean> listener ) { | |
| 573 | mDirty.addListener( listener ); | |
| 574 | } | |
| 575 | ||
| 576 | /** | |
| 577 | * Surrounds the selected text or word under the caret in Markdown markup. | |
| 578 | * | |
| 579 | * @param token The beginning and ending token for enclosing the text. | |
| 580 | */ | |
| 581 | private void enwrap( final String token ) { | |
| 582 | enwrap( token, token ); | |
| 583 | } | |
| 584 | ||
| 585 | /** | |
| 586 | * Surrounds the selected text or word under the caret in Markdown markup. | |
| 587 | * | |
| 588 | * @param began The beginning token for enclosing the text. | |
| 589 | * @param ended The ending token for enclosing the text. | |
| 590 | */ | |
| 591 | private void enwrap( final String began, String ended ) { | |
| 592 | // Ensure selected text takes precedence over the word at caret position. | |
| 593 | final var selected = mTextArea.selectionProperty().getValue(); | |
| 594 | final var range = selected.getLength() == 0 | |
| 595 | ? getCaretWord() | |
| 596 | : selected; | |
| 597 | String text = mTextArea.getText( range ); | |
| 598 | ||
| 599 | int length = range.getLength(); | |
| 600 | text = stripStart( text, null ); | |
| 601 | final int beganIndex = range.getStart() + (length - text.length()); | |
| 602 | ||
| 603 | length = text.length(); | |
| 604 | text = stripEnd( text, null ); | |
| 605 | final int endedIndex = range.getEnd() - (length - text.length()); | |
| 606 | ||
| 607 | mTextArea.replaceText( beganIndex, endedIndex, began + text + ended ); | |
| 608 | } | |
| 609 | ||
| 610 | /** | |
| 611 | * Inserts the given block-level markup at the current caret position | |
| 612 | * within the document. This will prepend two blank lines to ensure that | |
| 613 | * the block element begins at the start of a new line. | |
| 614 | * | |
| 615 | * @param markup The text to insert at the caret. | |
| 616 | */ | |
| 617 | private void block( final String markup ) { | |
| 618 | final int pos = mTextArea.getCaretPosition(); | |
| 619 | mTextArea.insertText( pos, format( "%n%n%s", markup ) ); | |
| 620 | } | |
| 621 | ||
| 622 | /** | |
| 623 | * Returns the caret position within the current paragraph. | |
| 624 | * | |
| 625 | * @return A value from 0 to the length of the current paragraph. | |
| 626 | */ | |
| 627 | private int getCaretColumn() { | |
| 628 | return mTextArea.getCaretColumn(); | |
| 629 | } | |
| 630 | ||
| 631 | @Override | |
| 632 | public IndexRange getCaretWord() { | |
| 633 | final var paragraph = getCaretParagraph(); | |
| 634 | final var length = paragraph.length(); | |
| 635 | final var column = getCaretColumn(); | |
| 636 | ||
| 637 | var began = column; | |
| 638 | var ended = column; | |
| 639 | ||
| 640 | while( began > 0 && !isWhitespace( paragraph.charAt( began - 1 ) ) ) { | |
| 641 | began--; | |
| 642 | } | |
| 643 | ||
| 644 | while( ended < length && !isWhitespace( paragraph.charAt( ended ) ) ) { | |
| 645 | ended++; | |
| 646 | } | |
| 647 | ||
| 648 | final var iterator = BreakIterator.getWordInstance(); | |
| 649 | iterator.setText( paragraph ); | |
| 650 | ||
| 651 | while( began < length && iterator.isBoundary( began + 1 ) ) { | |
| 652 | began++; | |
| 653 | } | |
| 654 | ||
| 655 | while( ended > 0 && iterator.isBoundary( ended - 1 ) ) { | |
| 656 | ended--; | |
| 657 | } | |
| 658 | ||
| 659 | final var offset = getCaretDocumentOffset( column ); | |
| 660 | ||
| 661 | return IndexRange.normalize( began + offset, ended + offset ); | |
| 662 | } | |
| 663 | ||
| 664 | private int getCaretDocumentOffset( final int column ) { | |
| 665 | return mTextArea.getCaretPosition() - column; | |
| 666 | } | |
| 667 | ||
| 668 | /** | |
| 669 | * Returns the index of the paragraph where the caret resides. | |
| 670 | * | |
| 671 | * @return A number greater than or equal to 0. | |
| 672 | */ | |
| 673 | private int getCurrentParagraph() { | |
| 674 | return mTextArea.getCurrentParagraph(); | |
| 675 | } | |
| 676 | ||
| 677 | /** | |
| 678 | * Returns the text for the paragraph that contains the caret. | |
| 679 | * | |
| 680 | * @return A non-null string, possibly empty. | |
| 681 | */ | |
| 682 | private String getCaretParagraph() { | |
| 683 | return getText( getCurrentParagraph() ); | |
| 684 | } | |
| 685 | ||
| 686 | @Override | |
| 687 | public String getText( final int paragraph ) { | |
| 688 | return mTextArea.getText( paragraph ); | |
| 689 | } | |
| 690 | ||
| 691 | @Override | |
| 692 | public String getText( final IndexRange indexes ) | |
| 693 | throws IndexOutOfBoundsException { | |
| 694 | return mTextArea.getText( indexes.getStart(), indexes.getEnd() ); | |
| 695 | } | |
| 696 | ||
| 697 | @Override | |
| 698 | public void replaceText( final IndexRange indexes, final String s ) { | |
| 699 | mTextArea.replaceText( indexes, s ); | |
| 700 | } | |
| 701 | ||
| 702 | private UndoManager<?> getUndoManager() { | |
| 703 | return mTextArea.getUndoManager(); | |
| 704 | } | |
| 705 | ||
| 706 | /** | |
| 707 | * Returns the path to a {@link Locale}-specific stylesheet. | |
| 708 | * | |
| 709 | * @return A non-null string to inject into the HTML document head. | |
| 710 | */ | |
| 711 | private static String getStylesheetPath( final Locale locale ) { | |
| 712 | return get( | |
| 713 | sSettings.getSetting( STYLESHEET_MARKDOWN_LOCALE, "" ), | |
| 714 | locale.getLanguage(), | |
| 715 | locale.getScript(), | |
| 716 | locale.getCountry() | |
| 717 | ); | |
| 718 | } | |
| 719 | ||
| 720 | private Locale getLocale() { | |
| 721 | return localeProperty().toLocale(); | |
| 722 | } | |
| 723 | ||
| 724 | private LocaleProperty localeProperty() { | |
| 725 | return mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE ); | |
| 726 | } | |
| 727 | ||
| 728 | /** | |
| 729 | * Sets the font family name and font size at the same time. When the | |
| 730 | * workspace is loaded, the default font values are changed, which results | |
| 731 | * in this method being called. | |
| 732 | * | |
| 733 | * @param area Change the font settings for this text area. | |
| 734 | * @param name New font family name to apply. | |
| 735 | * @param points New font size to apply (in points, not pixels). | |
| 736 | */ | |
| 737 | private void setFont( | |
| 738 | final StyleClassedTextArea area, final String name, final double points ) { | |
| 739 | runLater( () -> area.setStyle( | |
| 740 | format( | |
| 741 | "-fx-font-family:'%s';-fx-font-size:%spx;", name, toPixels( points ) | |
| 742 | ) | |
| 743 | ) ); | |
| 744 | } | |
| 745 | ||
| 746 | private String getFontName() { | |
| 747 | return fontNameProperty().get(); | |
| 748 | } | |
| 749 | ||
| 750 | private StringProperty fontNameProperty() { | |
| 751 | return mWorkspace.stringProperty( KEY_UI_FONT_EDITOR_NAME ); | |
| 752 | } | |
| 753 | ||
| 754 | private double getFontSize() { | |
| 755 | return fontSizeProperty().get(); | |
| 756 | } | |
| 757 | ||
| 758 | private DoubleProperty fontSizeProperty() { | |
| 759 | return mWorkspace.doubleProperty( KEY_UI_FONT_EDITOR_SIZE ); | |
| 760 | } | |
| 761 | ||
| 762 | /** | |
| 763 | * Answers whether the given resource is of compatible {@link MediaType}s. | |
| 764 | * | |
| 765 | * @param mediaType The {@link MediaType} to compare. | |
| 766 | * @return {@code true} if the given {@link MediaType} is suitable for | |
| 767 | * editing with this type of editor. | |
| 768 | */ | |
| 769 | @Override | |
| 770 | public boolean supports( final MediaType mediaType ) { | |
| 771 | return isMediaType( mediaType ) || | |
| 772 | mediaType == TEXT_MARKDOWN || | |
| 773 | mediaType == TEXT_R_MARKDOWN; | |
| 774 | } | |
| 775 | } | |
| 1 | 776 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import static com.keenwrite.events.Bus.post; | |
| 5 | ||
| 6 | /** | |
| 7 | * Marker interface for all application events. | |
| 8 | */ | |
| 9 | public interface AppEvent { | |
| 10 | ||
| 11 | /** | |
| 12 | * Submits this event to the {@link Bus}. | |
| 13 | */ | |
| 14 | default void fire() { | |
| 15 | post( this ); | |
| 16 | } | |
| 17 | } | |
| 1 | 18 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import org.greenrobot.eventbus.EventBus; | |
| 5 | ||
| 6 | /** | |
| 7 | * Responsible for delegating interactions to the event bus library. This | |
| 8 | * class decouples the rest of the application from a particular event bus | |
| 9 | * implementation. | |
| 10 | */ | |
| 11 | public class Bus { | |
| 12 | private static final EventBus sEventBus = EventBus | |
| 13 | .builder().logNoSubscriberMessages( false ).installDefaultEventBus(); | |
| 14 | ||
| 15 | public static <Subscriber> void register( final Subscriber subscriber ) { | |
| 16 | sEventBus.register( subscriber ); | |
| 17 | } | |
| 18 | ||
| 19 | public static <Subscriber> void unregister( final Subscriber subscriber ) { | |
| 20 | sEventBus.unregister( subscriber ); | |
| 21 | } | |
| 22 | ||
| 23 | public static <Event> void post( final Event event ) { | |
| 24 | sEventBus.post( event ); | |
| 25 | } | |
| 26 | } | |
| 1 | 27 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import com.keenwrite.ui.outline.DocumentOutline; | |
| 5 | ||
| 6 | /** | |
| 7 | * Collates information about a caret event, which is typically triggered when | |
| 8 | * the user double-clicks in the {@link DocumentOutline}. | |
| 9 | */ | |
| 10 | public class CaretNavigationEvent implements AppEvent { | |
| 11 | /** | |
| 12 | * Absolute document offset. | |
| 13 | */ | |
| 14 | private final int mOffset; | |
| 15 | ||
| 16 | private CaretNavigationEvent( final int offset ) { | |
| 17 | mOffset = offset; | |
| 18 | } | |
| 19 | ||
| 20 | /** | |
| 21 | * Publishes an event that requests moving the caret to the given offset. | |
| 22 | * | |
| 23 | * @param offset Move the caret to this document offset. | |
| 24 | */ | |
| 25 | public static void fireCaretNavigationEvent( final int offset ) { | |
| 26 | new CaretNavigationEvent( offset ).fire(); | |
| 27 | } | |
| 28 | ||
| 29 | public int getOffset() { | |
| 30 | return mOffset; | |
| 31 | } | |
| 32 | } | |
| 1 | 33 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import org.jsoup.nodes.Document; | |
| 5 | ||
| 6 | /** | |
| 7 | * Collates information about an HTML document that has changed. | |
| 8 | */ | |
| 9 | public class DocumentChangedEvent implements AppEvent { | |
| 10 | private final String mText; | |
| 11 | ||
| 12 | /** | |
| 13 | * Hash document (as plain text) so subscribers are notified upon changes. | |
| 14 | */ | |
| 15 | private static int sHash; | |
| 16 | ||
| 17 | /** | |
| 18 | * Creates an event with the new plain text document, having all variables | |
| 19 | * substituted and all markup removed. | |
| 20 | * | |
| 21 | * @param text The document text that has changed since the last time this | |
| 22 | * type of event was fired. | |
| 23 | */ | |
| 24 | private DocumentChangedEvent( final String text ) { | |
| 25 | mText = text; | |
| 26 | } | |
| 27 | ||
| 28 | /** | |
| 29 | * When the given document may have changed. This will only fire a change | |
| 30 | * event if the given document has changed from the last time this | |
| 31 | * event was fired. The document is first converted to plain text before | |
| 32 | * the comparison is made. | |
| 33 | * | |
| 34 | * @param html The document that may have changed. | |
| 35 | */ | |
| 36 | public static void fireDocumentChangedEvent( final Document html ) { | |
| 37 | // Hashing the document text ignores caret position changes. | |
| 38 | final var text = html.wholeText(); | |
| 39 | final var hash = text.hashCode(); | |
| 40 | ||
| 41 | if( hash != sHash ) { | |
| 42 | sHash = hash; | |
| 43 | new DocumentChangedEvent( text ).fire(); | |
| 44 | } | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Returns the text that has changed. | |
| 49 | * | |
| 50 | * @return The new document text. | |
| 51 | */ | |
| 52 | public String getDocument() { | |
| 53 | return mText; | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Returns the document. | |
| 58 | * | |
| 59 | * @return The value from {@link #getDocument()}. | |
| 60 | */ | |
| 61 | @Override | |
| 62 | public String toString() { | |
| 63 | return getDocument(); | |
| 64 | } | |
| 65 | } | |
| 1 | 66 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | /** | |
| 5 | * Responsible for kicking off an alert message when exporting (e.g., to PDF) | |
| 6 | * fails. This can happen when the executable to typeset the document cannot | |
| 7 | * be found. | |
| 8 | */ | |
| 9 | public class ExportFailedEvent implements AppEvent { | |
| 10 | public static void fireExportFailedEvent() { | |
| 11 | new ExportFailedEvent().fire(); | |
| 12 | } | |
| 13 | } | |
| 1 | 14 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import com.keenwrite.preview.HtmlPanel; | |
| 5 | ||
| 6 | import java.net.URI; | |
| 7 | ||
| 8 | /** | |
| 9 | * Collates information about a file requested to be opened. This can be called | |
| 10 | * when the user clicks a hyperlink in the {@link HtmlPanel}. | |
| 11 | */ | |
| 12 | public class FileOpenEvent implements AppEvent { | |
| 13 | private final URI mUri; | |
| 14 | ||
| 15 | private FileOpenEvent( final URI uri ) { | |
| 16 | assert uri != null; | |
| 17 | mUri = uri; | |
| 18 | } | |
| 19 | ||
| 20 | /** | |
| 21 | * Fires a new file open event using the given {@link URI} instance. | |
| 22 | * | |
| 23 | * @param uri The instance of {@link URI} to open as a file in a text editor. | |
| 24 | */ | |
| 25 | public static void fireFileOpenEvent( final URI uri ) { | |
| 26 | new FileOpenEvent( uri ).fire(); | |
| 27 | } | |
| 28 | ||
| 29 | /** | |
| 30 | * Returns the requested file name to be opened. | |
| 31 | * | |
| 32 | * @return A file reference that can be opened in a text editor. | |
| 33 | */ | |
| 34 | public URI getUri() { | |
| 35 | return mUri; | |
| 36 | } | |
| 37 | } | |
| 1 | 38 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | /** | |
| 5 | * Collates information about an object that has gained focus. This is typically | |
| 6 | * used by text resource editors (such as text editors and definition editors). | |
| 7 | */ | |
| 8 | public class FocusEvent<T> implements AppEvent { | |
| 9 | private final T mNode; | |
| 10 | ||
| 11 | protected FocusEvent( final T node ) { | |
| 12 | mNode = node; | |
| 13 | } | |
| 14 | ||
| 15 | /** | |
| 16 | * This method is used to help update the UI whenever a component has gained | |
| 17 | * input focus. | |
| 18 | * | |
| 19 | * @return The object that has gained focus. | |
| 20 | */ | |
| 21 | public T get() { | |
| 22 | return mNode; | |
| 23 | } | |
| 24 | } | |
| 1 | 25 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import java.io.IOException; | |
| 5 | import java.net.URI; | |
| 6 | ||
| 7 | import static com.keenwrite.events.StatusEvent.clue; | |
| 8 | ||
| 9 | /** | |
| 10 | * Collates information about a URL requested to be opened. | |
| 11 | */ | |
| 12 | public class HyperlinkOpenEvent implements AppEvent { | |
| 13 | private final URI mUri; | |
| 14 | ||
| 15 | private HyperlinkOpenEvent( final URI uri ) { | |
| 16 | mUri = uri; | |
| 17 | } | |
| 18 | ||
| 19 | /** | |
| 20 | * Requests to open the default browser at the given location. | |
| 21 | * | |
| 22 | * @param uri The location to open. | |
| 23 | */ | |
| 24 | public static void fireHyperlinkOpenEvent( final URI uri ) | |
| 25 | throws IOException { | |
| 26 | new HyperlinkOpenEvent( uri ).fire(); | |
| 27 | } | |
| 28 | ||
| 29 | /** | |
| 30 | * Requests to open the default browser at the given location. | |
| 31 | * | |
| 32 | * @param uri The location to open. | |
| 33 | */ | |
| 34 | public static void fireHyperlinkOpenEvent( final String uri ) { | |
| 35 | try { | |
| 36 | fireHyperlinkOpenEvent( new URI( uri ) ); | |
| 37 | } catch( final Exception ex ) { | |
| 38 | clue( ex ); | |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | /** | |
| 43 | * Returns the requested resource to be opened. | |
| 44 | * | |
| 45 | * @return A reference that can be opened in a web browser. | |
| 46 | */ | |
| 47 | public URI getUri() { | |
| 48 | return mUri; | |
| 49 | } | |
| 50 | } | |
| 1 | 51 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import com.keenwrite.processors.Processor; | |
| 5 | ||
| 6 | /** | |
| 7 | * Collates information about a document heading that has been parsed, after | |
| 8 | * all pertinent {@link Processor}s applied. | |
| 9 | */ | |
| 10 | public class ParseHeadingEvent implements AppEvent { | |
| 11 | private static final int NEW_OUTLINE_LEVEL = 0; | |
| 12 | ||
| 13 | /** | |
| 14 | * The heading text, which may be {@code null} upon creating a new outline. | |
| 15 | */ | |
| 16 | private final String mText; | |
| 17 | ||
| 18 | /** | |
| 19 | * The heading level, which will be set to {@link #NEW_OUTLINE_LEVEL} if this | |
| 20 | * event indicates that the existing outline should be cleared anew. | |
| 21 | */ | |
| 22 | private final int mLevel; | |
| 23 | ||
| 24 | /** | |
| 25 | * Offset into the text where the heading is found. | |
| 26 | */ | |
| 27 | private final int mOffset; | |
| 28 | ||
| 29 | private ParseHeadingEvent( | |
| 30 | final int level, final String text, final int offset ) { | |
| 31 | mText = text; | |
| 32 | mLevel = level; | |
| 33 | mOffset = offset; | |
| 34 | } | |
| 35 | ||
| 36 | /** | |
| 37 | * Call to indicate a new outline is to be created. | |
| 38 | */ | |
| 39 | public static void fireNewOutlineEvent() { | |
| 40 | new ParseHeadingEvent( NEW_OUTLINE_LEVEL, "Document", 0 ).fire(); | |
| 41 | } | |
| 42 | ||
| 43 | /** | |
| 44 | * Call to indicate that a new heading must be added to the document outline. | |
| 45 | * | |
| 46 | * @param text The heading text (parsed and processed). | |
| 47 | * @param level A value between 1 and 6. | |
| 48 | * @param offset Absolute offset into document where heading is found. | |
| 49 | */ | |
| 50 | public static void fireNewHeadingEvent( | |
| 51 | final int level, final String text, final int offset ) { | |
| 52 | assert text != null; | |
| 53 | assert 1 <= level && level <= 6; | |
| 54 | assert 0 <= offset; | |
| 55 | new ParseHeadingEvent( level, text, offset ).fire(); | |
| 56 | } | |
| 57 | ||
| 58 | public boolean isNewOutline() { | |
| 59 | return getLevel() == NEW_OUTLINE_LEVEL; | |
| 60 | } | |
| 61 | ||
| 62 | public int getLevel() { | |
| 63 | return mLevel; | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Returns the text description for the heading. | |
| 68 | * | |
| 69 | * @return The post-parsed and processed heading text from the document. | |
| 70 | */ | |
| 71 | public String getText() { | |
| 72 | return mText; | |
| 73 | } | |
| 74 | ||
| 75 | /** | |
| 76 | * Returns an offset into the document where the heading is found. | |
| 77 | * | |
| 78 | * @return A zero-based document offset. | |
| 79 | */ | |
| 80 | public int getOffset() { | |
| 81 | return mOffset; | |
| 82 | } | |
| 83 | ||
| 84 | @Override | |
| 85 | public String toString() { | |
| 86 | return getText(); | |
| 87 | } | |
| 88 | } | |
| 1 | 89 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import static java.awt.Toolkit.getDefaultToolkit; | |
| 5 | import static java.awt.event.KeyEvent.VK_SCROLL_LOCK; | |
| 6 | ||
| 7 | /** | |
| 8 | * Collates information about the scroll lock status. | |
| 9 | */ | |
| 10 | public class ScrollLockEvent implements AppEvent { | |
| 11 | private final boolean mLocked; | |
| 12 | ||
| 13 | private ScrollLockEvent( final boolean locked ) { | |
| 14 | mLocked = locked; | |
| 15 | } | |
| 16 | ||
| 17 | /** | |
| 18 | * Fires a scroll lock event provided that the scroll lock key is in the | |
| 19 | * off state. | |
| 20 | * | |
| 21 | * @param locked The new locked status. | |
| 22 | */ | |
| 23 | public static void fireScrollLockEvent( final boolean locked ) { | |
| 24 | // If the scroll lock key is off, allow the status to change. | |
| 25 | if( !getScrollLockKeyStatus() ) { | |
| 26 | fire( locked ); | |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 30 | /** | |
| 31 | * Fires a scroll lock event based on the current status of the scroll | |
| 32 | * lock key. | |
| 33 | */ | |
| 34 | public static void fireScrollLockEvent() { | |
| 35 | fire( getScrollLockKeyStatus() ); | |
| 36 | } | |
| 37 | ||
| 38 | /** | |
| 39 | * Answers whether the synchronized scrolling should be locked in place | |
| 40 | * (i.e., prevent sync scrolling). | |
| 41 | * | |
| 42 | * @return {@code true} when the user has locked the scrollbar position. | |
| 43 | */ | |
| 44 | public boolean isLocked() { | |
| 45 | return mLocked; | |
| 46 | } | |
| 47 | ||
| 48 | private static void fire( final boolean locked ) { | |
| 49 | new ScrollLockEvent( locked ).fire(); | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Returns the state of the scroll lock key. | |
| 54 | * | |
| 55 | * @return {@code true} when the scroll lock key is in the on state. | |
| 56 | */ | |
| 57 | private static boolean getScrollLockKeyStatus() { | |
| 58 | return getDefaultToolkit().getLockingKeyState( VK_SCROLL_LOCK ); | |
| 59 | } | |
| 60 | } | |
| 1 | 61 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import com.keenwrite.MainApp; | |
| 5 | ||
| 6 | import java.util.List; | |
| 7 | import java.util.stream.Collectors; | |
| 8 | ||
| 9 | import static com.keenwrite.Messages.get; | |
| 10 | import static com.keenwrite.constants.Constants.NEWLINE; | |
| 11 | import static com.keenwrite.constants.Constants.STATUS_BAR_OK; | |
| 12 | import static java.lang.String.format; | |
| 13 | import static java.lang.String.join; | |
| 14 | import static java.util.Arrays.stream; | |
| 15 | ||
| 16 | /** | |
| 17 | * Collates information about an application issue. The issues can be | |
| 18 | * exceptions, state problems, parsing errors, and so forth. | |
| 19 | */ | |
| 20 | public final class StatusEvent implements AppEvent { | |
| 21 | private static final String PACKAGE_NAME = MainApp.class.getPackageName(); | |
| 22 | ||
| 23 | private static final String ENGLISHIFY = | |
| 24 | "(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])"; | |
| 25 | ||
| 26 | /** | |
| 27 | * Detailed information about a problem. | |
| 28 | */ | |
| 29 | private final String mMessage; | |
| 30 | ||
| 31 | /** | |
| 32 | * Provides stack trace information that isolates the cause. | |
| 33 | */ | |
| 34 | private final Throwable mProblem; | |
| 35 | ||
| 36 | /** | |
| 37 | * Constructs a new event that contains a problem description to help the | |
| 38 | * user resolve an issue encountered while using the application. | |
| 39 | * | |
| 40 | * @param message The human-readable message, typically displayed on-screen. | |
| 41 | */ | |
| 42 | public StatusEvent( final String message ) { | |
| 43 | assert message != null; | |
| 44 | mMessage = message; | |
| 45 | mProblem = null; | |
| 46 | } | |
| 47 | ||
| 48 | public StatusEvent( final Throwable problem ) { | |
| 49 | this( "", problem ); | |
| 50 | } | |
| 51 | ||
| 52 | public StatusEvent( final String message, final Throwable problem ) { | |
| 53 | assert message != null; | |
| 54 | assert problem != null; | |
| 55 | mMessage = message; | |
| 56 | mProblem = problem; | |
| 57 | } | |
| 58 | ||
| 59 | /** | |
| 60 | * Returns the stack trace information for the issue encountered. This is | |
| 61 | * optional because usually a status message isn't an application error. | |
| 62 | * | |
| 63 | * @return Optional stack trace to pin-point the problem area in the code. | |
| 64 | */ | |
| 65 | public String getProblem() { | |
| 66 | // 256 is arbitrary; stack traces shouldn't be much larger. | |
| 67 | final var sb = new StringBuilder( 256 ); | |
| 68 | final var trace = mProblem; | |
| 69 | ||
| 70 | if( trace != null ) { | |
| 71 | stream( trace.getStackTrace() ) | |
| 72 | .takeWhile( StatusEvent::filter ) | |
| 73 | .limit( 10 ) | |
| 74 | .collect( Collectors.toList() ) | |
| 75 | .forEach( e -> sb.append( e.toString() ).append( NEWLINE ) ); | |
| 76 | } | |
| 77 | ||
| 78 | return sb.toString(); | |
| 79 | } | |
| 80 | ||
| 81 | @Override | |
| 82 | public String toString() { | |
| 83 | return format( "%s%s%s", | |
| 84 | mMessage, | |
| 85 | mMessage.isBlank() ? "" : " ", | |
| 86 | mProblem == null ? "" : toEnglish( mProblem ) ); | |
| 87 | } | |
| 88 | ||
| 89 | private static boolean filter( final StackTraceElement e ) { | |
| 90 | final var clazz = e.getClassName(); | |
| 91 | return clazz.contains( PACKAGE_NAME ) || | |
| 92 | clazz.contains( "org.renjin." ) || | |
| 93 | clazz.contains( "sun." ) || | |
| 94 | clazz.contains( "flexmark." ) || | |
| 95 | clazz.contains( "java." ); | |
| 96 | } | |
| 97 | ||
| 98 | /** | |
| 99 | * Separates the exception class name from TitleCase into lowercase, | |
| 100 | * space-separated words. This makes the exception look a little more like | |
| 101 | * English. Any {@link RuntimeException} instances passed into this method | |
| 102 | * will have the cause extracted, if possible. | |
| 103 | * | |
| 104 | * @param problem The exception that triggered the status event change. | |
| 105 | * @return A human-readable message with the exception name and the | |
| 106 | * exception's message. | |
| 107 | */ | |
| 108 | private static String toEnglish( Throwable problem ) { | |
| 109 | assert problem != null; | |
| 110 | ||
| 111 | // Subclasses of RuntimeException must be subject to Englishification. | |
| 112 | if( problem.getClass().equals( RuntimeException.class ) ) { | |
| 113 | final var cause = problem.getCause(); | |
| 114 | return cause == null ? problem.getMessage() : cause.getMessage(); | |
| 115 | } | |
| 116 | ||
| 117 | final var className = problem.getClass().getSimpleName(); | |
| 118 | final var words = join( " ", className.split( ENGLISHIFY ) ); | |
| 119 | return format( "(%s: %s)", words.toLowerCase(), problem.getMessage() ); | |
| 120 | } | |
| 121 | ||
| 122 | /** | |
| 123 | * Returns the message used to construct the event. | |
| 124 | * | |
| 125 | * @return The message for this event. | |
| 126 | */ | |
| 127 | public String getMessage() { | |
| 128 | return mMessage; | |
| 129 | } | |
| 130 | ||
| 131 | /** | |
| 132 | * Resets the status bar to a default message. Indicates that there are no | |
| 133 | * issues to bring to the user's attention. | |
| 134 | */ | |
| 135 | public static void clue() { | |
| 136 | fireStatusEvent( get( STATUS_BAR_OK, "OK" ) ); | |
| 137 | } | |
| 138 | ||
| 139 | /** | |
| 140 | * Notifies listeners of a series of messages. This is useful when providing | |
| 141 | * users feedback of how third-party executables have failed. | |
| 142 | * | |
| 143 | * @param messages The lines of text to display. | |
| 144 | */ | |
| 145 | public static void clue( final List<String> messages ) { | |
| 146 | messages.forEach( StatusEvent::fireStatusEvent ); | |
| 147 | } | |
| 148 | ||
| 149 | /** | |
| 150 | * Notifies listeners of an error. | |
| 151 | * | |
| 152 | * @param key The message bundle key to look up. | |
| 153 | * @param t The exception that caused the error. | |
| 154 | */ | |
| 155 | public static void clue( final String key, final Throwable t ) { | |
| 156 | fireStatusEvent( get( key ), t ); | |
| 157 | } | |
| 158 | ||
| 159 | /** | |
| 160 | * Notifies listeners of a custom message. | |
| 161 | * | |
| 162 | * @param key The property key having a value to populate with arguments. | |
| 163 | * @param args The placeholder values to substitute into the key's value. | |
| 164 | */ | |
| 165 | public static void clue( final String key, final Object... args ) { | |
| 166 | fireStatusEvent( get( key, args ) ); | |
| 167 | } | |
| 168 | ||
| 169 | /** | |
| 170 | * Notifies listeners of an exception occurs that warrants the user's | |
| 171 | * attention. | |
| 172 | * | |
| 173 | * @param problem The exception with a message to display to the user. | |
| 174 | */ | |
| 175 | public static void clue( final Throwable problem ) { | |
| 176 | fireStatusEvent( problem ); | |
| 177 | } | |
| 178 | ||
| 179 | private static void fireStatusEvent( final String message ) { | |
| 180 | new StatusEvent( message ).fire(); | |
| 181 | } | |
| 182 | ||
| 183 | private static void fireStatusEvent( final Throwable problem ) { | |
| 184 | new StatusEvent( problem ).fire(); | |
| 185 | } | |
| 186 | ||
| 187 | private static void fireStatusEvent( | |
| 188 | final String message, final Throwable problem ) { | |
| 189 | new StatusEvent( message, problem ).fire(); | |
| 190 | } | |
| 191 | } | |
| 1 | 192 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import com.keenwrite.editors.TextDefinition; | |
| 5 | ||
| 6 | public class TextDefinitionFocusEvent extends FocusEvent<TextDefinition> { | |
| 7 | protected TextDefinitionFocusEvent( final TextDefinition editor ) { | |
| 8 | super( editor ); | |
| 9 | } | |
| 10 | ||
| 11 | /** | |
| 12 | * When the {@link TextDefinition} editor has focus, fire an event so that | |
| 13 | * subscribers may perform an action. | |
| 14 | * | |
| 15 | * @param editor The instance of editor that has gained input focus. | |
| 16 | */ | |
| 17 | public static void fireTextDefinitionFocus( final TextDefinition editor ) { | |
| 18 | new TextDefinitionFocusEvent( editor ).fire(); | |
| 19 | } | |
| 20 | } | |
| 1 | 21 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | import com.keenwrite.editors.TextEditor; | |
| 5 | ||
| 6 | public class TextEditorFocusEvent extends FocusEvent<TextEditor> { | |
| 7 | protected TextEditorFocusEvent( final TextEditor editor ) { | |
| 8 | super( editor ); | |
| 9 | } | |
| 10 | ||
| 11 | /** | |
| 12 | * When the {@link TextEditor} has focus, fire an event so that subscribers | |
| 13 | * may perform an action---such as parsing and rendering the contents. | |
| 14 | * | |
| 15 | * @param editor The instance of editor that has gained input focus. | |
| 16 | */ | |
| 17 | public static void fireTextEditorFocus( final TextEditor editor ) { | |
| 18 | new TextEditorFocusEvent( editor ).fire(); | |
| 19 | } | |
| 20 | } | |
| 1 | 21 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.events; | |
| 3 | ||
| 4 | /** | |
| 5 | * Collates information about the word count changing. | |
| 6 | */ | |
| 7 | public class WordCountEvent implements AppEvent { | |
| 8 | /** | |
| 9 | * Number of words in the document. | |
| 10 | */ | |
| 11 | private final int mCount; | |
| 12 | ||
| 13 | private WordCountEvent( final int count ) { | |
| 14 | mCount = count; | |
| 15 | } | |
| 16 | ||
| 17 | /** | |
| 18 | * Publishes an event that indicates the number of words in the document. | |
| 19 | * | |
| 20 | * @param count The approximate number of words in the document. | |
| 21 | */ | |
| 22 | public static void fireWordCountEvent( final int count ) { | |
| 23 | new WordCountEvent( count ).fire(); | |
| 24 | } | |
| 25 | ||
| 26 | public int getCount() { | |
| 27 | return mCount; | |
| 28 | } | |
| 29 | } | |
| 1 | 30 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.exceptions; | |
| 3 | ||
| 4 | import java.io.FileNotFoundException; | |
| 5 | ||
| 6 | import static com.keenwrite.Messages.get; | |
| 7 | ||
| 8 | /** | |
| 9 | * Responsible for informing the user when a file cannot be found. | |
| 10 | * This avoids duplicating the error message prefix. | |
| 11 | */ | |
| 12 | public final class MissingFileException extends FileNotFoundException { | |
| 13 | /** | |
| 14 | * Constructs a new {@link MissingFileException} using the given path. | |
| 15 | * | |
| 16 | * @param uri The path to the file resource that could not be found. | |
| 17 | */ | |
| 18 | public MissingFileException( final String uri ) { | |
| 19 | super( get( "Main.status.error.file.missing", uri ) ); | |
| 20 | } | |
| 21 | } | |
| 1 | 22 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | ||
| 3 | /** | |
| 4 | * This package contains classes to help with word count. In logographic, | |
| 5 | * or other non-alphabetic languages, word tokenization cannot rely on | |
| 6 | * spaces. Instead, we need to employ a more sophisticated approach using | |
| 7 | * natural language parsing (NLP). | |
| 8 | */ | |
| 9 | package com.keenwrite.heuristics; | |
| 1 | 10 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | import java.io.File; | |
| 5 | import java.util.EventObject; | |
| 6 | ||
| 7 | /** | |
| 8 | * Responsible for indicating that a file has been modified by the file system. | |
| 9 | */ | |
| 10 | public class FileEvent extends EventObject { | |
| 11 | ||
| 12 | /** | |
| 13 | * Constructs a new event that indicates the source of a file system event. | |
| 14 | * | |
| 15 | * @param file The {@link File} that has succumb to a file system event. | |
| 16 | */ | |
| 17 | public FileEvent( final File file ) { | |
| 18 | super( file ); | |
| 19 | } | |
| 20 | ||
| 21 | /** | |
| 22 | * Returns the source as an instance of {@link File}. | |
| 23 | * | |
| 24 | * @return The {@link File} being watched. | |
| 25 | */ | |
| 26 | public File getFile() { | |
| 27 | return (File) getSource(); | |
| 28 | } | |
| 29 | } | |
| 1 | 30 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | import java.util.EventListener; | |
| 5 | import java.util.function.Consumer; | |
| 6 | ||
| 7 | /** | |
| 8 | * Responsible for informing listeners when a file has been modified. | |
| 9 | */ | |
| 10 | public interface FileModifiedListener | |
| 11 | extends EventListener, Consumer<FileEvent> { | |
| 12 | } | |
| 1 | 13 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | /** | |
| 5 | * Represents different file type classifications. These are high-level mappings | |
| 6 | * that correspond to the list of glob patterns found within {@code | |
| 7 | * settings.properties}. | |
| 8 | */ | |
| 9 | public enum FileType { | |
| 10 | ||
| 11 | ALL( "all" ), | |
| 12 | RMARKDOWN( "rmarkdown" ), | |
| 13 | RXML( "rxml" ), | |
| 14 | SOURCE( "source" ), | |
| 15 | DEFINITION( "definition" ), | |
| 16 | CSV( "csv" ), | |
| 17 | JSON( "json" ), | |
| 18 | TOML( "toml" ), | |
| 19 | YAML( "yaml" ), | |
| 20 | PROPERTIES( "properties" ), | |
| 21 | UNKNOWN( "unknown" ); | |
| 22 | ||
| 23 | private final String mType; | |
| 24 | ||
| 25 | /** | |
| 26 | * Default constructor for enumerated file type. | |
| 27 | * | |
| 28 | * @param type Human-readable name for the file type. | |
| 29 | */ | |
| 30 | FileType( final String type ) { | |
| 31 | mType = type; | |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * Returns the file type that corresponds to the given string. | |
| 36 | * | |
| 37 | * @param type The string to compare against this enumeration of file types. | |
| 38 | * @return The corresponding File Type for the given string. | |
| 39 | * @throws IllegalArgumentException Type not found. | |
| 40 | */ | |
| 41 | public static FileType from( final String type ) { | |
| 42 | for( final FileType fileType : FileType.values() ) { | |
| 43 | if( fileType.isType( type ) ) { | |
| 44 | return fileType; | |
| 45 | } | |
| 46 | } | |
| 47 | ||
| 48 | throw new IllegalArgumentException( type ); | |
| 49 | } | |
| 50 | ||
| 51 | /** | |
| 52 | * Answers whether this file type matches the given string, case insensitive | |
| 53 | * comparison. | |
| 54 | * | |
| 55 | * @param type Presumably a file name extension to check against. | |
| 56 | * @return true The given extension corresponds to this enumerated type. | |
| 57 | */ | |
| 58 | public boolean isType( final String type ) { | |
| 59 | return getType().equalsIgnoreCase( type ); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Returns the human-readable name for the file type. | |
| 64 | * | |
| 65 | * @return A non-null instance. | |
| 66 | */ | |
| 67 | private String getType() { | |
| 68 | return mType; | |
| 69 | } | |
| 70 | ||
| 71 | /** | |
| 72 | * Returns the lowercase version of the file name extension. | |
| 73 | * | |
| 74 | * @return The file name, in lower case. | |
| 75 | */ | |
| 76 | @Override | |
| 77 | public String toString() { | |
| 78 | return getType(); | |
| 79 | } | |
| 80 | } | |
| 1 | 81 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | import org.renjin.repackaged.guava.collect.BiMap; | |
| 5 | import org.renjin.repackaged.guava.collect.HashBiMap; | |
| 6 | ||
| 7 | import java.io.File; | |
| 8 | import java.io.IOException; | |
| 9 | import java.nio.file.Path; | |
| 10 | import java.nio.file.WatchKey; | |
| 11 | import java.nio.file.WatchService; | |
| 12 | import java.util.Set; | |
| 13 | import java.util.concurrent.ConcurrentHashMap; | |
| 14 | ||
| 15 | import static java.nio.file.FileSystems.getDefault; | |
| 16 | import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; | |
| 17 | import static java.util.Collections.newSetFromMap; | |
| 18 | ||
| 19 | /** | |
| 20 | * Responsible for watching when a file has been changed. | |
| 21 | */ | |
| 22 | public class FileWatchService implements Runnable { | |
| 23 | /** | |
| 24 | * Set to {@code false} when {@link #stop()} is called. | |
| 25 | */ | |
| 26 | private volatile boolean mRunning; | |
| 27 | ||
| 28 | /** | |
| 29 | * Contains the listeners to notify when a given file has changed. | |
| 30 | */ | |
| 31 | private final Set<FileModifiedListener> mListeners = | |
| 32 | newSetFromMap( new ConcurrentHashMap<>() ); | |
| 33 | private final WatchService mWatchService; | |
| 34 | private final BiMap<File, WatchKey> mWatched = HashBiMap.create(); | |
| 35 | ||
| 36 | /** | |
| 37 | * Creates a new file system watch service with the given files to watch. | |
| 38 | * | |
| 39 | * @param files The files to watch for file system events. | |
| 40 | */ | |
| 41 | public FileWatchService( final File... files ) { | |
| 42 | mWatchService = createWatchService(); | |
| 43 | ||
| 44 | try { | |
| 45 | for( final var file : files ) { | |
| 46 | register( file ); | |
| 47 | } | |
| 48 | } catch( final Exception ex ) { | |
| 49 | throw new RuntimeException( ex ); | |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Runs the event handler until {@link #stop()} is called. | |
| 55 | * | |
| 56 | * @throws RuntimeException There was an error watching for file events. | |
| 57 | */ | |
| 58 | @Override | |
| 59 | public void run() { | |
| 60 | mRunning = true; | |
| 61 | ||
| 62 | while( mRunning ) { | |
| 63 | handleEvents(); | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | private void handleEvents() { | |
| 68 | try { | |
| 69 | final var watchKey = mWatchService.take(); | |
| 70 | ||
| 71 | for( final var pollEvent : watchKey.pollEvents() ) { | |
| 72 | final var watchable = (Path) watchKey.watchable(); | |
| 73 | final var context = (Path) pollEvent.context(); | |
| 74 | final var file = watchable.resolve( context ).toFile(); | |
| 75 | ||
| 76 | if( mWatched.containsKey( file ) ) { | |
| 77 | final var fileEvent = new FileEvent( file ); | |
| 78 | ||
| 79 | for( final var listener : mListeners ) { | |
| 80 | listener.accept( fileEvent ); | |
| 81 | } | |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | if( !watchKey.reset() ) { | |
| 86 | unregister( watchKey ); | |
| 87 | } | |
| 88 | } catch( final Exception ex ) { | |
| 89 | throw new RuntimeException( ex ); | |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Adds the given {@link File}'s containing directory to the watch list. When | |
| 95 | * the given {@link File} is modified, this service will receive a | |
| 96 | * notification that the containing directory has been modified, which will | |
| 97 | * then be filtered by file name. | |
| 98 | * <p> | |
| 99 | * This method is idempotent. | |
| 100 | * </p> | |
| 101 | * | |
| 102 | * @param file The {@link File} to watch for modification events. | |
| 103 | * @return The {@link File}'s directory watch state. | |
| 104 | * @throws IOException Could not register the directory. | |
| 105 | * @throws IllegalArgumentException The {@link File} has no parent directory. | |
| 106 | */ | |
| 107 | public WatchKey register( final File file ) throws IOException { | |
| 108 | if( mWatched.containsKey( file ) ) { | |
| 109 | return mWatched.get( file ); | |
| 110 | } | |
| 111 | ||
| 112 | final var path = getParentDirectory( file ); | |
| 113 | final var watchKey = path.register( mWatchService, ENTRY_MODIFY ); | |
| 114 | ||
| 115 | return mWatched.put( file, watchKey ); | |
| 116 | } | |
| 117 | ||
| 118 | /** | |
| 119 | * Removes the given {@link File}'s containing directory from the watch list. | |
| 120 | * <p> | |
| 121 | * This method is idempotent. | |
| 122 | * </p> | |
| 123 | * | |
| 124 | * @param file The {@link File} to no longer watch. | |
| 125 | * @throws IllegalArgumentException The {@link File} has no parent directory. | |
| 126 | */ | |
| 127 | public void unregister( final File file ) { | |
| 128 | mWatched.remove( cancel( file ) ); | |
| 129 | } | |
| 130 | ||
| 131 | /** | |
| 132 | * Cancels watching the given file for file system changes. | |
| 133 | * | |
| 134 | * @param file The {@link File} to watch for file events. | |
| 135 | * @return The given file, always. | |
| 136 | */ | |
| 137 | private File cancel( final File file ) { | |
| 138 | final var watchKey = mWatched.get( file ); | |
| 139 | ||
| 140 | if( watchKey != null ) { | |
| 141 | watchKey.cancel(); | |
| 142 | } | |
| 143 | ||
| 144 | return file; | |
| 145 | } | |
| 146 | ||
| 147 | /** | |
| 148 | * Removes the given {@link WatchKey} from the registration map. | |
| 149 | * | |
| 150 | * @param watchKey The {@link WatchKey} to remove from the map. | |
| 151 | */ | |
| 152 | private void unregister( final WatchKey watchKey ) { | |
| 153 | unregister( mWatched.inverse().get( watchKey ) ); | |
| 154 | } | |
| 155 | ||
| 156 | /** | |
| 157 | * Adds a listener to be notified when a file under watch has been modified. | |
| 158 | * Listeners are backed by a set. | |
| 159 | * | |
| 160 | * @param listener The {@link FileModifiedListener} to add to the list. | |
| 161 | * @return {@code true} if this set did not already contain listener. | |
| 162 | */ | |
| 163 | public boolean addListener( final FileModifiedListener listener ) { | |
| 164 | return mListeners.add( listener ); | |
| 165 | } | |
| 166 | ||
| 167 | /** | |
| 168 | * Removes a listener from the notify list. | |
| 169 | * | |
| 170 | * @param listener The {@link FileModifiedListener} to remove. | |
| 171 | * @return {@code true} if this contained the given listener. | |
| 172 | */ | |
| 173 | public boolean removeListener( final FileModifiedListener listener ) { | |
| 174 | return mListeners.remove( listener ); | |
| 175 | } | |
| 176 | ||
| 177 | /** | |
| 178 | * Shuts down the file watch service and clears both watchers and listeners. | |
| 179 | * | |
| 180 | * @throws IOException Could not close the watch service. | |
| 181 | */ | |
| 182 | public void stop() throws IOException { | |
| 183 | mRunning = false; | |
| 184 | ||
| 185 | for( final var file : mWatched.keySet() ) { | |
| 186 | cancel( file ); | |
| 187 | } | |
| 188 | ||
| 189 | mWatched.clear(); | |
| 190 | mListeners.clear(); | |
| 191 | mWatchService.close(); | |
| 192 | } | |
| 193 | ||
| 194 | /** | |
| 195 | * Returns the directory containing the given {@link File} instance. | |
| 196 | * | |
| 197 | * @param file The {@link File}'s containing directory to watch. | |
| 198 | * @return The {@link Path} to the {@link File}'s directory. | |
| 199 | * @throws IllegalArgumentException The {@link File} has no parent directory. | |
| 200 | */ | |
| 201 | private Path getParentDirectory( final File file ) { | |
| 202 | assert file != null; | |
| 203 | assert !file.isDirectory(); | |
| 204 | ||
| 205 | final var directory = file.getParentFile(); | |
| 206 | ||
| 207 | if( directory == null ) { | |
| 208 | throw new IllegalArgumentException( file.getAbsolutePath() ); | |
| 209 | } | |
| 210 | ||
| 211 | return directory.toPath(); | |
| 212 | } | |
| 213 | ||
| 214 | private WatchService createWatchService() { | |
| 215 | try { | |
| 216 | return getDefault().newWatchService(); | |
| 217 | } catch( final Exception ex ) { | |
| 218 | // Create a fallback that allows the class to be instantiated and used | |
| 219 | // without without preventing the application from launching. | |
| 220 | return new PollingWatchService(); | |
| 221 | } | |
| 222 | } | |
| 223 | } | |
| 1 | 224 |
| 1 | package com.keenwrite.io; | |
| 2 | ||
| 3 | import java.io.BufferedInputStream; | |
| 4 | import java.io.Closeable; | |
| 5 | import java.io.IOException; | |
| 6 | import java.io.InputStream; | |
| 7 | import java.net.HttpURLConnection; | |
| 8 | import java.net.URI; | |
| 9 | import java.net.URL; | |
| 10 | import java.net.URLConnection; | |
| 11 | import java.util.zip.GZIPInputStream; | |
| 12 | ||
| 13 | import static com.keenwrite.events.StatusEvent.clue; | |
| 14 | import static java.lang.System.getProperty; | |
| 15 | import static java.lang.System.setProperty; | |
| 16 | import static java.net.HttpURLConnection.HTTP_OK; | |
| 17 | import static java.net.HttpURLConnection.setFollowRedirects; | |
| 18 | ||
| 19 | /** | |
| 20 | * Responsible for making HTTP requests, a thin wrapper around the | |
| 21 | * {@link URLConnection} class. This will attempt to use compression. | |
| 22 | * <p> | |
| 23 | * This class must be used within a try-with-resources block to ensure all | |
| 24 | * resources are released, even if only calling {@link Response#getMediaType()}. | |
| 25 | * </p> | |
| 26 | */ | |
| 27 | public class HttpFacade { | |
| 28 | static { | |
| 29 | setProperty( "http.keepAlive", "false" ); | |
| 30 | setFollowRedirects( true ); | |
| 31 | } | |
| 32 | ||
| 33 | /** | |
| 34 | * Sends an HTTP GET request to a server. | |
| 35 | * | |
| 36 | * @param url The remote resource to fetch. | |
| 37 | * @return The server response. | |
| 38 | */ | |
| 39 | public static Response httpGet( final URL url ) throws Exception { | |
| 40 | return new Response( url ); | |
| 41 | } | |
| 42 | ||
| 43 | /** | |
| 44 | * Convenience method to send an HTTP GET request to a server. | |
| 45 | * | |
| 46 | * @param uri The remote resource to fetch. | |
| 47 | * @return The server response. | |
| 48 | * @see #httpGet(URL) | |
| 49 | */ | |
| 50 | public static Response httpGet( final URI uri ) throws Exception { | |
| 51 | return httpGet( uri.toURL() ); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Convenience method to send an HTTP GET request to a server. | |
| 56 | * | |
| 57 | * @param url The remote resource to fetch. | |
| 58 | * @return The server response. | |
| 59 | * @see #httpGet(URL) | |
| 60 | */ | |
| 61 | public static Response httpGet( final String url ) throws Exception { | |
| 62 | return httpGet( new URL( url ) ); | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Callers are responsible for closing the response. | |
| 67 | */ | |
| 68 | public static final class Response implements Closeable { | |
| 69 | private final HttpURLConnection mConn; | |
| 70 | private final BufferedInputStream mStream; | |
| 71 | ||
| 72 | private Response( final URL url ) throws IOException { | |
| 73 | assert url != null; | |
| 74 | ||
| 75 | clue( "Main.status.image.request.init" ); | |
| 76 | ||
| 77 | if( url.openConnection() instanceof HttpURLConnection conn ) { | |
| 78 | conn.setUseCaches( false ); | |
| 79 | conn.setInstanceFollowRedirects( true ); | |
| 80 | conn.setRequestProperty( "Accept-Encoding", "gzip" ); | |
| 81 | conn.setRequestProperty( "User-Agent", getProperty( "http.agent" ) ); | |
| 82 | conn.setRequestMethod( "GET" ); | |
| 83 | conn.setConnectTimeout( 15000 ); | |
| 84 | conn.setRequestProperty( "connection", "close" ); | |
| 85 | conn.connect(); | |
| 86 | ||
| 87 | clue( "Main.status.image.request.fetch", url.getHost() ); | |
| 88 | ||
| 89 | final var code = conn.getResponseCode(); | |
| 90 | ||
| 91 | // Even though there are other "okay" error codes, tell the user when | |
| 92 | // a resource has changed in any unexpected way. | |
| 93 | if( code != HTTP_OK ) { | |
| 94 | throw new IOException( url + " [HTTP " + code + "]" ); | |
| 95 | } | |
| 96 | ||
| 97 | mConn = conn; | |
| 98 | mStream = openBufferedInputStream(); | |
| 99 | } | |
| 100 | else { | |
| 101 | throw new UnsupportedOperationException( url.toString() ); | |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Returns the {@link MediaType} based on the resulting HTTP content type | |
| 107 | * provided by the server. If the content type from the server is not | |
| 108 | * found, this will probe the first several bytes to determine the type. | |
| 109 | * | |
| 110 | * @return The stream's IANA-defined {@link MediaType}. | |
| 111 | */ | |
| 112 | public MediaType getMediaType() throws IOException { | |
| 113 | final var contentType = mConn.getContentType(); | |
| 114 | var mediaType = MediaType.valueFrom( contentType ); | |
| 115 | ||
| 116 | if( mediaType.isUndefined() ) { | |
| 117 | mediaType = MediaTypeSniffer.getMediaType( mStream ); | |
| 118 | } | |
| 119 | ||
| 120 | clue( "Main.status.image.request.success", mediaType ); | |
| 121 | return mediaType; | |
| 122 | } | |
| 123 | ||
| 124 | /** | |
| 125 | * Returns the stream opened using an HTTP connection, decompressing if | |
| 126 | * the server supports gzip compression. The caller must close the stream | |
| 127 | * by calling {@link #close()} on this object. | |
| 128 | * | |
| 129 | * @return The stream representing the content at the URL used to | |
| 130 | * construct the {@link HttpFacade}. | |
| 131 | */ | |
| 132 | public InputStream getInputStream() throws IOException { | |
| 133 | return mStream; | |
| 134 | } | |
| 135 | ||
| 136 | /** | |
| 137 | * This will disconnect the HTTP request and close the associated stream. | |
| 138 | */ | |
| 139 | @Override | |
| 140 | public void close() { | |
| 141 | mConn.disconnect(); | |
| 142 | } | |
| 143 | ||
| 144 | /** | |
| 145 | * Opens the connection for reading. It is an error to call this more than | |
| 146 | * once. This may use gzip compression. A {@link BufferedInputStream} is | |
| 147 | * returned to allow peeking at the stream when checking the content | |
| 148 | * type. | |
| 149 | * | |
| 150 | * @return The {@link InputStream} containing content from an HTTP request. | |
| 151 | * @throws IOException Could not open the stream. | |
| 152 | */ | |
| 153 | private BufferedInputStream openBufferedInputStream() throws IOException { | |
| 154 | final var encoding = mConn.getContentEncoding(); | |
| 155 | final var is = mConn.getInputStream(); | |
| 156 | ||
| 157 | return new BufferedInputStream( | |
| 158 | "gzip".equalsIgnoreCase( encoding ) ? new GZIPInputStream( is ) : is ); | |
| 159 | } | |
| 160 | } | |
| 161 | } | |
| 1 | 162 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | import java.io.File; | |
| 5 | import java.io.IOException; | |
| 6 | import java.nio.file.Path; | |
| 7 | ||
| 8 | import static com.keenwrite.io.MediaType.TypeName.*; | |
| 9 | import static com.keenwrite.io.MediaTypeExtension.getMediaType; | |
| 10 | import static java.io.File.createTempFile; | |
| 11 | import static org.apache.commons.io.FilenameUtils.getExtension; | |
| 12 | ||
| 13 | /** | |
| 14 | * Defines various file formats and format contents. | |
| 15 | * | |
| 16 | * @see | |
| 17 | * <a href="https://www.iana.org/assignments/media-types/media-types.xhtml">IANA | |
| 18 | * Media Types</a> | |
| 19 | */ | |
| 20 | public enum MediaType { | |
| 21 | APP_DOCUMENT_OUTLINE( APPLICATION, "x-document-outline" ), | |
| 22 | APP_DOCUMENT_STATISTICS( APPLICATION, "x-document-statistics" ), | |
| 23 | APP_FILE_MANAGER( APPLICATION, "x-file-manager" ), | |
| 24 | ||
| 25 | APP_ACAD( APPLICATION, "acad" ), | |
| 26 | APP_JAVA_OBJECT( APPLICATION, "x-java-serialized-object" ), | |
| 27 | APP_JAVA( APPLICATION, "java" ), | |
| 28 | APP_PS( APPLICATION, "postscript" ), | |
| 29 | APP_EPS( APPLICATION, "eps" ), | |
| 30 | APP_PDF( APPLICATION, "pdf" ), | |
| 31 | APP_ZIP( APPLICATION, "zip" ), | |
| 32 | ||
| 33 | /* | |
| 34 | * Standard font types. | |
| 35 | */ | |
| 36 | FONT_OTF( "otf" ), | |
| 37 | FONT_TTF( "ttf" ), | |
| 38 | ||
| 39 | /* | |
| 40 | * Standard image types. | |
| 41 | */ | |
| 42 | IMAGE_APNG( "apng" ), | |
| 43 | IMAGE_ACES( "aces" ), | |
| 44 | IMAGE_AVCI( "avci" ), | |
| 45 | IMAGE_AVCS( "avcs" ), | |
| 46 | IMAGE_BMP( "bmp" ), | |
| 47 | IMAGE_CGM( "cgm" ), | |
| 48 | IMAGE_DICOM_RLE( "dicom_rle" ), | |
| 49 | IMAGE_EMF( "emf" ), | |
| 50 | IMAGE_EXAMPLE( "example" ), | |
| 51 | IMAGE_FITS( "fits" ), | |
| 52 | IMAGE_G3FAX( "g3fax" ), | |
| 53 | IMAGE_GIF( "gif" ), | |
| 54 | IMAGE_HEIC( "heic" ), | |
| 55 | IMAGE_HEIF( "heif" ), | |
| 56 | IMAGE_HEJ2K( "hej2k" ), | |
| 57 | IMAGE_HSJ2( "hsj2" ), | |
| 58 | IMAGE_X_ICON( "x-icon" ), | |
| 59 | IMAGE_JLS( "jls" ), | |
| 60 | IMAGE_JP2( "jp2" ), | |
| 61 | IMAGE_JPEG( "jpeg" ), | |
| 62 | IMAGE_JPH( "jph" ), | |
| 63 | IMAGE_JPHC( "jphc" ), | |
| 64 | IMAGE_JPM( "jpm" ), | |
| 65 | IMAGE_JPX( "jpx" ), | |
| 66 | IMAGE_JXR( "jxr" ), | |
| 67 | IMAGE_JXRA( "jxrA" ), | |
| 68 | IMAGE_JXRS( "jxrS" ), | |
| 69 | IMAGE_JXS( "jxs" ), | |
| 70 | IMAGE_JXSC( "jxsc" ), | |
| 71 | IMAGE_JXSI( "jxsi" ), | |
| 72 | IMAGE_JXSS( "jxss" ), | |
| 73 | IMAGE_KTX( "ktx" ), | |
| 74 | IMAGE_KTX2( "ktx2" ), | |
| 75 | IMAGE_NAPLPS( "naplps" ), | |
| 76 | IMAGE_PNG( "png" ), | |
| 77 | IMAGE_PHOTOSHOP( "photoshop" ), | |
| 78 | IMAGE_SVG_XML( "svg+xml" ), | |
| 79 | IMAGE_T38( "t38" ), | |
| 80 | IMAGE_TIFF( "tiff" ), | |
| 81 | IMAGE_WEBP( "webp" ), | |
| 82 | IMAGE_WMF( "wmf" ), | |
| 83 | IMAGE_X_BITMAP( "x-xbitmap" ), | |
| 84 | IMAGE_X_PIXMAP( "x-xpixmap" ), | |
| 85 | ||
| 86 | /* | |
| 87 | * Standard audio types. | |
| 88 | */ | |
| 89 | AUDIO_BASIC( AUDIO, "basic" ), | |
| 90 | AUDIO_MP3( AUDIO, "mp3" ), | |
| 91 | AUDIO_WAV( AUDIO, "x-wav" ), | |
| 92 | ||
| 93 | /* | |
| 94 | * Standard video types. | |
| 95 | */ | |
| 96 | VIDEO_MNG( VIDEO, "x-mng" ), | |
| 97 | ||
| 98 | /* | |
| 99 | * Document types for editing or displaying documents, mix of standard and | |
| 100 | * application-specific. | |
| 101 | */ | |
| 102 | TEXT_HTML( TEXT, "html" ), | |
| 103 | TEXT_MARKDOWN( TEXT, "markdown" ), | |
| 104 | TEXT_PLAIN( TEXT, "plain" ), | |
| 105 | TEXT_R_MARKDOWN( TEXT, "R+markdown" ), | |
| 106 | TEXT_XHTML( TEXT, "xhtml+xml" ), | |
| 107 | TEXT_XML( TEXT, "xml" ), | |
| 108 | TEXT_YAML( TEXT, "yaml" ), | |
| 109 | ||
| 110 | /* | |
| 111 | * When all other lights go out. | |
| 112 | */ | |
| 113 | UNDEFINED( TypeName.UNDEFINED, "undefined" ); | |
| 114 | ||
| 115 | /** | |
| 116 | * The IANA-defined types. | |
| 117 | */ | |
| 118 | public enum TypeName { | |
| 119 | APPLICATION, | |
| 120 | AUDIO, | |
| 121 | IMAGE, | |
| 122 | TEXT, | |
| 123 | UNDEFINED, | |
| 124 | VIDEO | |
| 125 | } | |
| 126 | ||
| 127 | /** | |
| 128 | * The fully qualified IANA-defined media type. | |
| 129 | */ | |
| 130 | private final String mMediaType; | |
| 131 | ||
| 132 | /** | |
| 133 | * The IANA-defined type name. | |
| 134 | */ | |
| 135 | private final TypeName mTypeName; | |
| 136 | ||
| 137 | /** | |
| 138 | * The IANA-defined subtype name. | |
| 139 | */ | |
| 140 | private final String mSubtype; | |
| 141 | ||
| 142 | /** | |
| 143 | * Constructs an instance using the default type name of "image". | |
| 144 | * | |
| 145 | * @param subtype The image subtype name. | |
| 146 | */ | |
| 147 | MediaType( final String subtype ) { | |
| 148 | this( IMAGE, subtype ); | |
| 149 | } | |
| 150 | ||
| 151 | /** | |
| 152 | * Constructs an instance using an IANA-defined type and subtype pair. | |
| 153 | * | |
| 154 | * @param typeName The media type's type name. | |
| 155 | * @param subtype The media type's subtype name. | |
| 156 | */ | |
| 157 | MediaType( final TypeName typeName, final String subtype ) { | |
| 158 | mTypeName = typeName; | |
| 159 | mSubtype = subtype; | |
| 160 | mMediaType = typeName.toString().toLowerCase() + '/' + subtype; | |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Returns the {@link MediaType} associated with the given file. | |
| 165 | * | |
| 166 | * @param file Has a file name that may contain an extension associated with | |
| 167 | * a known {@link MediaType}. | |
| 168 | * @return {@link MediaType#UNDEFINED} if the extension has not been | |
| 169 | * assigned, otherwise the {@link MediaType} associated with this | |
| 170 | * {@link File}'s file name extension. | |
| 171 | */ | |
| 172 | public static MediaType valueFrom( final File file ) { | |
| 173 | assert file != null; | |
| 174 | return fromFilename( file.getName() ); | |
| 175 | } | |
| 176 | ||
| 177 | /** | |
| 178 | * Returns the {@link MediaType} associated with the given file name. | |
| 179 | * | |
| 180 | * @param filename The file name that may contain an extension associated | |
| 181 | * with a known {@link MediaType}. | |
| 182 | * @return {@link MediaType#UNDEFINED} if the extension has not been | |
| 183 | * assigned, otherwise the {@link MediaType} associated with this | |
| 184 | * URL's file name extension. | |
| 185 | */ | |
| 186 | public static MediaType fromFilename( final String filename ) { | |
| 187 | assert filename != null; | |
| 188 | return getMediaType( getExtension( filename ) ); | |
| 189 | } | |
| 190 | ||
| 191 | /** | |
| 192 | * Returns the {@link MediaType} associated with the path to a file. | |
| 193 | * | |
| 194 | * @param path Has a file name that may contain an extension associated with | |
| 195 | * a known {@link MediaType}. | |
| 196 | * @return {@link MediaType#UNDEFINED} if the extension has not been | |
| 197 | * assigned, otherwise the {@link MediaType} associated with this | |
| 198 | * {@link File}'s file name extension. | |
| 199 | */ | |
| 200 | public static MediaType valueFrom( final Path path ) { | |
| 201 | assert path != null; | |
| 202 | return valueFrom( path.toFile() ); | |
| 203 | } | |
| 204 | ||
| 205 | /** | |
| 206 | * Determines the media type an IANA-defined, semi-colon-separated string. | |
| 207 | * This is often used after making an HTTP request to extract the type | |
| 208 | * and subtype from the content-type. | |
| 209 | * | |
| 210 | * @param header The content-type header value, may be {@code null}. | |
| 211 | * @return The data type for the resource or {@link MediaType#UNDEFINED} if | |
| 212 | * unmapped. | |
| 213 | */ | |
| 214 | public static MediaType valueFrom( String header ) { | |
| 215 | if( header == null || header.isBlank() ) { | |
| 216 | return UNDEFINED; | |
| 217 | } | |
| 218 | ||
| 219 | // Trim off the character encoding. | |
| 220 | var i = header.indexOf( ';' ); | |
| 221 | header = header.substring( 0, i == -1 ? header.length() : i ); | |
| 222 | ||
| 223 | // Split the type and subtype. | |
| 224 | i = header.indexOf( '/' ); | |
| 225 | i = i == -1 ? header.length() : i; | |
| 226 | final var type = header.substring( 0, i ); | |
| 227 | final var subtype = header.substring( i + 1 ); | |
| 228 | ||
| 229 | return valueFrom( type, subtype ); | |
| 230 | } | |
| 231 | ||
| 232 | /** | |
| 233 | * Returns the {@link MediaType} for the given type and subtype names. | |
| 234 | * | |
| 235 | * @param type The IANA-defined type name. | |
| 236 | * @param subtype The IANA-defined subtype name. | |
| 237 | * @return {@link MediaType#UNDEFINED} if there is no {@link MediaType} that | |
| 238 | * matches the given type and subtype names. | |
| 239 | */ | |
| 240 | public static MediaType valueFrom( | |
| 241 | final String type, final String subtype ) { | |
| 242 | assert type != null; | |
| 243 | assert subtype != null; | |
| 244 | ||
| 245 | for( final var mediaType : values() ) { | |
| 246 | if( mediaType.equals( type, subtype ) ) { | |
| 247 | return mediaType; | |
| 248 | } | |
| 249 | } | |
| 250 | ||
| 251 | return UNDEFINED; | |
| 252 | } | |
| 253 | ||
| 254 | /** | |
| 255 | * Answers whether the given type and subtype names equal this enumerated | |
| 256 | * value. This performs a case-insensitive comparison. | |
| 257 | * | |
| 258 | * @param type The type name to compare against this {@link MediaType}. | |
| 259 | * @param subtype The subtype name to compare against this {@link MediaType}. | |
| 260 | * @return {@code true} when the type and subtype name match. | |
| 261 | */ | |
| 262 | public boolean equals( final String type, final String subtype ) { | |
| 263 | assert type != null; | |
| 264 | assert subtype != null; | |
| 265 | ||
| 266 | return mTypeName.name().equalsIgnoreCase( type ) && | |
| 267 | mSubtype.equalsIgnoreCase( subtype ); | |
| 268 | } | |
| 269 | ||
| 270 | /** | |
| 271 | * Answers whether the given {@link TypeName} matches this type name. | |
| 272 | * | |
| 273 | * @param typeName The {@link TypeName} to compare against the internal value. | |
| 274 | * @return {@code true} if the given value is the same IANA-defined type name. | |
| 275 | */ | |
| 276 | public boolean isType( final TypeName typeName ) { | |
| 277 | return mTypeName == typeName; | |
| 278 | } | |
| 279 | ||
| 280 | /** | |
| 281 | * Answers whether this instance is a scalable vector graphic. | |
| 282 | * | |
| 283 | * @return {@code true} if this instance represents an SVG object. | |
| 284 | */ | |
| 285 | public boolean isSvg() { | |
| 286 | return this == IMAGE_SVG_XML; | |
| 287 | } | |
| 288 | ||
| 289 | public boolean isUndefined() { | |
| 290 | return this == UNDEFINED; | |
| 291 | } | |
| 292 | ||
| 293 | /** | |
| 294 | * Returns the IANA-defined subtype classification. Primarily used by | |
| 295 | * {@link MediaTypeExtension} to initialize associations where the subtype | |
| 296 | * name and the file name extension have a 1:1 mapping. | |
| 297 | * | |
| 298 | * @return The IANA subtype value. | |
| 299 | */ | |
| 300 | public String getSubtype() { | |
| 301 | return mSubtype; | |
| 302 | } | |
| 303 | ||
| 304 | /** | |
| 305 | * Creates a temporary {@link File} that starts with the given prefix. The | |
| 306 | * file will be deleted when the application exits. | |
| 307 | * | |
| 308 | * @param prefix The file name begins with this string (may be empty). | |
| 309 | * @return The fully qualified path to the temporary file. | |
| 310 | * @throws IOException Could not create the temporary file. | |
| 311 | */ | |
| 312 | public Path createTemporaryFile( final String prefix ) throws IOException { | |
| 313 | assert prefix != null; | |
| 314 | ||
| 315 | final var file = createTempFile( | |
| 316 | prefix, '.' + MediaTypeExtension.valueFrom( this ).getExtension() ); | |
| 317 | file.deleteOnExit(); | |
| 318 | return file.toPath(); | |
| 319 | } | |
| 320 | ||
| 321 | /** | |
| 322 | * Returns the IANA-defined type and sub-type. | |
| 323 | * | |
| 324 | * @return The unique media type identifier. | |
| 325 | */ | |
| 326 | @Override | |
| 327 | public String toString() { | |
| 328 | return mMediaType; | |
| 329 | } | |
| 330 | } | |
| 1 | 331 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | import java.util.List; | |
| 5 | ||
| 6 | import static com.keenwrite.io.MediaType.*; | |
| 7 | import static java.util.List.of; | |
| 8 | ||
| 9 | /** | |
| 10 | * Responsible for associating file extensions with {@link MediaType} instances. | |
| 11 | * Insertion order must be maintained because the first element in the list | |
| 12 | * represents the file name extension that corresponds to its icon. | |
| 13 | */ | |
| 14 | public enum MediaTypeExtension { | |
| 15 | MEDIA_APP_ACAD( APP_ACAD, of( "dwg" ) ), | |
| 16 | MEDIA_APP_PDF( APP_PDF ), | |
| 17 | MEDIA_APP_PS( APP_PS, of( "ps" ) ), | |
| 18 | MEDIA_APP_EPS( APP_EPS ), | |
| 19 | MEDIA_APP_ZIP( APP_ZIP ), | |
| 20 | ||
| 21 | MEDIA_AUDIO_MP3( AUDIO_MP3 ), | |
| 22 | MEDIA_AUDIO_BASIC( AUDIO_BASIC, of( "au" ) ), | |
| 23 | MEDIA_AUDIO_WAV( AUDIO_WAV, of( "wav" ) ), | |
| 24 | ||
| 25 | MEDIA_FONT_OTF( FONT_OTF ), | |
| 26 | MEDIA_FONT_TTF( FONT_TTF ), | |
| 27 | ||
| 28 | MEDIA_IMAGE_APNG( IMAGE_APNG ), | |
| 29 | MEDIA_IMAGE_BMP( IMAGE_BMP ), | |
| 30 | MEDIA_IMAGE_GIF( IMAGE_GIF ), | |
| 31 | MEDIA_IMAGE_JPEG( IMAGE_JPEG, | |
| 32 | of( "jpg", "jpe", "jpeg", "jfif", "pjpeg", "pjp" ) ), | |
| 33 | MEDIA_IMAGE_PNG( IMAGE_PNG ), | |
| 34 | MEDIA_IMAGE_PSD( IMAGE_PHOTOSHOP, of( "psd" ) ), | |
| 35 | MEDIA_IMAGE_SVG( IMAGE_SVG_XML, of( "svg" ) ), | |
| 36 | MEDIA_IMAGE_TIFF( IMAGE_TIFF, of( "tiff", "tif" ) ), | |
| 37 | MEDIA_IMAGE_WEBP( IMAGE_WEBP ), | |
| 38 | MEDIA_IMAGE_X_BITMAP( IMAGE_X_BITMAP, of( "xbm" ) ), | |
| 39 | MEDIA_IMAGE_X_PIXMAP( IMAGE_X_PIXMAP, of( "xpm" ) ), | |
| 40 | ||
| 41 | MEDIA_VIDEO_MNG( VIDEO_MNG, of( "mng" ) ), | |
| 42 | ||
| 43 | MEDIA_TEXT_MARKDOWN( TEXT_MARKDOWN, of( | |
| 44 | "md", "markdown", "mdown", "mdtxt", "mdtext", "mdwn", "mkd", "mkdown", | |
| 45 | "mkdn" ) ), | |
| 46 | MEDIA_TEXT_PLAIN( TEXT_PLAIN, of( "txt", "asc", "ascii", "text", "utxt" ) ), | |
| 47 | MEDIA_TEXT_R_MARKDOWN( TEXT_R_MARKDOWN, of( "Rmd" ) ), | |
| 48 | MEDIA_TEXT_XHTML( TEXT_XHTML, of( "xhtml" ) ), | |
| 49 | MEDIA_TEXT_XML( TEXT_XML ), | |
| 50 | MEDIA_TEXT_YAML( TEXT_YAML, of( "yaml", "yml" ) ), | |
| 51 | ||
| 52 | MEDIA_UNDEFINED( UNDEFINED, of( "undefined" ) ); | |
| 53 | ||
| 54 | private final MediaType mMediaType; | |
| 55 | private final List<String> mExtensions; | |
| 56 | ||
| 57 | /** | |
| 58 | * Several media types have only one corresponding standard file name | |
| 59 | * extension; this constructor calls {@link MediaType#getSubtype()} to obtain | |
| 60 | * said extension. Some {@link MediaType}s have a single extension but their | |
| 61 | * assigned IANA name differs (e.g., {@code svg} maps to {@code svg+xml}) | |
| 62 | * and thus must not use this constructor. | |
| 63 | * | |
| 64 | * @param mediaType The {@link MediaType} containing only one extension. | |
| 65 | */ | |
| 66 | MediaTypeExtension( final MediaType mediaType ) { | |
| 67 | this( mediaType, of( mediaType.getSubtype() ) ); | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Constructs an association of file name extensions to a single {@link | |
| 72 | * MediaType}. | |
| 73 | * | |
| 74 | * @param mediaType The {@link MediaType} to associate with the given | |
| 75 | * file name extensions. | |
| 76 | * @param extensions The file name extensions used to lookup a corresponding | |
| 77 | * {@link MediaType}. | |
| 78 | */ | |
| 79 | MediaTypeExtension( | |
| 80 | final MediaType mediaType, final List<String> extensions ) { | |
| 81 | assert mediaType != null; | |
| 82 | assert extensions != null; | |
| 83 | assert !extensions.isEmpty(); | |
| 84 | ||
| 85 | mMediaType = mediaType; | |
| 86 | mExtensions = extensions; | |
| 87 | } | |
| 88 | ||
| 89 | /** | |
| 90 | * Returns the first file name extension in the list of file names given | |
| 91 | * at construction time. | |
| 92 | * | |
| 93 | * @return The one file name to rule them all. | |
| 94 | */ | |
| 95 | public String getExtension() { | |
| 96 | return mExtensions.get( 0 ); | |
| 97 | } | |
| 98 | ||
| 99 | /** | |
| 100 | * Returns the {@link MediaTypeExtension} that matches the given media type. | |
| 101 | * | |
| 102 | * @param mediaType The media type to find. | |
| 103 | * @return The correlated value or {@link #MEDIA_UNDEFINED} if not found. | |
| 104 | */ | |
| 105 | public static MediaTypeExtension valueFrom( final MediaType mediaType ) { | |
| 106 | for( final var type : values() ) { | |
| 107 | if( type.isMediaType( mediaType ) ) { | |
| 108 | return type; | |
| 109 | } | |
| 110 | } | |
| 111 | ||
| 112 | return MEDIA_UNDEFINED; | |
| 113 | } | |
| 114 | ||
| 115 | boolean isMediaType( final MediaType mediaType ) { | |
| 116 | return mMediaType == mediaType; | |
| 117 | } | |
| 118 | ||
| 119 | /** | |
| 120 | * Returns the {@link MediaType} associated with the given file name | |
| 121 | * extension. The extension must not contain a period. | |
| 122 | * | |
| 123 | * @param extension File name extension, case insensitive, {@code null}-safe. | |
| 124 | * @return The associated {@link MediaType} as defined by IANA. | |
| 125 | */ | |
| 126 | static MediaType getMediaType( final String extension ) { | |
| 127 | final var sanitized = sanitize( extension ); | |
| 128 | ||
| 129 | for( final var mediaType : MediaTypeExtension.values() ) { | |
| 130 | if( mediaType.isType( sanitized ) ) { | |
| 131 | return mediaType.getMediaType(); | |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | return UNDEFINED; | |
| 136 | } | |
| 137 | ||
| 138 | private boolean isType( final String sanitized ) { | |
| 139 | for( final var extension : mExtensions ) { | |
| 140 | if( extension.equalsIgnoreCase( sanitized ) ) { | |
| 141 | return true; | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | return false; | |
| 146 | } | |
| 147 | ||
| 148 | private static String sanitize( final String extension ) { | |
| 149 | return extension == null ? "" : extension.toLowerCase(); | |
| 150 | } | |
| 151 | ||
| 152 | private MediaType getMediaType() { | |
| 153 | return mMediaType; | |
| 154 | } | |
| 155 | } | |
| 1 | 156 |
| 1 | package com.keenwrite.io; | |
| 2 | ||
| 3 | import java.io.*; | |
| 4 | import java.nio.file.Path; | |
| 5 | import java.util.LinkedHashMap; | |
| 6 | import java.util.Map; | |
| 7 | ||
| 8 | import static com.keenwrite.io.MediaType.*; | |
| 9 | import static java.lang.System.arraycopy; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for associating file signatures with IANA-defined | |
| 13 | * {@link MediaType} instances. For details see: | |
| 14 | * <ul> | |
| 15 | * <li> | |
| 16 | * <a href="https://www.garykessler.net/library/file_sigs.html">Kessler's List</a> | |
| 17 | * </li> | |
| 18 | * <li> | |
| 19 | * <a href="https://en.wikipedia.org/wiki/List_of_file_signatures">Wikipedia's List</a> | |
| 20 | * </li> | |
| 21 | * <li> | |
| 22 | * <a href="https://github.com/veniware/Space-Maker/blob/master/FileSignatures.cs">Space Maker's List</a> | |
| 23 | * </li> | |
| 24 | * </ul> | |
| 25 | */ | |
| 26 | public class MediaTypeSniffer { | |
| 27 | private static final int FORMAT_LENGTH = 11; | |
| 28 | private static final int END_OF_DATA = -2; | |
| 29 | ||
| 30 | private static final Map<int[], MediaType> FORMAT = new LinkedHashMap<>(); | |
| 31 | ||
| 32 | static { | |
| 33 | //@formatter:off | |
| 34 | FORMAT.put( ints( 0x3C, 0x73, 0x76, 0x67, 0x20 ), IMAGE_SVG_XML ); | |
| 35 | FORMAT.put( ints( 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), IMAGE_PNG ); | |
| 36 | FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xE0 ), IMAGE_JPEG ); | |
| 37 | FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xEE ), IMAGE_JPEG ); | |
| 38 | FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xE1, -1, -1, 0x45, 0x78, 0x69, 0x66, 0x00 ), IMAGE_JPEG ); | |
| 39 | FORMAT.put( ints( 0x49, 0x49, 0x2A, 0x00 ), IMAGE_TIFF ); | |
| 40 | FORMAT.put( ints( 0x4D, 0x4D, 0x00, 0x2A ), IMAGE_TIFF ); | |
| 41 | FORMAT.put( ints( 0x47, 0x49, 0x46, 0x38 ), IMAGE_GIF ); | |
| 42 | FORMAT.put( ints( 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E ), APP_PDF ); | |
| 43 | FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D ), APP_EPS ); | |
| 44 | FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53 ), APP_PS ); | |
| 45 | FORMAT.put( ints( 0x38, 0x42, 0x50, 0x53, 0x00, 0x01 ), IMAGE_PHOTOSHOP ); | |
| 46 | FORMAT.put( ints( 0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), VIDEO_MNG ); | |
| 47 | FORMAT.put( ints( 0x42, 0x4D ), IMAGE_BMP ); | |
| 48 | FORMAT.put( ints( 0xFF, 0xFB, 0x30 ), AUDIO_MP3 ); | |
| 49 | FORMAT.put( ints( 0x49, 0x44, 0x33 ), AUDIO_MP3 ); | |
| 50 | FORMAT.put( ints( 0x3C, 0x21 ), TEXT_HTML ); | |
| 51 | FORMAT.put( ints( 0x3C, 0x68, 0x74, 0x6D, 0x6C ), TEXT_HTML ); | |
| 52 | FORMAT.put( ints( 0x3C, 0x68, 0x65, 0x61, 0x64 ), TEXT_HTML ); | |
| 53 | FORMAT.put( ints( 0x3C, 0x62, 0x6F, 0x64, 0x79 ), TEXT_HTML ); | |
| 54 | FORMAT.put( ints( 0x3C, 0x48, 0x54, 0x4D, 0x4C ), TEXT_HTML ); | |
| 55 | FORMAT.put( ints( 0x3C, 0x48, 0x45, 0x41, 0x44 ), TEXT_HTML ); | |
| 56 | FORMAT.put( ints( 0x3C, 0x42, 0x4F, 0x44, 0x59 ), TEXT_HTML ); | |
| 57 | FORMAT.put( ints( 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20 ), TEXT_XML ); | |
| 58 | FORMAT.put( ints( 0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78 ), TEXT_XML ); | |
| 59 | FORMAT.put( ints( 0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00 ), TEXT_XML ); | |
| 60 | FORMAT.put( ints( 0x23, 0x64, 0x65, 0x66 ), IMAGE_X_BITMAP ); | |
| 61 | FORMAT.put( ints( 0x21, 0x20, 0x58, 0x50, 0x4D, 0x32 ), IMAGE_X_PIXMAP ); | |
| 62 | FORMAT.put( ints( 0x2E, 0x73, 0x6E, 0x64 ), AUDIO_BASIC ); | |
| 63 | FORMAT.put( ints( 0x64, 0x6E, 0x73, 0x2E ), AUDIO_BASIC ); | |
| 64 | FORMAT.put( ints( 0x52, 0x49, 0x46, 0x46 ), AUDIO_WAV ); | |
| 65 | FORMAT.put( ints( 0x50, 0x4B ), APP_ZIP ); | |
| 66 | FORMAT.put( ints( 0x41, 0x43, -1, -1, -1, -1, 0x00, 0x00, 0x00, 0x00, 0x00 ), APP_ACAD ); | |
| 67 | FORMAT.put( ints( 0xCA, 0xFE, 0xBA, 0xBE ), APP_JAVA ); | |
| 68 | FORMAT.put( ints( 0xAC, 0xED ), APP_JAVA_OBJECT ); | |
| 69 | //@formatter:on | |
| 70 | } | |
| 71 | ||
| 72 | private MediaTypeSniffer() { | |
| 73 | } | |
| 74 | ||
| 75 | /** | |
| 76 | * Returns the {@link MediaType} for a given set of bytes. | |
| 77 | * | |
| 78 | * @param data Binary data to compare against the list of known formats. | |
| 79 | * @return The IANA-defined {@link MediaType}, or | |
| 80 | * {@link MediaType#UNDEFINED} if indeterminate. | |
| 81 | */ | |
| 82 | public static MediaType getMediaType( final byte[] data ) { | |
| 83 | assert data != null; | |
| 84 | ||
| 85 | final var source = new int[]{ | |
| 86 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; | |
| 87 | ||
| 88 | for( int i = 0; i < data.length; i++ ) { | |
| 89 | source[ i ] = data[ i ] & 0xFF; | |
| 90 | } | |
| 91 | ||
| 92 | for( final var key : FORMAT.keySet() ) { | |
| 93 | int i = -1; | |
| 94 | boolean matches = true; | |
| 95 | ||
| 96 | while( ++i < FORMAT_LENGTH && key[ i ] != END_OF_DATA && matches ) { | |
| 97 | matches = key[ i ] == source[ i ] || key[ i ] == -1; | |
| 98 | } | |
| 99 | ||
| 100 | if( matches ) { | |
| 101 | return FORMAT.get( key ); | |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | return UNDEFINED; | |
| 106 | } | |
| 107 | ||
| 108 | /** | |
| 109 | * Convenience method to return the probed media type for the given | |
| 110 | * {@link Path} instance by delegating to {@link #getMediaType(InputStream)}. | |
| 111 | * | |
| 112 | * @param path Path to ascertain the {@link MediaType}. | |
| 113 | * @return The IANA-defined {@link MediaType}, or | |
| 114 | * {@link MediaType#UNDEFINED} if indeterminate. | |
| 115 | * @throws IOException Could not read from the {@link SysFile}. | |
| 116 | */ | |
| 117 | public static MediaType getMediaType( final Path path ) throws IOException { | |
| 118 | return getMediaType( path.toFile() ); | |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * Convenience method to return the probed media type for the given | |
| 123 | * {@link SysFile} instance by delegating to | |
| 124 | * {@link #getMediaType(InputStream)}. | |
| 125 | * | |
| 126 | * @param file File to ascertain the {@link MediaType}. | |
| 127 | * @return The IANA-defined {@link MediaType}, or | |
| 128 | * {@link MediaType#UNDEFINED} if indeterminate. | |
| 129 | * @throws IOException Could not read from the {@link SysFile}. | |
| 130 | */ | |
| 131 | public static MediaType getMediaType( final File file ) | |
| 132 | throws IOException { | |
| 133 | try( final var fis = new FileInputStream( file ) ) { | |
| 134 | return getMediaType( fis ); | |
| 135 | } | |
| 136 | } | |
| 137 | ||
| 138 | /** | |
| 139 | * Convenience method to return the probed media type for the given | |
| 140 | * {@link BufferedInputStream} instance. <strong>This resets the stream | |
| 141 | * pointer</strong> making the call idempotent. Users of this class should | |
| 142 | * prefer to call this method when operating on streams to avoid advancing | |
| 143 | * the stream. | |
| 144 | * | |
| 145 | * @param bis Data source to ascertain the {@link MediaType}. | |
| 146 | * @return The IANA-defined {@link MediaType}, or | |
| 147 | * {@link MediaType#UNDEFINED} if indeterminate. | |
| 148 | * @throws IOException Could not read from the {@link SysFile}. | |
| 149 | */ | |
| 150 | public static MediaType getMediaType( final BufferedInputStream bis ) | |
| 151 | throws IOException { | |
| 152 | bis.mark( FORMAT_LENGTH ); | |
| 153 | final var result = getMediaType( (InputStream) bis ); | |
| 154 | bis.reset(); | |
| 155 | ||
| 156 | return result; | |
| 157 | } | |
| 158 | ||
| 159 | /** | |
| 160 | * Helper method to return the probed media type for the given | |
| 161 | * {@link InputStream} instance. The caller is responsible for closing | |
| 162 | * the stream. <strong>This advances the stream pointer.</strong> | |
| 163 | * | |
| 164 | * @param is Data source to ascertain the {@link MediaType}. | |
| 165 | * @return The IANA-defined {@link MediaType}, or | |
| 166 | * {@link MediaType#UNDEFINED} if indeterminate. | |
| 167 | * @throws IOException Could not read from the {@link InputStream}. | |
| 168 | * @see #getMediaType(BufferedInputStream) to perform a non-destructive | |
| 169 | * read. | |
| 170 | */ | |
| 171 | private static MediaType getMediaType( final InputStream is ) | |
| 172 | throws IOException { | |
| 173 | final var input = new byte[ FORMAT_LENGTH ]; | |
| 174 | final var count = is.read( input, 0, FORMAT_LENGTH ); | |
| 175 | ||
| 176 | if( count > 1 ) { | |
| 177 | final var available = new byte[ count ]; | |
| 178 | arraycopy( input, 0, available, 0, count ); | |
| 179 | return getMediaType( available ); | |
| 180 | } | |
| 181 | ||
| 182 | return UNDEFINED; | |
| 183 | } | |
| 184 | ||
| 185 | /** | |
| 186 | * Creates an array of integers from the given data, padded with {@link | |
| 187 | * #END_OF_DATA} values up to {@link #FORMAT_LENGTH}. | |
| 188 | * | |
| 189 | * @param data The input byte values to pad. | |
| 190 | * @return The data with padding. | |
| 191 | */ | |
| 192 | private static int[] ints( final int... data ) { | |
| 193 | final var magic = new int[ FORMAT_LENGTH ]; | |
| 194 | int i = -1; | |
| 195 | while( ++i < data.length ) { | |
| 196 | magic[ i ] = data[ i ]; | |
| 197 | } | |
| 198 | ||
| 199 | while( i < FORMAT_LENGTH ) { | |
| 200 | magic[ i++ ] = END_OF_DATA; | |
| 201 | } | |
| 202 | ||
| 203 | return magic; | |
| 204 | } | |
| 205 | } | |
| 1 | 206 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | import java.nio.file.WatchEvent; | |
| 5 | import java.nio.file.WatchKey; | |
| 6 | import java.nio.file.WatchService; | |
| 7 | import java.nio.file.Watchable; | |
| 8 | import java.util.List; | |
| 9 | import java.util.concurrent.TimeUnit; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for polling the file system to see whether a file has been | |
| 13 | * updated. This is instantiated when an instance of {@link WatchService} | |
| 14 | * cannot be created using the Java API. | |
| 15 | * <p> | |
| 16 | * This is a skeleton class to avoid {@code null} references. In theory, | |
| 17 | * it should never get instantiated. If the application is run on a system | |
| 18 | * that does not support file system events, this should eliminate NPEs. | |
| 19 | * </p> | |
| 20 | */ | |
| 21 | public class PollingWatchService implements WatchService { | |
| 22 | private final WatchKey EMPTY_KEY = new WatchKey() { | |
| 23 | private final Watchable WATCHABLE = new Watchable() { | |
| 24 | @Override | |
| 25 | public WatchKey register( | |
| 26 | final WatchService watcher, | |
| 27 | final WatchEvent.Kind<?>[] events, | |
| 28 | final WatchEvent.Modifier... modifiers ) { | |
| 29 | return EMPTY_KEY; | |
| 30 | } | |
| 31 | ||
| 32 | @Override | |
| 33 | public WatchKey register( | |
| 34 | final WatchService watcher, final WatchEvent.Kind<?>... events ) { | |
| 35 | return EMPTY_KEY; | |
| 36 | } | |
| 37 | }; | |
| 38 | ||
| 39 | @Override | |
| 40 | public boolean isValid() { | |
| 41 | return false; | |
| 42 | } | |
| 43 | ||
| 44 | @Override | |
| 45 | public List<WatchEvent<?>> pollEvents() { | |
| 46 | return List.of(); | |
| 47 | } | |
| 48 | ||
| 49 | @Override | |
| 50 | public boolean reset() { | |
| 51 | return false; | |
| 52 | } | |
| 53 | ||
| 54 | @Override | |
| 55 | public void cancel() { | |
| 56 | } | |
| 57 | ||
| 58 | @Override | |
| 59 | public Watchable watchable() { | |
| 60 | return WATCHABLE; | |
| 61 | } | |
| 62 | }; | |
| 63 | ||
| 64 | @Override | |
| 65 | public void close() { | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | public WatchKey poll() { | |
| 70 | return EMPTY_KEY; | |
| 71 | } | |
| 72 | ||
| 73 | @Override | |
| 74 | public WatchKey poll( final long timeout, final TimeUnit unit ) { | |
| 75 | return EMPTY_KEY; | |
| 76 | } | |
| 77 | ||
| 78 | @Override | |
| 79 | public WatchKey take() { | |
| 80 | return EMPTY_KEY; | |
| 81 | } | |
| 82 | } | |
| 1 | 83 |
| 1 | package com.keenwrite.io; | |
| 2 | ||
| 3 | import java.nio.file.Path; | |
| 4 | import java.nio.file.Paths; | |
| 5 | import java.util.stream.Stream; | |
| 6 | ||
| 7 | import static java.lang.System.getenv; | |
| 8 | import static java.nio.file.Files.isExecutable; | |
| 9 | import static java.util.regex.Pattern.quote; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for file-related functionality. | |
| 13 | */ | |
| 14 | public class SysFile extends java.io.File { | |
| 15 | /** | |
| 16 | * For finding executable programs. | |
| 17 | */ | |
| 18 | private static final String[] EXTENSIONS = new String[] | |
| 19 | {"", ".com", ".exe", ".bat", ".cmd"}; | |
| 20 | ||
| 21 | /** | |
| 22 | * Creates a new instance for a given file name. | |
| 23 | * | |
| 24 | * @param pathname File name to represent for subsequent operations. | |
| 25 | */ | |
| 26 | public SysFile( final String pathname ) { | |
| 27 | super( pathname ); | |
| 28 | } | |
| 29 | ||
| 30 | /** | |
| 31 | * For a file name that represents an executable (without an extension) | |
| 32 | * file, this determines whether the executable is found in the PATH | |
| 33 | * environment variable. This will search the PATH each time the method | |
| 34 | * is invoked, triggering a full directory scan for all paths listed in | |
| 35 | * the environment variable. The result is not cached, so avoid calling | |
| 36 | * this in a critical loop. | |
| 37 | * | |
| 38 | * @return {@code true} when the given file name references an executable | |
| 39 | * file located in the PATH environment variable. | |
| 40 | */ | |
| 41 | public boolean canRun() { | |
| 42 | final var exe = getName(); | |
| 43 | final var paths = getenv( "PATH" ).split( quote( pathSeparator ) ); | |
| 44 | return Stream.of( paths ).map( Paths::get ).anyMatch( | |
| 45 | path -> { | |
| 46 | final var p = path.resolve( exe ); | |
| 47 | ||
| 48 | for( final var extension : EXTENSIONS ) { | |
| 49 | if( isExecutable( Path.of( p.toString() + extension ) ) ) { | |
| 50 | return true; | |
| 51 | } | |
| 52 | } | |
| 53 | ||
| 54 | return false; | |
| 55 | } | |
| 56 | ); | |
| 57 | } | |
| 58 | } | |
| 1 | 59 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.predicates; | |
| 3 | ||
| 4 | import java.io.File; | |
| 5 | import java.util.Collection; | |
| 6 | import java.util.function.Predicate; | |
| 7 | ||
| 8 | import static java.lang.String.join; | |
| 9 | import static java.nio.file.FileSystems.getDefault; | |
| 10 | ||
| 11 | /** | |
| 12 | * Provides a number of simple {@link Predicate} instances for various types | |
| 13 | * of string comparisons, including basic strings and file name strings. | |
| 14 | */ | |
| 15 | public final class PredicateFactory { | |
| 16 | /** | |
| 17 | * Creates an instance of {@link Predicate} that matches a globbed file | |
| 18 | * name pattern. | |
| 19 | * | |
| 20 | * @param pattern The file name pattern to match. | |
| 21 | * @return A {@link Predicate} that can answer whether a given file name | |
| 22 | * matches the given glob pattern. | |
| 23 | */ | |
| 24 | public static Predicate<File> createFileTypePredicate( | |
| 25 | final String pattern ) { | |
| 26 | final var matcher = getDefault().getPathMatcher( | |
| 27 | "glob:**{" + pattern + "}" | |
| 28 | ); | |
| 29 | ||
| 30 | return file -> matcher.matches( file.toPath() ); | |
| 31 | } | |
| 32 | ||
| 33 | /** | |
| 34 | * Creates an instance of {@link Predicate} that matches any file name from | |
| 35 | * a {@link Collection} of file name patterns. The given patterns are joined | |
| 36 | * with commas into a single comma-separated list. | |
| 37 | * | |
| 38 | * @param patterns The file name patterns to be matched. | |
| 39 | * @return A {@link Predicate} that can answer whether a given file name | |
| 40 | * matches the given glob patterns. | |
| 41 | */ | |
| 42 | public static Predicate<File> createFileTypePredicate( | |
| 43 | final Collection<String> patterns ) { | |
| 44 | return createFileTypePredicate( join( ",", patterns ) ); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Creates an instance of {@link Predicate} that compares whether the given | |
| 49 | * {@code reference} string is contained by the comparator. Comparison is | |
| 50 | * case-insensitive. The test will also pass if the comparate is empty. | |
| 51 | * | |
| 52 | * @param comparator The string to check as being contained. | |
| 53 | * @return A {@link Predicate} that can answer whether the given string | |
| 54 | * is contained within the comparator, or the comparate is empty. | |
| 55 | */ | |
| 56 | public static Predicate<String> createStringContainsPredicate( | |
| 57 | final String comparator ) { | |
| 58 | return comparate -> comparate.isEmpty() || | |
| 59 | comparate.toLowerCase().contains( comparator.toLowerCase() ); | |
| 60 | } | |
| 1 | 61 | |
| 62 | /** | |
| 63 | * Creates an instance of {@link Predicate} that compares whether the given | |
| 64 | * {@code reference} string is starts with the comparator. Comparison is | |
| 65 | * case-insensitive. | |
| 66 | * | |
| 67 | * @param comparator The string to check as being contained. | |
| 68 | * @return A {@link Predicate} that can answer whether the given string | |
| 69 | * is contained within the comparator. | |
| 70 | */ | |
| 71 | public static Predicate<String> createStringStartsPredicate( | |
| 72 | final String comparator ) { | |
| 73 | return comparate -> | |
| 74 | comparate.toLowerCase().startsWith( comparator.toLowerCase() ); | |
| 75 | } | |
| 76 | } |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import javafx.beans.property.SimpleObjectProperty; | |
| 5 | ||
| 6 | import java.io.File; | |
| 7 | ||
| 8 | public final class FileProperty extends SimpleObjectProperty<File> { | |
| 9 | public FileProperty( final File file ) { | |
| 10 | super( file ); | |
| 11 | } | |
| 12 | ||
| 13 | public void setValue( final String filename ) { | |
| 14 | setValue( new File( filename ) ); | |
| 15 | } | |
| 16 | } | |
| 1 | 17 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | /** | |
| 5 | * Responsible for creating a type hierarchy of preference storage keys. | |
| 6 | */ | |
| 7 | public class Key { | |
| 8 | private final Key mParent; | |
| 9 | private final String mName; | |
| 10 | ||
| 11 | private Key( final Key parent, final String name ) { | |
| 12 | mParent = parent; | |
| 13 | mName = name; | |
| 14 | } | |
| 15 | ||
| 16 | /** | |
| 17 | * Returns a new key with no parent. | |
| 18 | * | |
| 19 | * @param name The key name, never {@code null}. | |
| 20 | * @return The new {@link Key} instance with a name but no parent. | |
| 21 | */ | |
| 22 | public static Key key( final String name ) { | |
| 23 | assert name != null && !name.isEmpty(); | |
| 24 | return key( null, name ); | |
| 25 | } | |
| 26 | ||
| 27 | /** | |
| 28 | * Returns a new key with a given parent. | |
| 29 | * | |
| 30 | * @param parent The parent of this {@link Key}, or {@code null} if this is | |
| 31 | * the topmost key in the chain. | |
| 32 | * @param name The key name, never {@code null}. | |
| 33 | * @return The new {@link Key} instance with a name and parent. | |
| 34 | */ | |
| 35 | public static Key key( final Key parent, final String name ) { | |
| 36 | assert name != null && !name.isEmpty(); | |
| 37 | return new Key( parent, name ); | |
| 38 | } | |
| 39 | ||
| 40 | private Key parent() { | |
| 41 | return mParent; | |
| 42 | } | |
| 43 | ||
| 44 | private String name() { | |
| 45 | return mName; | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * Returns a dot-separated path representing the key's name. | |
| 50 | * | |
| 51 | * @return The recursively derived dot-separated key name. | |
| 52 | */ | |
| 53 | @Override | |
| 54 | public String toString() { | |
| 55 | return parent() == null ? name() : parent().toString() + '.' + name(); | |
| 56 | } | |
| 57 | } | |
| 1 | 58 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import javafx.beans.property.SimpleObjectProperty; | |
| 5 | import javafx.collections.ObservableList; | |
| 6 | ||
| 7 | import java.util.LinkedHashMap; | |
| 8 | import java.util.Locale; | |
| 9 | import java.util.Map; | |
| 10 | import java.util.Objects; | |
| 11 | ||
| 12 | import static com.keenwrite.constants.Constants.LOCALE_DEFAULT; | |
| 13 | import static com.keenwrite.preferences.Workspace.listProperty; | |
| 14 | import static java.util.Locale.forLanguageTag; | |
| 15 | ||
| 16 | /** | |
| 17 | * Responsible for providing a list of locales from which the user may pick. | |
| 18 | */ | |
| 19 | public final class LocaleProperty extends SimpleObjectProperty<String> { | |
| 20 | ||
| 21 | /** | |
| 22 | * Lists the locales having fonts that are supported by the application. | |
| 23 | * When the Markdown and preview CSS files are loaded, a general file is | |
| 24 | * first loaded, then a specific file is loaded according to the locale. | |
| 25 | * The specific file overrides font families so that different languages | |
| 26 | * may be presented. | |
| 27 | * <p> | |
| 28 | * Using an instance of {@link LinkedHashMap} preserves display order. | |
| 29 | * </p> | |
| 30 | * <p> | |
| 31 | * See | |
| 32 | * <a href="https://www.oracle.com/java/technologies/javase/jdk12locales.html">JDK 12 Locales</a> | |
| 33 | * for details. | |
| 34 | * </p> | |
| 35 | */ | |
| 36 | private static final Map<String, Locale> sLocales = new LinkedHashMap<>(); | |
| 37 | ||
| 38 | static { | |
| 39 | final String[] tags = { | |
| 40 | "en-Latn-AU", | |
| 41 | "en-Latn-CA", | |
| 42 | "en-Latn-GB", | |
| 43 | "en-Latn-NZ", | |
| 44 | "en-Latn-US", | |
| 45 | "en-Latn-ZA", | |
| 46 | "ja-Jpan-JP", | |
| 47 | "ko-Kore-KR", | |
| 48 | "zh-Hans-CN", | |
| 49 | "zh-Hans-SG", | |
| 50 | "zh-Hant-HK", | |
| 51 | "zh-Hant-TW", | |
| 52 | }; | |
| 53 | ||
| 54 | for( final var tag : tags ) { | |
| 55 | final var locale = forLanguageTag( tag ); | |
| 56 | sLocales.put( locale.getDisplayName(), locale ); | |
| 57 | } | |
| 58 | } | |
| 59 | ||
| 60 | public LocaleProperty( final Locale locale ) { | |
| 61 | super( sanitize( locale ).getDisplayName() ); | |
| 62 | } | |
| 63 | ||
| 64 | public static String parseLocale( final String languageTag ) { | |
| 65 | final var locale = forLanguageTag( languageTag ); | |
| 66 | final var key = getKey( sLocales, locale ); | |
| 67 | return key == null ? LOCALE_DEFAULT.getDisplayName() : key; | |
| 68 | } | |
| 69 | ||
| 70 | public static String toLanguageTag( final String displayName ) { | |
| 71 | return sLocales.getOrDefault( displayName, LOCALE_DEFAULT ).toLanguageTag(); | |
| 72 | } | |
| 73 | ||
| 74 | public Locale toLocale() { | |
| 75 | return sLocales.getOrDefault( getValue(), LOCALE_DEFAULT ); | |
| 76 | } | |
| 77 | ||
| 78 | private static Locale sanitize( final Locale locale ) { | |
| 79 | // If the language is "und"efined then use the default locale. | |
| 80 | return locale == null || "und".equalsIgnoreCase( locale.toLanguageTag() ) | |
| 81 | ? LOCALE_DEFAULT | |
| 82 | : locale; | |
| 83 | } | |
| 84 | ||
| 85 | public static ObservableList<String> localeListProperty() { | |
| 86 | return listProperty( sLocales.keySet() ); | |
| 87 | } | |
| 88 | ||
| 89 | /** | |
| 90 | * Performs an O(n) search through the given map to find the key that is | |
| 91 | * mapped to the given value. A bi-directional map would be faster, but | |
| 92 | * also introduces additional dependencies. This doesn't need to be fast | |
| 93 | * because it happens once, at start up, and there aren't a lot of values. | |
| 94 | * | |
| 95 | * @param map The map containing a key to find based on a value. | |
| 96 | * @param value The value to find within the map. | |
| 97 | * @param <K> The type of key associated with a value. | |
| 98 | * @param <V> The type of value associated with a key. | |
| 99 | * @return The key that corresponds to the given value, or {@code null} if | |
| 100 | * the key is not found. | |
| 101 | */ | |
| 102 | @SuppressWarnings( "SameParameterValue" ) | |
| 103 | private static <K, V> K getKey( final Map<K, V> map, final V value ) { | |
| 104 | for( final var entry : map.entrySet() ) { | |
| 105 | if( Objects.equals( value, entry.getValue() ) ) { | |
| 106 | return entry.getKey(); | |
| 107 | } | |
| 108 | } | |
| 109 | ||
| 110 | return null; | |
| 111 | } | |
| 112 | } | |
| 1 | 113 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import java.util.Collections; | |
| 5 | import java.util.HashMap; | |
| 6 | import java.util.Locale; | |
| 7 | import java.util.Map; | |
| 8 | ||
| 9 | import static java.util.Arrays.asList; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for adding an ISO 15924 alpha-4 script code to {@link Locale} | |
| 13 | * instances. This allows all {@link Locale} objects to produce language tags | |
| 14 | * using the same format. | |
| 15 | */ | |
| 16 | public final class LocaleScripts { | |
| 17 | /** | |
| 18 | * ISO 15924 alpha-4 script code to represent Latin scripts. | |
| 19 | */ | |
| 20 | private static final String SCRIPT_LATIN = "Latn"; | |
| 21 | ||
| 22 | /** | |
| 23 | * This value is returned when a script hasn't been mapped for an instance of | |
| 24 | * {@link Locale}. | |
| 25 | */ | |
| 26 | private static final Map<String, String> SCRIPT_DEFAULT = m( SCRIPT_LATIN ); | |
| 27 | ||
| 28 | private static final Map<String, Map<String, String>> SCRIPTS = | |
| 29 | new HashMap<>(); | |
| 30 | ||
| 31 | static { | |
| 32 | put( "en", m( "Latn" ) ); | |
| 33 | put( "jp", m( "Jpan" ) ); | |
| 34 | put( "ko", m( "Kore" ) ); | |
| 35 | put( "zh", m( "Hant" ), m( "Hans", "CN", "MN", "MY", "SG" ) ); | |
| 36 | } | |
| 37 | ||
| 38 | /** | |
| 39 | * Adds a script to a given {@link Locale} object. If the given {@link Locale} | |
| 40 | * already has a script, then it is returned unchanged. | |
| 41 | * | |
| 42 | * @param locale The {@link Locale} to update with its associated script. | |
| 43 | * @return The given {@link Locale} with a script included. | |
| 44 | */ | |
| 45 | public static Locale withScript( Locale locale ) { | |
| 46 | assert locale != null; | |
| 47 | ||
| 48 | final var script = locale.getScript(); | |
| 49 | ||
| 50 | if( script == null || script.isBlank() ) { | |
| 51 | final var builder = new Locale.Builder(); | |
| 52 | builder.setLocale( locale ); | |
| 53 | builder.setScript( getScript( locale ) ); | |
| 54 | locale = builder.build(); | |
| 55 | } | |
| 56 | ||
| 57 | return locale; | |
| 58 | } | |
| 59 | ||
| 60 | @SafeVarargs | |
| 61 | private static void put( | |
| 62 | final String language, final Map<String, String>... scripts ) { | |
| 63 | final var merged = new HashMap<String, String>(); | |
| 64 | asList( scripts ).forEach( merged::putAll ); | |
| 65 | SCRIPTS.put( language, merged ); | |
| 66 | } | |
| 67 | ||
| 68 | /** | |
| 69 | * Returns the ISO 15924 alpha-4 script code for the given {@link Locale}. | |
| 70 | * | |
| 71 | * @param locale Language and country are used to find the script code. | |
| 72 | * @return The ISO code for the given locale, or {@link #SCRIPT_LATIN} if | |
| 73 | * no code has been mapped yet. | |
| 74 | */ | |
| 75 | private static String getScript( final Locale locale ) { | |
| 76 | return SCRIPTS.getOrDefault( locale.getLanguage(), SCRIPT_DEFAULT ) | |
| 77 | .getOrDefault( locale.getCountry(), SCRIPT_LATIN ); | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Helper method to instantiate a new {@link Map} having all keys referencing | |
| 82 | * the same value. | |
| 83 | * | |
| 84 | * @param v The value to associate with each key. | |
| 85 | * @param k The keys to associate with the given value. | |
| 86 | * @return A new {@link Map} with all keys referencing the same value. | |
| 87 | */ | |
| 88 | private static Map<String, String> m( final String v, final String... k ) { | |
| 89 | final var map = new HashMap<String, String>(); | |
| 90 | asList( k ).forEach( ( key ) -> map.put( key, v ) ); | |
| 91 | return Collections.unmodifiableMap( map ); | |
| 92 | } | |
| 93 | ||
| 94 | /** | |
| 95 | * Helper method to instantiate a new {@link Map} having an empty key | |
| 96 | * referencing the given value. This provides a default value so that | |
| 97 | * an unmapped country code can return a valid script code. | |
| 98 | * | |
| 99 | * @param v The value to associate with an empty key. | |
| 100 | * @return A new {@link Map} with the empty key referencing the given value. | |
| 101 | */ | |
| 102 | private static Map<String, String> m( final String v ) { | |
| 103 | return m( v, "" ); | |
| 104 | } | |
| 105 | } | |
| 1 | 106 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import com.dlsc.formsfx.model.structure.StringField; | |
| 5 | import com.dlsc.preferencesfx.PreferencesFx; | |
| 6 | import com.dlsc.preferencesfx.PreferencesFxEvent; | |
| 7 | import com.dlsc.preferencesfx.model.Category; | |
| 8 | import com.dlsc.preferencesfx.model.Group; | |
| 9 | import com.dlsc.preferencesfx.model.Setting; | |
| 10 | import com.dlsc.preferencesfx.util.StorageHandler; | |
| 11 | import com.dlsc.preferencesfx.view.NavigationView; | |
| 12 | import javafx.beans.property.BooleanProperty; | |
| 13 | import javafx.beans.property.DoubleProperty; | |
| 14 | import javafx.beans.property.ObjectProperty; | |
| 15 | import javafx.beans.property.StringProperty; | |
| 16 | import javafx.event.EventHandler; | |
| 17 | import javafx.scene.Node; | |
| 18 | import javafx.scene.control.Button; | |
| 19 | import javafx.scene.control.DialogPane; | |
| 20 | import javafx.scene.control.Label; | |
| 21 | import org.controlsfx.control.MasterDetailPane; | |
| 22 | ||
| 23 | import java.io.File; | |
| 24 | ||
| 25 | import static com.dlsc.formsfx.model.structure.Field.ofStringType; | |
| 26 | import static com.dlsc.preferencesfx.PreferencesFxEvent.EVENT_PREFERENCES_SAVED; | |
| 27 | import static com.keenwrite.Messages.get; | |
| 28 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG; | |
| 29 | import static com.keenwrite.preferences.LocaleProperty.localeListProperty; | |
| 30 | import static com.keenwrite.preferences.SkinProperty.skinListProperty; | |
| 31 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 32 | import static javafx.scene.control.ButtonType.CANCEL; | |
| 33 | import static javafx.scene.control.ButtonType.OK; | |
| 34 | ||
| 35 | /** | |
| 36 | * Provides the ability for users to configure their preferences. This links | |
| 37 | * the {@link Workspace} model with the {@link PreferencesFx} view, in MVC. | |
| 38 | */ | |
| 39 | @SuppressWarnings( "SameParameterValue" ) | |
| 40 | public final class PreferencesController { | |
| 41 | ||
| 42 | private final Workspace mWorkspace; | |
| 43 | private final PreferencesFx mPreferencesFx; | |
| 44 | ||
| 45 | public PreferencesController( final Workspace workspace ) { | |
| 46 | mWorkspace = workspace; | |
| 47 | ||
| 48 | // All properties must be initialized before creating the dialog. | |
| 49 | mPreferencesFx = createPreferencesFx(); | |
| 50 | ||
| 51 | initKeyEventHandler( mPreferencesFx ); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Display the user preferences settings dialog (non-modal). | |
| 56 | */ | |
| 57 | public void show() { | |
| 58 | getPreferencesFx().show( false ); | |
| 59 | } | |
| 60 | ||
| 61 | /** | |
| 62 | * Call to persist the settings. Strictly speaking, this could watch on | |
| 63 | * all values for external changes then save automatically. | |
| 64 | */ | |
| 65 | public void save() { | |
| 66 | getPreferencesFx().saveSettings(); | |
| 67 | } | |
| 68 | ||
| 69 | /** | |
| 70 | * Delegates to the {@link PreferencesFx} event handler for monitoring | |
| 71 | * save events. | |
| 72 | * | |
| 73 | * @param eventHandler The handler to call when the preferences are saved. | |
| 74 | */ | |
| 75 | public void addSaveEventHandler( | |
| 76 | final EventHandler<? super PreferencesFxEvent> eventHandler ) { | |
| 77 | getPreferencesFx().addEventHandler( EVENT_PREFERENCES_SAVED, eventHandler ); | |
| 78 | } | |
| 79 | ||
| 80 | private StringField createFontNameField( | |
| 81 | final StringProperty fontName, final DoubleProperty fontSize ) { | |
| 82 | final var control = new SimpleFontControl( "Change" ); | |
| 83 | control.fontSizeProperty().addListener( ( c, o, n ) -> { | |
| 84 | if( n != null ) { | |
| 85 | fontSize.set( n.doubleValue() ); | |
| 86 | } | |
| 87 | } ); | |
| 88 | return ofStringType( fontName ).render( control ); | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Creates the preferences dialog based using {@link XmlStorageHandler} and | |
| 93 | * numerous {@link Category} objects. | |
| 94 | * | |
| 95 | * @return A component for editing preferences. | |
| 96 | * @throws RuntimeException Could not construct the {@link PreferencesFx} | |
| 97 | * object (e.g., illegal access permissions, | |
| 98 | * unmapped XML resource). | |
| 99 | */ | |
| 100 | private PreferencesFx createPreferencesFx() { | |
| 101 | return PreferencesFx.of( createStorageHandler(), createCategories() ) | |
| 102 | .instantPersistent( false ) | |
| 103 | .dialogIcon( ICON_DIALOG ); | |
| 104 | } | |
| 105 | ||
| 106 | private StorageHandler createStorageHandler() { | |
| 107 | return new XmlStorageHandler(); | |
| 108 | } | |
| 109 | ||
| 110 | private Category[] createCategories() { | |
| 111 | return new Category[]{ | |
| 112 | Category.of( | |
| 113 | get( KEY_DOC ), | |
| 114 | Group.of( | |
| 115 | get( KEY_DOC_TITLE ), | |
| 116 | Setting.of( label( KEY_DOC_TITLE ) ), | |
| 117 | Setting.of( title( KEY_DOC_TITLE ), | |
| 118 | stringProperty( KEY_DOC_TITLE ) ) | |
| 119 | ), | |
| 120 | Group.of( | |
| 121 | get( KEY_DOC_AUTHOR ), | |
| 122 | Setting.of( label( KEY_DOC_AUTHOR ) ), | |
| 123 | Setting.of( title( KEY_DOC_AUTHOR ), | |
| 124 | stringProperty( KEY_DOC_AUTHOR ) ) | |
| 125 | ), | |
| 126 | Group.of( | |
| 127 | get( KEY_DOC_BYLINE ), | |
| 128 | Setting.of( label( KEY_DOC_BYLINE ) ), | |
| 129 | Setting.of( title( KEY_DOC_BYLINE ), | |
| 130 | stringProperty( KEY_DOC_BYLINE ) ) | |
| 131 | ), | |
| 132 | Group.of( | |
| 133 | get( KEY_DOC_ADDRESS ), | |
| 134 | Setting.of( label( KEY_DOC_ADDRESS ) ), | |
| 135 | createMultilineSetting( "Address", KEY_DOC_ADDRESS ) | |
| 136 | ), | |
| 137 | Group.of( | |
| 138 | get( KEY_DOC_PHONE ), | |
| 139 | Setting.of( label( KEY_DOC_PHONE ) ), | |
| 140 | Setting.of( title( KEY_DOC_PHONE ), | |
| 141 | stringProperty( KEY_DOC_PHONE ) ) | |
| 142 | ), | |
| 143 | Group.of( | |
| 144 | get( KEY_DOC_EMAIL ), | |
| 145 | Setting.of( label( KEY_DOC_EMAIL ) ), | |
| 146 | Setting.of( title( KEY_DOC_EMAIL ), | |
| 147 | stringProperty( KEY_DOC_EMAIL ) ) | |
| 148 | ), | |
| 149 | Group.of( | |
| 150 | get( KEY_DOC_KEYWORDS ), | |
| 151 | Setting.of( label( KEY_DOC_KEYWORDS ) ), | |
| 152 | Setting.of( title( KEY_DOC_KEYWORDS ), | |
| 153 | stringProperty( KEY_DOC_KEYWORDS ) ) | |
| 154 | ), | |
| 155 | Group.of( | |
| 156 | get( KEY_DOC_COPYRIGHT ), | |
| 157 | Setting.of( label( KEY_DOC_COPYRIGHT ) ), | |
| 158 | Setting.of( title( KEY_DOC_COPYRIGHT ), | |
| 159 | stringProperty( KEY_DOC_COPYRIGHT ) ) | |
| 160 | ), | |
| 161 | Group.of( | |
| 162 | get( KEY_DOC_DATE ), | |
| 163 | Setting.of( label( KEY_DOC_DATE ) ), | |
| 164 | Setting.of( title( KEY_DOC_DATE ), | |
| 165 | stringProperty( KEY_DOC_DATE ) ) | |
| 166 | ) | |
| 167 | ), | |
| 168 | Category.of( | |
| 169 | get( KEY_TYPESET ), | |
| 170 | Group.of( | |
| 171 | get( KEY_TYPESET_CONTEXT ), | |
| 172 | Setting.of( label( KEY_TYPESET_CONTEXT_THEMES_PATH ) ), | |
| 173 | Setting.of( title( KEY_TYPESET_CONTEXT_THEMES_PATH ), | |
| 174 | fileProperty( KEY_TYPESET_CONTEXT_THEMES_PATH ), true ), | |
| 175 | Setting.of( label( KEY_TYPESET_CONTEXT_CLEAN ) ), | |
| 176 | Setting.of( title( KEY_TYPESET_CONTEXT_CLEAN ), | |
| 177 | booleanProperty( KEY_TYPESET_CONTEXT_CLEAN ) ) | |
| 178 | ), | |
| 179 | Group.of( | |
| 180 | get( KEY_TYPESET_TYPOGRAPHY ), | |
| 181 | Setting.of( label( KEY_TYPESET_TYPOGRAPHY_QUOTES ) ), | |
| 182 | Setting.of( title( KEY_TYPESET_TYPOGRAPHY_QUOTES ), | |
| 183 | booleanProperty( KEY_TYPESET_TYPOGRAPHY_QUOTES ) ) | |
| 184 | ) | |
| 185 | ), | |
| 186 | Category.of( | |
| 187 | get( KEY_R ), | |
| 188 | Group.of( | |
| 189 | get( KEY_R_DIR ), | |
| 190 | Setting.of( label( KEY_R_DIR, | |
| 191 | stringProperty( KEY_DEF_DELIM_BEGAN ).get(), | |
| 192 | stringProperty( KEY_DEF_DELIM_ENDED ).get() ) ), | |
| 193 | Setting.of( title( KEY_R_DIR ), | |
| 194 | fileProperty( KEY_R_DIR ), true ) | |
| 195 | ), | |
| 196 | Group.of( | |
| 197 | get( KEY_R_SCRIPT ), | |
| 198 | Setting.of( label( KEY_R_SCRIPT ) ), | |
| 199 | createMultilineSetting( "Script", KEY_R_SCRIPT ) | |
| 200 | ), | |
| 201 | Group.of( | |
| 202 | get( KEY_R_DELIM_BEGAN ), | |
| 203 | Setting.of( label( KEY_R_DELIM_BEGAN ) ), | |
| 204 | Setting.of( title( KEY_R_DELIM_BEGAN ), | |
| 205 | stringProperty( KEY_R_DELIM_BEGAN ) ) | |
| 206 | ), | |
| 207 | Group.of( | |
| 208 | get( KEY_R_DELIM_ENDED ), | |
| 209 | Setting.of( label( KEY_R_DELIM_ENDED ) ), | |
| 210 | Setting.of( title( KEY_R_DELIM_ENDED ), | |
| 211 | stringProperty( KEY_R_DELIM_ENDED ) ) | |
| 212 | ) | |
| 213 | ), | |
| 214 | Category.of( | |
| 215 | get( KEY_IMAGES ), | |
| 216 | Group.of( | |
| 217 | get( KEY_IMAGES_DIR ), | |
| 218 | Setting.of( label( KEY_IMAGES_DIR ) ), | |
| 219 | Setting.of( title( KEY_IMAGES_DIR ), | |
| 220 | fileProperty( KEY_IMAGES_DIR ), true ) | |
| 221 | ), | |
| 222 | Group.of( | |
| 223 | get( KEY_IMAGES_ORDER ), | |
| 224 | Setting.of( label( KEY_IMAGES_ORDER ) ), | |
| 225 | Setting.of( title( KEY_IMAGES_ORDER ), | |
| 226 | stringProperty( KEY_IMAGES_ORDER ) ) | |
| 227 | ), | |
| 228 | Group.of( | |
| 229 | get( KEY_IMAGES_RESIZE ), | |
| 230 | Setting.of( label( KEY_IMAGES_RESIZE ) ), | |
| 231 | Setting.of( title( KEY_IMAGES_RESIZE ), | |
| 232 | booleanProperty( KEY_IMAGES_RESIZE ) ) | |
| 233 | ), | |
| 234 | Group.of( | |
| 235 | get( KEY_IMAGES_SERVER ), | |
| 236 | Setting.of( label( KEY_IMAGES_SERVER ) ), | |
| 237 | Setting.of( title( KEY_IMAGES_SERVER ), | |
| 238 | stringProperty( KEY_IMAGES_SERVER ) ) | |
| 239 | ) | |
| 240 | ), | |
| 241 | Category.of( | |
| 242 | get( KEY_DEF ), | |
| 243 | Group.of( | |
| 244 | get( KEY_DEF_PATH ), | |
| 245 | Setting.of( label( KEY_DEF_PATH ) ), | |
| 246 | Setting.of( title( KEY_DEF_PATH ), | |
| 247 | fileProperty( KEY_DEF_PATH ), false ) | |
| 248 | ), | |
| 249 | Group.of( | |
| 250 | get( KEY_DEF_DELIM_BEGAN ), | |
| 251 | Setting.of( label( KEY_DEF_DELIM_BEGAN ) ), | |
| 252 | Setting.of( title( KEY_DEF_DELIM_BEGAN ), | |
| 253 | stringProperty( KEY_DEF_DELIM_BEGAN ) ) | |
| 254 | ), | |
| 255 | Group.of( | |
| 256 | get( KEY_DEF_DELIM_ENDED ), | |
| 257 | Setting.of( label( KEY_DEF_DELIM_ENDED ) ), | |
| 258 | Setting.of( title( KEY_DEF_DELIM_ENDED ), | |
| 259 | stringProperty( KEY_DEF_DELIM_ENDED ) ) | |
| 260 | ) | |
| 261 | ), | |
| 262 | Category.of( | |
| 263 | get( KEY_UI_FONT ), | |
| 264 | Group.of( | |
| 265 | get( KEY_UI_FONT_EDITOR ), | |
| 266 | Setting.of( label( KEY_UI_FONT_EDITOR_NAME ) ), | |
| 267 | Setting.of( title( KEY_UI_FONT_EDITOR_NAME ), | |
| 268 | createFontNameField( | |
| 269 | stringProperty( KEY_UI_FONT_EDITOR_NAME ), | |
| 270 | doubleProperty( KEY_UI_FONT_EDITOR_SIZE ) ), | |
| 271 | stringProperty( KEY_UI_FONT_EDITOR_NAME ) ), | |
| 272 | Setting.of( label( KEY_UI_FONT_EDITOR_SIZE ) ), | |
| 273 | Setting.of( title( KEY_UI_FONT_EDITOR_SIZE ), | |
| 274 | doubleProperty( KEY_UI_FONT_EDITOR_SIZE ) ) | |
| 275 | ), | |
| 276 | Group.of( | |
| 277 | get( KEY_UI_FONT_PREVIEW ), | |
| 278 | Setting.of( label( KEY_UI_FONT_PREVIEW_NAME ) ), | |
| 279 | Setting.of( title( KEY_UI_FONT_PREVIEW_NAME ), | |
| 280 | createFontNameField( | |
| 281 | stringProperty( KEY_UI_FONT_PREVIEW_NAME ), | |
| 282 | doubleProperty( KEY_UI_FONT_PREVIEW_SIZE ) ), | |
| 283 | stringProperty( KEY_UI_FONT_PREVIEW_NAME ) ), | |
| 284 | Setting.of( label( KEY_UI_FONT_PREVIEW_SIZE ) ), | |
| 285 | Setting.of( title( KEY_UI_FONT_PREVIEW_SIZE ), | |
| 286 | doubleProperty( KEY_UI_FONT_PREVIEW_SIZE ) ), | |
| 287 | Setting.of( label( KEY_UI_FONT_PREVIEW_MONO_NAME ) ), | |
| 288 | Setting.of( title( KEY_UI_FONT_PREVIEW_MONO_NAME ), | |
| 289 | createFontNameField( | |
| 290 | stringProperty( KEY_UI_FONT_PREVIEW_MONO_NAME ), | |
| 291 | doubleProperty( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ), | |
| 292 | stringProperty( KEY_UI_FONT_PREVIEW_MONO_NAME ) ), | |
| 293 | Setting.of( label( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ), | |
| 294 | Setting.of( title( KEY_UI_FONT_PREVIEW_MONO_SIZE ), | |
| 295 | doubleProperty( KEY_UI_FONT_PREVIEW_MONO_SIZE ) ) | |
| 296 | ) | |
| 297 | ), | |
| 298 | Category.of( | |
| 299 | get( KEY_UI_SKIN ), | |
| 300 | Group.of( | |
| 301 | get( KEY_UI_SKIN_SELECTION ), | |
| 302 | Setting.of( label( KEY_UI_SKIN_SELECTION ) ), | |
| 303 | Setting.of( title( KEY_UI_SKIN_SELECTION ), | |
| 304 | skinListProperty(), | |
| 305 | skinProperty( KEY_UI_SKIN_SELECTION ) ) | |
| 306 | ), | |
| 307 | Group.of( | |
| 308 | get( KEY_UI_SKIN_CUSTOM ), | |
| 309 | Setting.of( label( KEY_UI_SKIN_CUSTOM ) ), | |
| 310 | Setting.of( title( KEY_UI_SKIN_CUSTOM ), | |
| 311 | fileProperty( KEY_UI_SKIN_CUSTOM ), false ) | |
| 312 | ) | |
| 313 | ), | |
| 314 | Category.of( | |
| 315 | get( KEY_LANGUAGE ), | |
| 316 | Group.of( | |
| 317 | get( KEY_LANGUAGE_LOCALE ), | |
| 318 | Setting.of( label( KEY_LANGUAGE_LOCALE ) ), | |
| 319 | Setting.of( title( KEY_LANGUAGE_LOCALE ), | |
| 320 | localeListProperty(), | |
| 321 | localeProperty( KEY_LANGUAGE_LOCALE ) ) | |
| 322 | ) | |
| 323 | )}; | |
| 324 | } | |
| 325 | ||
| 326 | @SuppressWarnings( "unchecked" ) | |
| 327 | private Setting<StringField, StringProperty> createMultilineSetting( | |
| 328 | final String description, final Key property ) { | |
| 329 | final Setting<StringField, StringProperty> setting = | |
| 330 | Setting.of( description, stringProperty( property ) ); | |
| 331 | final var field = setting.getElement(); | |
| 332 | field.multiline( true ); | |
| 333 | ||
| 334 | return setting; | |
| 335 | } | |
| 336 | ||
| 337 | private void initKeyEventHandler( final PreferencesFx preferences ) { | |
| 338 | final var view = preferences.getView(); | |
| 339 | final var nodes = view.getChildrenUnmodifiable(); | |
| 340 | final var master = (MasterDetailPane) nodes.get( 0 ); | |
| 341 | final var detail = (NavigationView) master.getDetailNode(); | |
| 342 | final var pane = (DialogPane) view.getParent(); | |
| 343 | ||
| 344 | detail.setOnKeyReleased( ( key ) -> { | |
| 345 | switch( key.getCode() ) { | |
| 346 | case ENTER -> ((Button) pane.lookupButton( OK )).fire(); | |
| 347 | case ESCAPE -> ((Button) pane.lookupButton( CANCEL )).fire(); | |
| 348 | } | |
| 349 | } ); | |
| 350 | } | |
| 351 | ||
| 352 | /** | |
| 353 | * Creates a label for the given key after interpolating its value. | |
| 354 | * | |
| 355 | * @param key The key to find in the resource bundle. | |
| 356 | * @return The value of the key as a label. | |
| 357 | */ | |
| 358 | private Node label( final Key key ) { | |
| 359 | return label( key, (String[]) null ); | |
| 360 | } | |
| 361 | ||
| 362 | private Node label( final Key key, final String... values ) { | |
| 363 | return new Label( get( key.toString() + ".desc", (Object[]) values ) ); | |
| 364 | } | |
| 365 | ||
| 366 | private String title( final Key key ) { | |
| 367 | return get( key.toString() + ".title" ); | |
| 368 | } | |
| 369 | ||
| 370 | private ObjectProperty<File> fileProperty( final Key key ) { | |
| 371 | return mWorkspace.fileProperty( key ); | |
| 372 | } | |
| 373 | ||
| 374 | private StringProperty stringProperty( final Key key ) { | |
| 375 | return mWorkspace.stringProperty( key ); | |
| 376 | } | |
| 377 | ||
| 378 | private BooleanProperty booleanProperty( final Key key ) { | |
| 379 | return mWorkspace.booleanProperty( key ); | |
| 380 | } | |
| 381 | ||
| 382 | @SuppressWarnings( "SameParameterValue" ) | |
| 383 | private DoubleProperty doubleProperty( final Key key ) { | |
| 384 | return mWorkspace.doubleProperty( key ); | |
| 385 | } | |
| 386 | ||
| 387 | private ObjectProperty<String> skinProperty( final Key key ) { | |
| 388 | return mWorkspace.skinProperty( key ); | |
| 389 | } | |
| 390 | ||
| 391 | private ObjectProperty<String> localeProperty( final Key key ) { | |
| 392 | return mWorkspace.localeProperty( key ); | |
| 393 | } | |
| 394 | ||
| 395 | private PreferencesFx getPreferencesFx() { | |
| 396 | return mPreferencesFx; | |
| 397 | } | |
| 398 | } | |
| 1 | 399 |
| 1 | /* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import com.dlsc.formsfx.model.structure.StringField; | |
| 5 | import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl; | |
| 6 | import javafx.beans.property.DoubleProperty; | |
| 7 | import javafx.beans.property.SimpleDoubleProperty; | |
| 8 | import javafx.scene.control.Button; | |
| 9 | import javafx.scene.control.ListView; | |
| 10 | import javafx.scene.control.TextField; | |
| 11 | import javafx.scene.input.KeyEvent; | |
| 12 | import javafx.scene.layout.HBox; | |
| 13 | import javafx.scene.layout.Region; | |
| 14 | import javafx.scene.layout.StackPane; | |
| 15 | import javafx.scene.text.Font; | |
| 16 | import javafx.stage.Stage; | |
| 17 | import org.controlsfx.dialog.FontSelectorDialog; | |
| 18 | ||
| 19 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG; | |
| 20 | import static com.keenwrite.events.StatusEvent.clue; | |
| 21 | import static java.lang.System.currentTimeMillis; | |
| 22 | import static javafx.geometry.Pos.CENTER_LEFT; | |
| 23 | import static javafx.scene.control.ButtonType.CANCEL; | |
| 24 | import static javafx.scene.control.ButtonType.OK; | |
| 25 | import static javafx.scene.input.KeyCode.ENTER; | |
| 26 | import static javafx.scene.input.KeyCode.ESCAPE; | |
| 27 | import static javafx.scene.layout.Priority.ALWAYS; | |
| 28 | import static javafx.scene.text.Font.font; | |
| 29 | import static javafx.scene.text.Font.getDefault; | |
| 30 | ||
| 31 | /** | |
| 32 | * Responsible for provide users the ability to select a font using a friendly | |
| 33 | * font dialog. | |
| 34 | */ | |
| 35 | public class SimpleFontControl extends SimpleControl<StringField, StackPane> { | |
| 36 | private final Button mButton = new Button(); | |
| 37 | private final String mButtonText; | |
| 38 | private final DoubleProperty mFontSize = new SimpleDoubleProperty(); | |
| 39 | private final TextField mFontName = new TextField(); | |
| 40 | ||
| 41 | public SimpleFontControl( final String buttonText ) { | |
| 42 | mButtonText = buttonText; | |
| 43 | } | |
| 44 | ||
| 45 | @Override | |
| 46 | public void initializeParts() { | |
| 47 | super.initializeParts(); | |
| 48 | ||
| 49 | mFontName.setText( field.getValue() ); | |
| 50 | mFontName.setPromptText( field.placeholderProperty().getValue() ); | |
| 51 | ||
| 52 | final var fieldProperty = field.valueProperty(); | |
| 53 | if( fieldProperty.get().equals( "null" ) ) { | |
| 54 | fieldProperty.set( "" ); | |
| 55 | } | |
| 56 | ||
| 57 | mButton.setText( mButtonText ); | |
| 58 | mButton.setOnAction( event -> { | |
| 59 | final var selected = !fieldProperty.get().trim().isEmpty(); | |
| 60 | var initialFont = getDefault(); | |
| 61 | if( selected ) { | |
| 62 | final var previousValue = fieldProperty.get(); | |
| 63 | initialFont = font( previousValue ); | |
| 64 | } | |
| 65 | ||
| 66 | createFontSelectorDialog( initialFont ) | |
| 67 | .showAndWait() | |
| 68 | .ifPresent( ( font ) -> { | |
| 69 | mFontName.setText( font.getFamily() ); | |
| 70 | mFontSize.set( font.getSize() ); | |
| 71 | } ); | |
| 72 | } ); | |
| 73 | ||
| 74 | node = new StackPane(); | |
| 75 | } | |
| 76 | ||
| 77 | @Override | |
| 78 | public void layoutParts() { | |
| 79 | node.getStyleClass().add( "simple-text-control" ); | |
| 80 | fieldLabel.getStyleClass().addAll( field.getStyleClass() ); | |
| 81 | fieldLabel.getStyleClass().add( "read-only-label" ); | |
| 82 | ||
| 83 | final var box = new HBox(); | |
| 84 | HBox.setHgrow( mFontName, ALWAYS ); | |
| 85 | box.setAlignment( CENTER_LEFT ); | |
| 86 | box.getChildren().addAll( fieldLabel, mFontName, mButton ); | |
| 87 | ||
| 88 | node.getChildren().add( box ); | |
| 89 | } | |
| 90 | ||
| 91 | @Override | |
| 92 | public void setupBindings() { | |
| 93 | super.setupBindings(); | |
| 94 | mFontName.textProperty().bindBidirectional( field.userInputProperty() ); | |
| 95 | } | |
| 96 | ||
| 97 | public DoubleProperty fontSizeProperty() { | |
| 98 | return mFontSize; | |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Creates a dialog that displays a list of available font families, | |
| 103 | * sizes, and a button for font selection. | |
| 104 | * | |
| 105 | * @param font The default font to select initially. | |
| 106 | * @return A dialog to help the user select a different {@link Font}. | |
| 107 | */ | |
| 108 | private FontSelectorDialog createFontSelectorDialog( final Font font ) { | |
| 109 | final var dialog = new FontSelectorDialog( font ); | |
| 110 | final var pane = dialog.getDialogPane(); | |
| 111 | final var buttonOk = ((Button) pane.lookupButton( OK )); | |
| 112 | final var buttonCancel = ((Button) pane.lookupButton( CANCEL )); | |
| 113 | ||
| 114 | buttonOk.setDefaultButton( true ); | |
| 115 | buttonCancel.setCancelButton( true ); | |
| 116 | pane.setOnKeyReleased( ( keyEvent ) -> { | |
| 117 | switch( keyEvent.getCode() ) { | |
| 118 | case ENTER -> buttonOk.fire(); | |
| 119 | case ESCAPE -> buttonCancel.fire(); | |
| 120 | } | |
| 121 | } ); | |
| 122 | ||
| 123 | final var stage = (Stage) pane.getScene().getWindow(); | |
| 124 | stage.getIcons().add( ICON_DIALOG ); | |
| 125 | ||
| 126 | final var frontPanel = (Region) pane.getContent(); | |
| 127 | for( final var node : frontPanel.getChildrenUnmodifiable() ) { | |
| 128 | if( node instanceof final ListView<?> listView ) { | |
| 129 | final var handler = new ListViewHandler<>( listView ); | |
| 130 | listView.setOnKeyPressed( handler::handle ); | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | return dialog; | |
| 135 | } | |
| 136 | ||
| 137 | /** | |
| 138 | * Responsible for handling key presses when selecting a font. Based on | |
| 139 | * <a href="https://stackoverflow.com/a/43604223/59087">Martin Široký</a>'s | |
| 140 | * answer. | |
| 141 | * | |
| 142 | * @param <T> The type of {@link ListView} to search. | |
| 143 | */ | |
| 144 | private static final class ListViewHandler<T> { | |
| 145 | /** | |
| 146 | * Amount of time to wait between key presses that typing a subsequent | |
| 147 | * key is considered part of the same search, in milliseconds. | |
| 148 | */ | |
| 149 | private static final int RESET_DELAY_MS = 1250; | |
| 150 | ||
| 151 | private String mNeedle = ""; | |
| 152 | private int mSearchSkip = 0; | |
| 153 | private long mLastTyped = currentTimeMillis(); | |
| 154 | private final ListView<T> mHaystack; | |
| 155 | ||
| 156 | private ListViewHandler( final ListView<T> listView ) { | |
| 157 | mHaystack = listView; | |
| 158 | } | |
| 159 | ||
| 160 | private void handle( final KeyEvent key ) { | |
| 161 | var ch = key.getText(); | |
| 162 | final var code = key.getCode(); | |
| 163 | ||
| 164 | if( ch == null || ch.isEmpty() || code == ESCAPE || code == ENTER ) { | |
| 165 | return; | |
| 166 | } | |
| 167 | ||
| 168 | ch = ch.toUpperCase(); | |
| 169 | ||
| 170 | if( mNeedle.equals( ch ) ) { | |
| 171 | mSearchSkip++; | |
| 172 | } | |
| 173 | else { | |
| 174 | mNeedle = currentTimeMillis() - mLastTyped > RESET_DELAY_MS | |
| 175 | ? ch : mNeedle + ch; | |
| 176 | } | |
| 177 | ||
| 178 | mLastTyped = currentTimeMillis(); | |
| 179 | ||
| 180 | boolean found = false; | |
| 181 | int skipped = 0; | |
| 182 | ||
| 183 | for( final T item : mHaystack.getItems() ) { | |
| 184 | final var straw = item.toString().toUpperCase(); | |
| 185 | ||
| 186 | if( straw.startsWith( mNeedle ) ) { | |
| 187 | if( mSearchSkip > skipped ) { | |
| 188 | skipped++; | |
| 189 | continue; | |
| 190 | } | |
| 191 | ||
| 192 | mHaystack.getSelectionModel().select( item ); | |
| 193 | final int index = mHaystack.getSelectionModel().getSelectedIndex(); | |
| 194 | mHaystack.getFocusModel().focus( index ); | |
| 195 | mHaystack.scrollTo( index ); | |
| 196 | found = true; | |
| 197 | break; | |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | if( !found ) { | |
| 202 | clue( "Main.status.font.search.missing", mNeedle ); | |
| 203 | mSearchSkip = 0; | |
| 204 | } | |
| 205 | } | |
| 206 | } | |
| 207 | } | |
| 1 | 208 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import com.keenwrite.constants.Constants; | |
| 5 | import javafx.beans.property.SimpleObjectProperty; | |
| 6 | import javafx.collections.ObservableList; | |
| 7 | ||
| 8 | import java.util.LinkedHashSet; | |
| 9 | import java.util.Set; | |
| 10 | ||
| 11 | import static com.keenwrite.constants.Constants.SKIN_DEFAULT; | |
| 12 | import static com.keenwrite.preferences.Workspace.listProperty; | |
| 13 | ||
| 14 | /** | |
| 15 | * Maintains a list of look and feels that the user may choose. | |
| 16 | */ | |
| 17 | public final class SkinProperty extends SimpleObjectProperty<String> { | |
| 18 | /** | |
| 19 | * Ordered set of available skins. | |
| 20 | */ | |
| 21 | private static final Set<String> sSkins = new LinkedHashSet<>(); | |
| 22 | ||
| 23 | static { | |
| 24 | sSkins.add( "Count Darcula" ); | |
| 25 | sSkins.add( "Haunted Grey" ); | |
| 26 | sSkins.add( "Modena Dark" ); | |
| 27 | sSkins.add( SKIN_DEFAULT ); | |
| 28 | sSkins.add( "Silver Cavern" ); | |
| 29 | sSkins.add( "Solarized Dark" ); | |
| 30 | sSkins.add( "Vampire Byte" ); | |
| 31 | } | |
| 32 | ||
| 33 | public SkinProperty( final String skin ) { | |
| 34 | super( skin ); | |
| 35 | } | |
| 36 | ||
| 37 | public static ObservableList<String> skinListProperty() { | |
| 38 | return listProperty( sSkins ); | |
| 39 | } | |
| 40 | ||
| 41 | /** | |
| 42 | * Returns the given skin name as a sanitized file name, which must map | |
| 43 | * to a stylesheet file bundled with the application. This does not include | |
| 44 | * the path to the stylesheet. If the given name is not known, the file | |
| 45 | * name for {@link Constants#SKIN_DEFAULT} is returned. The extension must | |
| 46 | * be added separately. | |
| 47 | * | |
| 48 | * @param skin The name to convert to a file name. | |
| 49 | * @return The given name converted lower case, spaces replaced with | |
| 50 | * underscores, without the ".css" extension appended. | |
| 51 | */ | |
| 52 | public static String toFilename( final String skin ) { | |
| 53 | return sanitize( skin ).toLowerCase().replace( ' ', '_' ); | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Ensures that the given name is in the list of known skins. | |
| 58 | * | |
| 59 | * @param skin Validate this name's existence. | |
| 60 | * @return The given name, if valid, otherwise the default skin. | |
| 61 | */ | |
| 62 | private static String sanitize( final String skin ) { | |
| 63 | return sSkins.contains( skin ) ? skin : SKIN_DEFAULT; | |
| 64 | } | |
| 65 | } | |
| 1 | 66 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import com.keenwrite.constants.Constants; | |
| 5 | import com.keenwrite.sigils.Tokens; | |
| 6 | import javafx.application.Platform; | |
| 7 | import javafx.beans.property.*; | |
| 8 | import javafx.collections.ObservableList; | |
| 9 | import org.apache.commons.configuration2.XMLConfiguration; | |
| 10 | import org.apache.commons.configuration2.builder.fluent.Configurations; | |
| 11 | import org.apache.commons.configuration2.io.FileHandler; | |
| 12 | ||
| 13 | import java.io.File; | |
| 14 | import java.time.Year; | |
| 15 | import java.time.ZonedDateTime; | |
| 16 | import java.util.*; | |
| 17 | import java.util.function.BiConsumer; | |
| 18 | import java.util.function.BooleanSupplier; | |
| 19 | import java.util.function.Consumer; | |
| 20 | import java.util.function.Function; | |
| 21 | ||
| 22 | import static com.keenwrite.Bootstrap.APP_TITLE_LOWERCASE; | |
| 23 | import static com.keenwrite.Launcher.getVersion; | |
| 24 | import static com.keenwrite.constants.Constants.*; | |
| 25 | import static com.keenwrite.events.StatusEvent.clue; | |
| 26 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 27 | import static java.lang.String.valueOf; | |
| 28 | import static java.lang.System.getProperty; | |
| 29 | import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME; | |
| 30 | import static java.util.Map.entry; | |
| 31 | import static javafx.application.Platform.runLater; | |
| 32 | import static javafx.collections.FXCollections.observableArrayList; | |
| 33 | import static javafx.collections.FXCollections.observableSet; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for defining behaviours for separate projects. A workspace has | |
| 37 | * the ability to save and restore a session, including the window dimensions, | |
| 38 | * tab setup, files, and user preferences. | |
| 39 | * <p> | |
| 40 | * The configuration must support hierarchical (nested) configuration nodes | |
| 41 | * to persist the user interface state. Although possible with a flat | |
| 42 | * configuration file, it's not nearly as simple or elegant. | |
| 43 | * </p> | |
| 44 | * <p> | |
| 45 | * Neither JSON nor HOCON support schema validation and versioning, which makes | |
| 46 | * XML the more suitable configuration file format. Schema validation and | |
| 47 | * versioning provide future-proofing and ease of reading and upgrading previous | |
| 48 | * versions of the configuration file. | |
| 49 | * </p> | |
| 50 | * <p> | |
| 51 | * Persistent preferences may be set directly by the user or indirectly by | |
| 52 | * the act of using the application. | |
| 53 | * </p> | |
| 54 | * <p> | |
| 55 | * Note the following definitions: | |
| 56 | * </p> | |
| 57 | * <dl> | |
| 58 | * <dt>File</dt> | |
| 59 | * <dd>References a file name (no path), path, or directory.</dd> | |
| 60 | * <dt>Path</dt> | |
| 61 | * <dd>Fully qualified file name, which includes all parent directories.</dd> | |
| 62 | * <dt>Dir</dt> | |
| 63 | * <dd>Directory without a file name ({@link File#isDirectory()} is true) | |
| 64 | * .</dd> | |
| 65 | * </dl> | |
| 66 | */ | |
| 67 | public final class Workspace { | |
| 68 | private final Map<Key, Property<?>> VALUES = Map.ofEntries( | |
| 69 | entry( KEY_META_VERSION, asStringProperty( getVersion() ) ), | |
| 70 | entry( KEY_META_NAME, asStringProperty( "default" ) ), | |
| 71 | ||
| 72 | entry( KEY_DOC_TITLE, asStringProperty( "title" ) ), | |
| 73 | entry( KEY_DOC_AUTHOR, asStringProperty( getProperty( "user.name" ) ) ), | |
| 74 | entry( KEY_DOC_BYLINE, asStringProperty( getProperty( "user.name" ) ) ), | |
| 75 | entry( KEY_DOC_ADDRESS, asStringProperty( "" ) ), | |
| 76 | entry( KEY_DOC_PHONE, asStringProperty( "" ) ), | |
| 77 | entry( KEY_DOC_EMAIL, asStringProperty( "" ) ), | |
| 78 | entry( KEY_DOC_KEYWORDS, asStringProperty( "science, nature" ) ), | |
| 79 | entry( KEY_DOC_COPYRIGHT, asStringProperty( getYear() ) ), | |
| 80 | entry( KEY_DOC_DATE, asStringProperty( getDate() ) ), | |
| 81 | ||
| 82 | entry( KEY_R_SCRIPT, asStringProperty( "" ) ), | |
| 83 | entry( KEY_R_DIR, asFileProperty( USER_DIRECTORY ) ), | |
| 84 | entry( KEY_R_DELIM_BEGAN, asStringProperty( R_DELIM_BEGAN_DEFAULT ) ), | |
| 85 | entry( KEY_R_DELIM_ENDED, asStringProperty( R_DELIM_ENDED_DEFAULT ) ), | |
| 86 | ||
| 87 | entry( KEY_IMAGES_DIR, asFileProperty( USER_DIRECTORY ) ), | |
| 88 | entry( KEY_IMAGES_ORDER, asStringProperty( PERSIST_IMAGES_DEFAULT ) ), | |
| 89 | entry( KEY_IMAGES_RESIZE, asBooleanProperty( true ) ), | |
| 90 | entry( KEY_IMAGES_SERVER, asStringProperty( DIAGRAM_SERVER_NAME ) ), | |
| 91 | ||
| 92 | entry( KEY_DEF_PATH, asFileProperty( DEFINITION_DEFAULT ) ), | |
| 93 | entry( KEY_DEF_DELIM_BEGAN, asStringProperty( DEF_DELIM_BEGAN_DEFAULT ) ), | |
| 94 | entry( KEY_DEF_DELIM_ENDED, asStringProperty( DEF_DELIM_ENDED_DEFAULT ) ), | |
| 95 | ||
| 96 | entry( KEY_UI_RECENT_DIR, asFileProperty( USER_DIRECTORY ) ), | |
| 97 | entry( KEY_UI_RECENT_DOCUMENT, asFileProperty( DOCUMENT_DEFAULT ) ), | |
| 98 | entry( KEY_UI_RECENT_DEFINITION, asFileProperty( DEFINITION_DEFAULT ) ), | |
| 99 | ||
| 100 | //@formatter:off | |
| 101 | entry( KEY_UI_FONT_EDITOR_NAME, asStringProperty( FONT_NAME_EDITOR_DEFAULT ) ), | |
| 102 | entry( KEY_UI_FONT_EDITOR_SIZE, asDoubleProperty( FONT_SIZE_EDITOR_DEFAULT ) ), | |
| 103 | entry( KEY_UI_FONT_PREVIEW_NAME, asStringProperty( FONT_NAME_PREVIEW_DEFAULT ) ), | |
| 104 | entry( KEY_UI_FONT_PREVIEW_SIZE, asDoubleProperty( FONT_SIZE_PREVIEW_DEFAULT ) ), | |
| 105 | entry( KEY_UI_FONT_PREVIEW_MONO_NAME, asStringProperty( FONT_NAME_PREVIEW_MONO_NAME_DEFAULT ) ), | |
| 106 | entry( KEY_UI_FONT_PREVIEW_MONO_SIZE, asDoubleProperty( FONT_SIZE_PREVIEW_MONO_SIZE_DEFAULT ) ), | |
| 107 | ||
| 108 | entry( KEY_UI_WINDOW_X, asDoubleProperty( WINDOW_X_DEFAULT ) ), | |
| 109 | entry( KEY_UI_WINDOW_Y, asDoubleProperty( WINDOW_Y_DEFAULT ) ), | |
| 110 | entry( KEY_UI_WINDOW_W, asDoubleProperty( WINDOW_W_DEFAULT ) ), | |
| 111 | entry( KEY_UI_WINDOW_H, asDoubleProperty( WINDOW_H_DEFAULT ) ), | |
| 112 | entry( KEY_UI_WINDOW_MAX, asBooleanProperty() ), | |
| 113 | entry( KEY_UI_WINDOW_FULL, asBooleanProperty() ), | |
| 114 | ||
| 115 | entry( KEY_UI_SKIN_SELECTION, asSkinProperty( SKIN_DEFAULT ) ), | |
| 116 | entry( KEY_UI_SKIN_CUSTOM, asFileProperty( SKIN_CUSTOM_DEFAULT ) ), | |
| 117 | ||
| 118 | entry( KEY_LANGUAGE_LOCALE, asLocaleProperty( LOCALE_DEFAULT ) ), | |
| 119 | ||
| 120 | entry( KEY_TYPESET_CONTEXT_CLEAN, asBooleanProperty( true ) ), | |
| 121 | entry( KEY_TYPESET_CONTEXT_THEMES_PATH, asFileProperty( USER_DIRECTORY ) ), | |
| 122 | entry( KEY_TYPESET_CONTEXT_THEME_SELECTION, asStringProperty( "boschet" ) ), | |
| 123 | entry( KEY_TYPESET_TYPOGRAPHY_QUOTES, asBooleanProperty( true ) ) | |
| 124 | //@formatter:on | |
| 125 | ); | |
| 126 | ||
| 127 | private StringProperty asStringProperty( final String defaultValue ) { | |
| 128 | return new SimpleStringProperty( defaultValue ); | |
| 129 | } | |
| 130 | ||
| 131 | private DoubleProperty asDoubleProperty( final double defaultValue ) { | |
| 132 | return new SimpleDoubleProperty( defaultValue ); | |
| 133 | } | |
| 134 | ||
| 135 | private BooleanProperty asBooleanProperty() { | |
| 136 | return new SimpleBooleanProperty(); | |
| 137 | } | |
| 138 | ||
| 139 | @SuppressWarnings( "SameParameterValue" ) | |
| 140 | private BooleanProperty asBooleanProperty( final boolean defaultValue ) { | |
| 141 | return new SimpleBooleanProperty( defaultValue ); | |
| 142 | } | |
| 143 | ||
| 144 | private FileProperty asFileProperty( final File defaultValue ) { | |
| 145 | return new FileProperty( defaultValue ); | |
| 146 | } | |
| 147 | ||
| 148 | @SuppressWarnings( "SameParameterValue" ) | |
| 149 | private SkinProperty asSkinProperty( final String defaultValue ) { | |
| 150 | return new SkinProperty( defaultValue ); | |
| 151 | } | |
| 152 | ||
| 153 | @SuppressWarnings( "SameParameterValue" ) | |
| 154 | private LocaleProperty asLocaleProperty( final Locale defaultValue ) { | |
| 155 | return new LocaleProperty( defaultValue ); | |
| 156 | } | |
| 157 | ||
| 158 | /** | |
| 159 | * Helps instantiate {@link Property} instances for XML configuration items. | |
| 160 | */ | |
| 161 | private static final Map<Class<?>, Function<String, Object>> UNMARSHALL = | |
| 162 | Map.of( | |
| 163 | LocaleProperty.class, LocaleProperty::parseLocale, | |
| 164 | SimpleBooleanProperty.class, Boolean::parseBoolean, | |
| 165 | SimpleDoubleProperty.class, Double::parseDouble, | |
| 166 | SimpleFloatProperty.class, Float::parseFloat, | |
| 167 | FileProperty.class, File::new | |
| 168 | ); | |
| 169 | ||
| 170 | private static final Map<Class<?>, Function<String, Object>> MARSHALL = | |
| 171 | Map.of( | |
| 172 | LocaleProperty.class, LocaleProperty::toLanguageTag | |
| 173 | ); | |
| 174 | ||
| 175 | private final Map<Key, SetProperty<?>> SETS = Map.ofEntries( | |
| 176 | entry( | |
| 177 | KEY_UI_FILES_PATH, | |
| 178 | new SimpleSetProperty<>( observableSet( new HashSet<>() ) ) | |
| 179 | ) | |
| 180 | ); | |
| 181 | ||
| 182 | /** | |
| 183 | * Creates a new {@link Workspace} that will attempt to load a configuration | |
| 184 | * file. If the configuration file cannot be loaded, the workspace settings | |
| 185 | * will return default values. This allows unit tests to provide an instance | |
| 186 | * of {@link Workspace} when necessary without encountering failures. | |
| 187 | */ | |
| 188 | public Workspace() { | |
| 189 | load( FILE_PREFERENCES ); | |
| 190 | } | |
| 191 | ||
| 192 | /** | |
| 193 | * Creates a new {@link Workspace} that will attempt to load the given | |
| 194 | * configuration file. | |
| 195 | * | |
| 196 | * @param filename The file to load. | |
| 197 | */ | |
| 198 | public Workspace( final String filename ) { | |
| 199 | load( filename ); | |
| 200 | } | |
| 201 | ||
| 202 | /** | |
| 203 | * Creates an instance of {@link ObservableList} that is based on a | |
| 204 | * modifiable observable array list for the given items. | |
| 205 | * | |
| 206 | * @param items The items to wrap in an observable list. | |
| 207 | * @param <E> The type of items to add to the list. | |
| 208 | * @return An observable property that can have its contents modified. | |
| 209 | */ | |
| 210 | public static <E> ObservableList<E> listProperty( final Set<E> items ) { | |
| 211 | return new SimpleListProperty<>( observableArrayList( items ) ); | |
| 212 | } | |
| 213 | ||
| 214 | /** | |
| 215 | * Returns a value that represents a setting in the application that the user | |
| 216 | * may configure, either directly or indirectly. | |
| 217 | * | |
| 218 | * @param key The reference to the users' preference stored in deference | |
| 219 | * of app reëntrance. | |
| 220 | * @return An observable property to be persisted. | |
| 221 | */ | |
| 222 | @SuppressWarnings( "unchecked" ) | |
| 223 | public <T, U extends Property<T>> U valuesProperty( final Key key ) { | |
| 224 | assert key != null; | |
| 225 | // The type that goes into the map must come out. | |
| 226 | return (U) VALUES.get( key ); | |
| 227 | } | |
| 228 | ||
| 229 | /** | |
| 230 | * Returns a list of values that represent a setting in the application that | |
| 231 | * the user may configure, either directly or indirectly. The property | |
| 232 | * returned is backed by a mutable {@link Set}. | |
| 233 | * | |
| 234 | * @param key The {@link Key} associated with a preference value. | |
| 235 | * @return An observable property to be persisted. | |
| 236 | */ | |
| 237 | @SuppressWarnings( "unchecked" ) | |
| 238 | public <T> SetProperty<T> setsProperty( final Key key ) { | |
| 239 | assert key != null; | |
| 240 | // The type that goes into the map must come out. | |
| 241 | return (SetProperty<T>) SETS.get( key ); | |
| 242 | } | |
| 243 | ||
| 244 | /** | |
| 245 | * Returns the {@link Boolean} preference value associated with the given | |
| 246 | * {@link Key}. The caller must be sure that the given {@link Key} is | |
| 247 | * associated with a value that matches the return type. | |
| 248 | * | |
| 249 | * @param key The {@link Key} associated with a preference value. | |
| 250 | * @return The value associated with the given {@link Key}. | |
| 251 | */ | |
| 252 | public boolean toBoolean( final Key key ) { | |
| 253 | assert key != null; | |
| 254 | return (Boolean) valuesProperty( key ).getValue(); | |
| 255 | } | |
| 256 | ||
| 257 | /** | |
| 258 | * Returns the {@link Double} preference value associated with the given | |
| 259 | * {@link Key}. The caller must be sure that the given {@link Key} is | |
| 260 | * associated with a value that matches the return type. | |
| 261 | * | |
| 262 | * @param key The {@link Key} associated with a preference value. | |
| 263 | * @return The value associated with the given {@link Key}. | |
| 264 | */ | |
| 265 | public double toDouble( final Key key ) { | |
| 266 | assert key != null; | |
| 267 | return (Double) valuesProperty( key ).getValue(); | |
| 268 | } | |
| 269 | ||
| 270 | public File toFile( final Key key ) { | |
| 271 | assert key != null; | |
| 272 | return fileProperty( key ).get(); | |
| 273 | } | |
| 274 | ||
| 275 | public String toString( final Key key ) { | |
| 276 | assert key != null; | |
| 277 | return stringProperty( key ).get(); | |
| 278 | } | |
| 279 | ||
| 280 | public Tokens toTokens( final Key began, final Key ended ) { | |
| 281 | assert began != null; | |
| 282 | assert ended != null; | |
| 283 | return new Tokens( stringProperty( began ), stringProperty( ended ) ); | |
| 284 | } | |
| 285 | ||
| 286 | @SuppressWarnings( "SameParameterValue" ) | |
| 287 | public DoubleProperty doubleProperty( final Key key ) { | |
| 288 | assert key != null; | |
| 289 | return valuesProperty( key ); | |
| 290 | } | |
| 291 | ||
| 292 | /** | |
| 293 | * Returns the {@link File} {@link Property} associated with the given | |
| 294 | * {@link Key} from the internal list of preference values. The caller | |
| 295 | * must be sure that the given {@link Key} is associated with a {@link File} | |
| 296 | * {@link Property}. | |
| 297 | * | |
| 298 | * @param key The {@link Key} associated with a preference value. | |
| 299 | * @return The value associated with the given {@link Key}. | |
| 300 | */ | |
| 301 | public ObjectProperty<File> fileProperty( final Key key ) { | |
| 302 | assert key != null; | |
| 303 | return valuesProperty( key ); | |
| 304 | } | |
| 305 | ||
| 306 | public ObjectProperty<String> skinProperty( final Key key ) { | |
| 307 | assert key != null; | |
| 308 | return valuesProperty( key ); | |
| 309 | } | |
| 310 | ||
| 311 | public LocaleProperty localeProperty( final Key key ) { | |
| 312 | assert key != null; | |
| 313 | return valuesProperty( key ); | |
| 314 | } | |
| 315 | ||
| 316 | /** | |
| 317 | * Returns the language locale setting for the | |
| 318 | * {@link WorkspaceKeys#KEY_LANGUAGE_LOCALE} key. | |
| 319 | * | |
| 320 | * @return The user's current locale setting. | |
| 321 | */ | |
| 322 | public Locale getLocale() { | |
| 323 | return localeProperty( KEY_LANGUAGE_LOCALE ).toLocale(); | |
| 324 | } | |
| 325 | ||
| 326 | public StringProperty stringProperty( final Key key ) { | |
| 327 | assert key != null; | |
| 328 | return valuesProperty( key ); | |
| 329 | } | |
| 330 | ||
| 331 | public BooleanProperty booleanProperty( final Key key ) { | |
| 332 | assert key != null; | |
| 333 | return valuesProperty( key ); | |
| 334 | } | |
| 335 | ||
| 336 | public void loadValueKeys( final Consumer<Key> consumer ) { | |
| 337 | VALUES.keySet().forEach( consumer ); | |
| 338 | } | |
| 339 | ||
| 340 | public void loadSetKeys( final Consumer<Key> consumer ) { | |
| 341 | SETS.keySet().forEach( consumer ); | |
| 342 | } | |
| 343 | ||
| 344 | /** | |
| 345 | * Calls the given consumer for all single-value keys. For lists, see | |
| 346 | * {@link #saveSets(BiConsumer)}. | |
| 347 | * | |
| 348 | * @param consumer Called to accept each preference key value. | |
| 349 | */ | |
| 350 | public void saveValues( final BiConsumer<Key, Property<?>> consumer ) { | |
| 351 | VALUES.forEach( consumer ); | |
| 352 | } | |
| 353 | ||
| 354 | /** | |
| 355 | * Calls the given consumer for all multi-value keys. For single items, see | |
| 356 | * {@link #saveValues(BiConsumer)}. Callers are responsible for iterating | |
| 357 | * over the list of items retrieved through this method. | |
| 358 | * | |
| 359 | * @param consumer Called to accept each preference key list. | |
| 360 | */ | |
| 361 | public void saveSets( final BiConsumer<Key, SetProperty<?>> consumer ) { | |
| 362 | SETS.forEach( consumer ); | |
| 363 | } | |
| 364 | ||
| 365 | /** | |
| 366 | * Delegates to {@link #listen(Key, ReadOnlyProperty, BooleanSupplier)}, | |
| 367 | * providing a value of {@code true} for the {@link BooleanSupplier} to | |
| 368 | * indicate the property changes always take effect. | |
| 369 | * | |
| 370 | * @param key The value to bind to the internal key property. | |
| 371 | * @param property The external property value that sets the internal value. | |
| 372 | */ | |
| 373 | public <T> void listen( final Key key, final ReadOnlyProperty<T> property ) { | |
| 374 | listen( key, property, () -> true ); | |
| 375 | } | |
| 376 | ||
| 377 | /** | |
| 378 | * Binds a read-only property to a value in the preferences. This allows | |
| 379 | * user interface properties to change and the preferences will be | |
| 380 | * synchronized automatically. | |
| 381 | * <p> | |
| 382 | * This calls {@link Platform#runLater(Runnable)} to ensure that all pending | |
| 383 | * application window states are finished before assessing whether property | |
| 384 | * changes should be applied. Without this, exiting the application while the | |
| 385 | * window is maximized would persist the window's maximum dimensions, | |
| 386 | * preventing restoration to its prior, non-maximum size. | |
| 387 | * </p> | |
| 388 | * | |
| 389 | * @param key The value to bind to the internal key property. | |
| 390 | * @param property The external property value that sets the internal value. | |
| 391 | * @param enabled Indicates whether property changes should be applied. | |
| 392 | */ | |
| 393 | public <T> void listen( | |
| 394 | final Key key, | |
| 395 | final ReadOnlyProperty<T> property, | |
| 396 | final BooleanSupplier enabled ) { | |
| 397 | property.addListener( | |
| 398 | ( c, o, n ) -> runLater( () -> { | |
| 399 | if( enabled.getAsBoolean() ) { | |
| 400 | valuesProperty( key ).setValue( n ); | |
| 401 | } | |
| 402 | } ) | |
| 403 | ); | |
| 404 | } | |
| 405 | ||
| 406 | /** | |
| 407 | * Saves the current workspace. | |
| 408 | */ | |
| 409 | public void save() { | |
| 410 | try { | |
| 411 | final var config = new XMLConfiguration(); | |
| 412 | ||
| 413 | // The root config key can only be set for an empty configuration file. | |
| 414 | config.setRootElementName( APP_TITLE_LOWERCASE ); | |
| 415 | valuesProperty( KEY_META_VERSION ).setValue( getVersion() ); | |
| 416 | ||
| 417 | saveValues( ( key, property ) -> | |
| 418 | config.setProperty( key.toString(), marshall( property ) ) | |
| 419 | ); | |
| 420 | ||
| 421 | saveSets( ( key, set ) -> { | |
| 422 | final var keyName = key.toString(); | |
| 423 | set.forEach( ( value ) -> config.addProperty( keyName, value ) ); | |
| 424 | } ); | |
| 425 | new FileHandler( config ).save( FILE_PREFERENCES ); | |
| 426 | } catch( final Exception ex ) { | |
| 427 | clue( ex ); | |
| 428 | } | |
| 429 | } | |
| 430 | ||
| 431 | /** | |
| 432 | * Attempts to load the {@link Constants#FILE_PREFERENCES} configuration file. | |
| 433 | * If not found, this will fall back to an empty configuration file, leaving | |
| 434 | * the application to fill in default values. | |
| 435 | * | |
| 436 | * @param filename The file containing user preferences to load. | |
| 437 | */ | |
| 438 | private void load( final String filename ) { | |
| 439 | try { | |
| 440 | final var config = new Configurations().xml( filename ); | |
| 441 | ||
| 442 | loadValueKeys( ( key ) -> { | |
| 443 | final var configValue = config.getProperty( key.toString() ); | |
| 444 | ||
| 445 | // Allow other properties to load, even if any are missing. | |
| 446 | if( configValue != null ) { | |
| 447 | final var propertyValue = valuesProperty( key ); | |
| 448 | propertyValue.setValue( unmarshall( propertyValue, configValue ) ); | |
| 449 | } | |
| 450 | } ); | |
| 451 | ||
| 452 | loadSetKeys( ( key ) -> { | |
| 453 | final var configSet = | |
| 454 | new LinkedHashSet<>( config.getList( key.toString() ) ); | |
| 455 | final var propertySet = setsProperty( key ); | |
| 456 | propertySet.setValue( observableSet( configSet ) ); | |
| 457 | } ); | |
| 458 | } catch( final Exception ex ) { | |
| 459 | clue( ex ); | |
| 460 | } | |
| 461 | } | |
| 462 | ||
| 463 | private Object unmarshall( | |
| 464 | final Property<?> property, final Object configValue ) { | |
| 465 | final var setting = configValue.toString(); | |
| 466 | ||
| 467 | return UNMARSHALL | |
| 468 | .getOrDefault( property.getClass(), ( value ) -> value ) | |
| 469 | .apply( setting ); | |
| 470 | } | |
| 471 | ||
| 472 | private Object marshall( final Property<?> property ) { | |
| 473 | return property.getValue() == null | |
| 474 | ? null | |
| 475 | : MARSHALL | |
| 476 | .getOrDefault( property.getClass(), ( __ ) -> property.getValue() ) | |
| 477 | .apply( property.getValue().toString() ); | |
| 478 | } | |
| 479 | ||
| 480 | private String getYear() { | |
| 481 | return valueOf( Year.now().getValue() ); | |
| 482 | } | |
| 483 | ||
| 484 | private String getDate() { | |
| 485 | return ZonedDateTime.now().format( RFC_1123_DATE_TIME ); | |
| 486 | } | |
| 487 | } | |
| 1 | 488 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import static com.keenwrite.preferences.Key.key; | |
| 5 | ||
| 6 | /** | |
| 7 | * Responsible for defining constants used throughout the application that | |
| 8 | * represent persisted preferences. | |
| 9 | */ | |
| 10 | public final class WorkspaceKeys { | |
| 11 | //@formatter:off | |
| 12 | private static final Key KEY_ROOT = key( "workspace" ); | |
| 13 | ||
| 14 | public static final Key KEY_META = key( KEY_ROOT, "meta" ); | |
| 15 | public static final Key KEY_META_NAME = key( KEY_META, "name" ); | |
| 16 | public static final Key KEY_META_VERSION = key( KEY_META, "version" ); | |
| 17 | ||
| 18 | public static final Key KEY_DOC = key( KEY_ROOT, "document" ); | |
| 19 | public static final Key KEY_DOC_TITLE = key( KEY_DOC, "title" ); | |
| 20 | public static final Key KEY_DOC_AUTHOR = key( KEY_DOC, "author" ); | |
| 21 | public static final Key KEY_DOC_BYLINE = key( KEY_DOC, "byline" ); | |
| 22 | public static final Key KEY_DOC_ADDRESS = key( KEY_DOC, "address" ); | |
| 23 | public static final Key KEY_DOC_PHONE = key( KEY_DOC, "phone" ); | |
| 24 | public static final Key KEY_DOC_EMAIL = key( KEY_DOC, "email" ); | |
| 25 | public static final Key KEY_DOC_KEYWORDS = key( KEY_DOC, "keywords" ); | |
| 26 | public static final Key KEY_DOC_DATE = key( KEY_DOC, "date" ); | |
| 27 | public static final Key KEY_DOC_COPYRIGHT = key( KEY_DOC, "copyright" ); | |
| 28 | ||
| 29 | public static final Key KEY_R = key( KEY_ROOT, "r" ); | |
| 30 | public static final Key KEY_R_SCRIPT = key( KEY_R, "script" ); | |
| 31 | public static final Key KEY_R_DIR = key( KEY_R, "dir" ); | |
| 32 | public static final Key KEY_R_DELIM = key( KEY_R, "delimiter" ); | |
| 33 | public static final Key KEY_R_DELIM_BEGAN = key( KEY_R_DELIM, "began" ); | |
| 34 | public static final Key KEY_R_DELIM_ENDED = key( KEY_R_DELIM, "ended" ); | |
| 35 | ||
| 36 | public static final Key KEY_IMAGES = key( KEY_ROOT, "images" ); | |
| 37 | public static final Key KEY_IMAGES_DIR = key( KEY_IMAGES, "dir" ); | |
| 38 | public static final Key KEY_IMAGES_ORDER = key( KEY_IMAGES, "order" ); | |
| 39 | public static final Key KEY_IMAGES_RESIZE = key( KEY_IMAGES, "resize" ); | |
| 40 | public static final Key KEY_IMAGES_SERVER = key( KEY_IMAGES, "server" ); | |
| 41 | ||
| 42 | public static final Key KEY_DEF = key( KEY_ROOT, "definition" ); | |
| 43 | public static final Key KEY_DEF_PATH = key( KEY_DEF, "path" ); | |
| 44 | public static final Key KEY_DEF_DELIM = key( KEY_DEF, "delimiter" ); | |
| 45 | public static final Key KEY_DEF_DELIM_BEGAN = key( KEY_DEF_DELIM, "began" ); | |
| 46 | public static final Key KEY_DEF_DELIM_ENDED = key( KEY_DEF_DELIM, "ended" ); | |
| 47 | ||
| 48 | public static final Key KEY_UI = key( KEY_ROOT, "ui" ); | |
| 49 | ||
| 50 | public static final Key KEY_UI_RECENT = key( KEY_UI, "recent" ); | |
| 51 | public static final Key KEY_UI_RECENT_DIR = key( KEY_UI_RECENT, "dir" ); | |
| 52 | public static final Key KEY_UI_RECENT_DOCUMENT = key( KEY_UI_RECENT, "document" ); | |
| 53 | public static final Key KEY_UI_RECENT_DEFINITION = key( KEY_UI_RECENT, "definition" ); | |
| 54 | ||
| 55 | public static final Key KEY_UI_FILES = key( KEY_UI, "files" ); | |
| 56 | public static final Key KEY_UI_FILES_PATH = key( KEY_UI_FILES, "path" ); | |
| 57 | ||
| 58 | public static final Key KEY_UI_FONT = key( KEY_UI, "font" ); | |
| 59 | public static final Key KEY_UI_FONT_EDITOR = key( KEY_UI_FONT, "editor" ); | |
| 60 | public static final Key KEY_UI_FONT_EDITOR_NAME = key( KEY_UI_FONT_EDITOR, "name" ); | |
| 61 | public static final Key KEY_UI_FONT_EDITOR_SIZE = key( KEY_UI_FONT_EDITOR, "size" ); | |
| 62 | public static final Key KEY_UI_FONT_PREVIEW = key( KEY_UI_FONT, "preview" ); | |
| 63 | public static final Key KEY_UI_FONT_PREVIEW_NAME = key( KEY_UI_FONT_PREVIEW, "name" ); | |
| 64 | public static final Key KEY_UI_FONT_PREVIEW_SIZE = key( KEY_UI_FONT_PREVIEW, "size" ); | |
| 65 | public static final Key KEY_UI_FONT_PREVIEW_MONO = key( KEY_UI_FONT_PREVIEW, "mono" ); | |
| 66 | public static final Key KEY_UI_FONT_PREVIEW_MONO_NAME = key( KEY_UI_FONT_PREVIEW_MONO, "name" ); | |
| 67 | public static final Key KEY_UI_FONT_PREVIEW_MONO_SIZE = key( KEY_UI_FONT_PREVIEW_MONO, "size" ); | |
| 68 | ||
| 69 | public static final Key KEY_UI_WINDOW = key( KEY_UI, "window" ); | |
| 70 | public static final Key KEY_UI_WINDOW_X = key( KEY_UI_WINDOW, "x" ); | |
| 71 | public static final Key KEY_UI_WINDOW_Y = key( KEY_UI_WINDOW, "y" ); | |
| 72 | public static final Key KEY_UI_WINDOW_W = key( KEY_UI_WINDOW, "width" ); | |
| 73 | public static final Key KEY_UI_WINDOW_H = key( KEY_UI_WINDOW, "height" ); | |
| 74 | public static final Key KEY_UI_WINDOW_MAX = key( KEY_UI_WINDOW, "maximized" ); | |
| 75 | public static final Key KEY_UI_WINDOW_FULL = key( KEY_UI_WINDOW, "full" ); | |
| 76 | ||
| 77 | public static final Key KEY_UI_SKIN = key( KEY_UI, "skin" ); | |
| 78 | public static final Key KEY_UI_SKIN_SELECTION = key( KEY_UI_SKIN, "selection" ); | |
| 79 | public static final Key KEY_UI_SKIN_CUSTOM = key( KEY_UI_SKIN, "custom" ); | |
| 80 | ||
| 81 | public static final Key KEY_LANGUAGE = key( KEY_ROOT, "language" ); | |
| 82 | public static final Key KEY_LANGUAGE_LOCALE = key( KEY_LANGUAGE, "locale" ); | |
| 83 | ||
| 84 | public static final Key KEY_TYPESET = key( KEY_ROOT, "typeset" ); | |
| 85 | public static final Key KEY_TYPESET_CONTEXT = key( KEY_TYPESET, "context" ); | |
| 86 | public static final Key KEY_TYPESET_CONTEXT_THEMES = key( KEY_TYPESET_CONTEXT, "themes" ); | |
| 87 | public static final Key KEY_TYPESET_CONTEXT_THEMES_PATH = key( KEY_TYPESET_CONTEXT_THEMES, "path" ); | |
| 88 | public static final Key KEY_TYPESET_CONTEXT_THEME_SELECTION = key( KEY_TYPESET_CONTEXT_THEMES, "selection" ); | |
| 89 | public static final Key KEY_TYPESET_CONTEXT_CLEAN = key( KEY_TYPESET_CONTEXT, "clean" ); | |
| 90 | public static final Key KEY_TYPESET_TYPOGRAPHY = key( KEY_TYPESET, "typography" ); | |
| 91 | public static final Key KEY_TYPESET_TYPOGRAPHY_QUOTES = key( KEY_TYPESET_TYPOGRAPHY, "quotes" ); | |
| 92 | //@formatter:on | |
| 93 | ||
| 94 | /** | |
| 95 | * Only for constants, do not instantiate. | |
| 96 | */ | |
| 97 | private WorkspaceKeys() { } | |
| 98 | } | |
| 1 | 99 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preferences; | |
| 3 | ||
| 4 | import com.dlsc.preferencesfx.PreferencesFx; | |
| 5 | import com.dlsc.preferencesfx.util.StorageHandler; | |
| 6 | import javafx.collections.ObservableList; | |
| 7 | ||
| 8 | import java.util.prefs.Preferences; | |
| 9 | ||
| 10 | /** | |
| 11 | * Prevents {@link PreferencesFx} from saving. Saving and loading preferences | |
| 12 | * and application window state is accomplished by the {@link Workspace}. | |
| 13 | * <p> | |
| 14 | * This implies that undo/redo functionality must be disabled because the | |
| 15 | * {@link Workspace} does not preserve previous states. | |
| 16 | * </p> | |
| 17 | */ | |
| 18 | public final class XmlStorageHandler implements StorageHandler { | |
| 19 | @Override | |
| 20 | public void saveSelectedCategory( final String breadcrumb ) { } | |
| 21 | ||
| 22 | @Override | |
| 23 | public String loadSelectedCategory() { | |
| 24 | return ""; | |
| 25 | } | |
| 26 | ||
| 27 | @Override | |
| 28 | public void saveDividerPosition( final double dividerPosition ) { | |
| 29 | } | |
| 30 | ||
| 31 | @Override | |
| 32 | public double loadDividerPosition() { | |
| 33 | return 0; | |
| 34 | } | |
| 35 | ||
| 36 | @Override | |
| 37 | public void saveWindowWidth( final double windowWidth ) { } | |
| 38 | ||
| 39 | @Override | |
| 40 | public double loadWindowWidth() { | |
| 41 | return 0; | |
| 42 | } | |
| 43 | ||
| 44 | @Override | |
| 45 | public void saveWindowHeight( final double windowHeight ) { } | |
| 46 | ||
| 47 | @Override | |
| 48 | public double loadWindowHeight() { | |
| 49 | return 0; | |
| 50 | } | |
| 51 | ||
| 52 | @Override | |
| 53 | public void saveWindowPosX( final double windowPosX ) { } | |
| 54 | ||
| 55 | @Override | |
| 56 | public double loadWindowPosX() { | |
| 57 | return 0; | |
| 58 | } | |
| 59 | ||
| 60 | @Override | |
| 61 | public void saveWindowPosY( final double windowPosY ) { } | |
| 62 | ||
| 63 | @Override | |
| 64 | public double loadWindowPosY() { | |
| 65 | return 0; | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | public void saveObject( final String breadcrumb, final Object object ) { } | |
| 70 | ||
| 71 | @Override | |
| 72 | public Object loadObject( | |
| 73 | final String breadcrumb, final Object defaultObject ) { | |
| 74 | return defaultObject; | |
| 75 | } | |
| 76 | ||
| 77 | @Override | |
| 78 | public <T> T loadObject( | |
| 79 | final String breadcrumb, final Class<T> type, final T defaultObject ) { | |
| 80 | return defaultObject; | |
| 81 | } | |
| 82 | ||
| 83 | @Override | |
| 84 | @SuppressWarnings("rawtypes") | |
| 85 | public ObservableList loadObservableList( | |
| 86 | final String breadcrumb, final ObservableList defaultObservableList ) { | |
| 87 | return defaultObservableList; | |
| 88 | } | |
| 89 | ||
| 90 | @Override | |
| 91 | public <T> ObservableList<T> loadObservableList( | |
| 92 | final String breadcrumb, | |
| 93 | final Class<T> type, | |
| 94 | final ObservableList<T> defaultObservableList ) { | |
| 95 | return defaultObservableList; | |
| 96 | } | |
| 97 | ||
| 98 | @Override | |
| 99 | public boolean clearPreferences() { | |
| 100 | return false; | |
| 101 | } | |
| 102 | ||
| 103 | @Override | |
| 104 | public Preferences getPreferences() { | |
| 105 | return null; | |
| 106 | } | |
| 107 | } | |
| 1 | 108 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preview; | |
| 3 | ||
| 4 | import com.keenwrite.ui.adapters.ReplacedElementAdapter; | |
| 5 | import com.keenwrite.util.BoundedCache; | |
| 6 | import org.w3c.dom.Element; | |
| 7 | import org.xhtmlrenderer.extend.ReplacedElement; | |
| 8 | import org.xhtmlrenderer.extend.ReplacedElementFactory; | |
| 9 | import org.xhtmlrenderer.extend.UserAgentCallback; | |
| 10 | import org.xhtmlrenderer.layout.LayoutContext; | |
| 11 | import org.xhtmlrenderer.render.BlockBox; | |
| 12 | import org.xhtmlrenderer.swing.ImageReplacedElement; | |
| 13 | ||
| 14 | import java.util.LinkedHashSet; | |
| 15 | import java.util.Map; | |
| 16 | import java.util.Set; | |
| 17 | ||
| 18 | import static com.keenwrite.preview.SvgReplacedElementFactory.HTML_IMAGE; | |
| 19 | import static com.keenwrite.preview.SvgReplacedElementFactory.HTML_IMAGE_SRC; | |
| 20 | import static com.keenwrite.processors.markdown.extensions.tex.TexNode.HTML_TEX; | |
| 21 | import static java.lang.Math.min; | |
| 22 | import static java.util.Arrays.asList; | |
| 23 | ||
| 24 | /** | |
| 25 | * Responsible for running one or more factories to perform post-processing on | |
| 26 | * the HTML document prior to displaying it. | |
| 27 | */ | |
| 28 | public final class ChainedReplacedElementFactory | |
| 29 | extends ReplacedElementAdapter { | |
| 30 | /** | |
| 31 | * Retain insertion order so that client classes can control the order that | |
| 32 | * factories are used to resolve images. | |
| 33 | */ | |
| 34 | private final Set<ReplacedElementFactory> mFactories = new LinkedHashSet<>(); | |
| 35 | ||
| 36 | /** | |
| 37 | * A bounded cache that removes the oldest image if the maximum number of | |
| 38 | * cached images has been reached. This constrains the number of images | |
| 39 | * loaded into memory. | |
| 40 | */ | |
| 41 | private final Map<String, ReplacedElement> mCache = new BoundedCache<>( 150 ); | |
| 42 | ||
| 43 | public ChainedReplacedElementFactory( | |
| 44 | final ReplacedElementFactory... factories ) { | |
| 45 | assert factories != null; | |
| 46 | assert factories.length > 0; | |
| 47 | mFactories.addAll( asList( factories ) ); | |
| 48 | } | |
| 49 | ||
| 50 | @Override | |
| 51 | public ReplacedElement createReplacedElement( | |
| 52 | final LayoutContext c, | |
| 53 | final BlockBox box, | |
| 54 | final UserAgentCallback uac, | |
| 55 | final int width, | |
| 56 | final int height ) { | |
| 57 | for( final var f : mFactories ) { | |
| 58 | final var e = box.getElement(); | |
| 59 | ||
| 60 | // Exit early for super-speed. | |
| 61 | if( e == null ) { | |
| 62 | break; | |
| 63 | } | |
| 64 | ||
| 65 | // If the source image is cached, don't bother fetching. This optimization | |
| 66 | // avoids making multiple HTTP requests for the same URI. | |
| 67 | final var node = e.getNodeName(); | |
| 68 | final var source = switch( node ) { | |
| 69 | case HTML_IMAGE -> e.getAttribute( HTML_IMAGE_SRC ); | |
| 70 | case HTML_TEX -> e.getTextContent(); | |
| 71 | default -> ""; | |
| 72 | }; | |
| 73 | ||
| 74 | // HTML <img> or <tex> elements without source data shall not pass. | |
| 75 | if( source.isBlank() ) { | |
| 76 | break; | |
| 77 | } | |
| 78 | ||
| 79 | final var replaced = mCache.computeIfAbsent( | |
| 80 | source, k -> { | |
| 81 | final var r = f.createReplacedElement( c, box, uac, width, height ); | |
| 82 | return r instanceof final ImageReplacedElement ire | |
| 83 | ? createImageElement( box, ire ) | |
| 84 | : r; | |
| 85 | } | |
| 86 | ); | |
| 87 | ||
| 88 | if( replaced != null ) { | |
| 89 | return replaced; | |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | return null; | |
| 94 | } | |
| 95 | ||
| 96 | @Override | |
| 97 | public void reset() { | |
| 98 | for( final var factory : mFactories ) { | |
| 99 | factory.reset(); | |
| 100 | } | |
| 101 | } | |
| 102 | ||
| 103 | @Override | |
| 104 | public void remove( final Element element ) { | |
| 105 | for( final var factory : mFactories ) { | |
| 106 | factory.remove( element ); | |
| 107 | } | |
| 108 | } | |
| 109 | ||
| 110 | public void addFactory( final ReplacedElementFactory factory ) { | |
| 111 | mFactories.add( factory ); | |
| 112 | } | |
| 113 | ||
| 114 | public void clearCache() { | |
| 115 | mCache.clear(); | |
| 116 | } | |
| 117 | ||
| 118 | /** | |
| 119 | * Creates a new image that maintains its aspect ratio while fitting into | |
| 120 | * the given {@link BlockBox}. If the image is too big, it is scaled down. | |
| 121 | * | |
| 122 | * @param box The bounding region the image must fit into. | |
| 123 | * @param ire The image to resize. | |
| 124 | * @return An image that is scaled down to fit, but only if necessary. | |
| 125 | */ | |
| 126 | private SmoothImageReplacedElement createImageElement( | |
| 127 | final BlockBox box, final ImageReplacedElement ire ) { | |
| 128 | return new SmoothImageReplacedElement( | |
| 129 | ire.getImage(), min( ire.getIntrinsicWidth(), box.getWidth() ), -1 ); | |
| 130 | } | |
| 131 | } | |
| 1 | 132 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preview; | |
| 3 | ||
| 4 | import java.util.HashMap; | |
| 5 | import java.util.Map; | |
| 6 | ||
| 7 | import static java.awt.RenderingHints.*; | |
| 8 | import static java.awt.Toolkit.getDefaultToolkit; | |
| 9 | ||
| 10 | /** | |
| 11 | * Responsible for initializing settings to produce high-quality image | |
| 12 | * transformations. | |
| 13 | */ | |
| 14 | @SuppressWarnings( "rawtypes" ) | |
| 15 | public class HighQualityRenderingHints { | |
| 16 | /** | |
| 17 | * Default hints for high-quality rendering that may be changed by | |
| 18 | * the system's rendering hints. | |
| 19 | */ | |
| 20 | private static final Map<Object, Object> DEFAULT_HINTS = Map.of( | |
| 21 | KEY_ANTIALIASING, VALUE_ANTIALIAS_ON, | |
| 22 | KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_QUALITY, | |
| 23 | KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY, | |
| 24 | KEY_DITHERING, VALUE_DITHER_DISABLE, | |
| 25 | KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON, | |
| 26 | KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC, | |
| 27 | KEY_RENDERING, VALUE_RENDER_QUALITY, | |
| 28 | KEY_STROKE_CONTROL, VALUE_STROKE_PURE, | |
| 29 | KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON | |
| 30 | ); | |
| 31 | ||
| 32 | /** | |
| 33 | * Shared hints for high-quality rendering. | |
| 34 | */ | |
| 35 | static final Map<Object, Object> RENDERING_HINTS = new HashMap<>( | |
| 36 | DEFAULT_HINTS | |
| 37 | ); | |
| 38 | ||
| 39 | static { | |
| 40 | final var toolkit = getDefaultToolkit(); | |
| 41 | final var hints = toolkit.getDesktopProperty( "awt.font.desktophints" ); | |
| 42 | ||
| 43 | if( hints instanceof final Map map ) { | |
| 44 | for( final var key : map.keySet() ) { | |
| 45 | final var hint = map.get( key ); | |
| 46 | RENDERING_HINTS.put( key, hint ); | |
| 47 | } | |
| 48 | } | |
| 49 | } | |
| 50 | ||
| 51 | /** | |
| 52 | * Defines a reusable constant, nothing more. | |
| 53 | */ | |
| 54 | private HighQualityRenderingHints() { | |
| 55 | } | |
| 56 | } | |
| 1 | 57 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preview; | |
| 3 | ||
| 4 | import com.keenwrite.dom.DocumentConverter; | |
| 5 | import com.keenwrite.ui.adapters.DocumentAdapter; | |
| 6 | import javafx.beans.property.BooleanProperty; | |
| 7 | import javafx.beans.property.SimpleBooleanProperty; | |
| 8 | import org.xhtmlrenderer.layout.SharedContext; | |
| 9 | import org.xhtmlrenderer.render.Box; | |
| 10 | import org.xhtmlrenderer.simple.XHTMLPanel; | |
| 11 | import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler; | |
| 12 | import org.xhtmlrenderer.swing.BasicPanel; | |
| 13 | import org.xhtmlrenderer.swing.FSMouseListener; | |
| 14 | import org.xhtmlrenderer.swing.HoverListener; | |
| 15 | import org.xhtmlrenderer.swing.LinkListener; | |
| 16 | ||
| 17 | import java.awt.event.ComponentAdapter; | |
| 18 | import java.awt.event.ComponentEvent; | |
| 19 | import java.net.URI; | |
| 20 | ||
| 21 | import static com.keenwrite.events.DocumentChangedEvent.fireDocumentChangedEvent; | |
| 22 | import static com.keenwrite.events.FileOpenEvent.fireFileOpenEvent; | |
| 23 | import static com.keenwrite.events.HyperlinkOpenEvent.fireHyperlinkOpenEvent; | |
| 24 | import static com.keenwrite.events.StatusEvent.clue; | |
| 25 | import static com.keenwrite.util.ProtocolScheme.getProtocol; | |
| 26 | import static java.lang.Boolean.FALSE; | |
| 27 | import static java.lang.Boolean.TRUE; | |
| 28 | import static javax.swing.SwingUtilities.invokeLater; | |
| 29 | import static javax.swing.SwingUtilities.isEventDispatchThread; | |
| 30 | import static org.jsoup.Jsoup.parse; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for configuring FlyingSaucer's {@link XHTMLPanel}. | |
| 34 | */ | |
| 35 | public final class HtmlPanel extends XHTMLPanel { | |
| 36 | ||
| 37 | /** | |
| 38 | * Suppresses scroll attempts until after the document has loaded. | |
| 39 | */ | |
| 40 | private static final class DocumentEventHandler extends DocumentAdapter { | |
| 41 | private final BooleanProperty mReadyProperty = new SimpleBooleanProperty(); | |
| 42 | ||
| 43 | @Override | |
| 44 | public void documentStarted() { | |
| 45 | mReadyProperty.setValue( FALSE ); | |
| 46 | } | |
| 47 | ||
| 48 | @Override | |
| 49 | public void documentLoaded() { | |
| 50 | mReadyProperty.setValue( TRUE ); | |
| 51 | } | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Ensures that the preview panel fills its container's area completely. | |
| 56 | */ | |
| 57 | private final class ComponentEventHandler extends ComponentAdapter { | |
| 58 | /** | |
| 59 | * Invoked when the component's size changes. | |
| 60 | */ | |
| 61 | public void componentResized( final ComponentEvent e ) { | |
| 62 | setPreferredSize( e.getComponent().getPreferredSize() ); | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Responsible for opening hyperlinks. External hyperlinks are opened in | |
| 68 | * the system's default browser; local file system links are opened in the | |
| 69 | * editor. | |
| 70 | */ | |
| 71 | private static final class HyperlinkListener extends LinkListener { | |
| 72 | @Override | |
| 73 | public void linkClicked( final BasicPanel panel, final String link ) { | |
| 74 | try { | |
| 75 | final var uri = new URI( link ); | |
| 76 | ||
| 77 | switch( getProtocol( uri ) ) { | |
| 78 | case HTTP -> fireHyperlinkOpenEvent( uri ); | |
| 79 | case FILE -> fireFileOpenEvent( uri ); | |
| 80 | } | |
| 81 | } catch( final Exception ex ) { | |
| 82 | clue( ex ); | |
| 83 | } | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | private static final DocumentConverter CONVERTER = new DocumentConverter(); | |
| 88 | private static final XhtmlNamespaceHandler XNH = new XhtmlNamespaceHandler(); | |
| 89 | ||
| 90 | public HtmlPanel() { | |
| 91 | addDocumentListener( new DocumentEventHandler() ); | |
| 92 | removeMouseTrackingListeners(); | |
| 93 | addMouseTrackingListener( new HyperlinkListener() ); | |
| 94 | addComponentListener( new ComponentEventHandler() ); | |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * Updates the document model displayed by the renderer. Effectively, this | |
| 99 | * updates the HTML document to provide new content. | |
| 100 | * | |
| 101 | * @param html A complete HTML5 document, including doctype. | |
| 102 | * @param baseUri URI to use for finding relative files, such as images. | |
| 103 | */ | |
| 104 | public void render( final String html, final String baseUri ) { | |
| 105 | final var soup = parse( html ); | |
| 106 | final var doc = CONVERTER.fromJsoup( soup ); | |
| 107 | final Runnable renderDocument = () -> setDocument( doc, baseUri, XNH ); | |
| 108 | doc.setDocumentURI( baseUri ); | |
| 109 | ||
| 110 | // Access to a Swing component must occur from the Event Dispatch | |
| 111 | // Thread (EDT) according to Swing threading restrictions. Setting a new | |
| 112 | // document invokes a Swing repaint operation. | |
| 113 | if( isEventDispatchThread() ) { | |
| 114 | renderDocument.run(); | |
| 115 | } | |
| 116 | else { | |
| 117 | invokeLater( renderDocument ); | |
| 118 | } | |
| 119 | ||
| 120 | // When the text changes, let subscribers know. This allows for text | |
| 121 | // analysis to occur on a separate thread. | |
| 122 | fireDocumentChangedEvent( soup ); | |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * Delegates to the {@link SharedContext}. | |
| 127 | * | |
| 128 | * @param id The HTML element identifier to retrieve in {@link Box} form. | |
| 129 | * @return The {@link Box} that corresponds to the given element ID, or | |
| 130 | * {@code null} if none found. | |
| 131 | */ | |
| 132 | public Box getBoxById( final String id ) { | |
| 133 | return getSharedContext().getBoxById( id ); | |
| 134 | } | |
| 135 | ||
| 136 | /** | |
| 137 | * Suppress scrolling to the top on updates. | |
| 138 | */ | |
| 139 | @Override | |
| 140 | public void resetScrollPosition() { | |
| 141 | } | |
| 142 | ||
| 143 | /** | |
| 144 | * The default mouse click listener attempts navigation within the preview | |
| 145 | * panel. We want to usurp that behaviour to open the link in a | |
| 146 | * platform-specific browser. | |
| 147 | */ | |
| 148 | private void removeMouseTrackingListeners() { | |
| 149 | for( final var listener : getMouseTrackingListeners() ) { | |
| 150 | if( !(listener instanceof HoverListener) ) { | |
| 151 | removeMouseTrackingListener( (FSMouseListener) listener ); | |
| 152 | } | |
| 153 | } | |
| 154 | } | |
| 155 | } | |
| 1 | 156 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preview; | |
| 3 | ||
| 4 | import com.keenwrite.events.ScrollLockEvent; | |
| 5 | import com.keenwrite.preferences.LocaleProperty; | |
| 6 | import com.keenwrite.preferences.Workspace; | |
| 7 | import javafx.beans.property.DoubleProperty; | |
| 8 | import javafx.beans.property.StringProperty; | |
| 9 | import javafx.embed.swing.SwingNode; | |
| 10 | import org.greenrobot.eventbus.Subscribe; | |
| 11 | import org.xhtmlrenderer.render.Box; | |
| 12 | import org.xhtmlrenderer.swing.SwingReplacedElementFactory; | |
| 13 | ||
| 14 | import javax.swing.*; | |
| 15 | import java.awt.*; | |
| 16 | import java.awt.event.ComponentEvent; | |
| 17 | import java.awt.event.ComponentListener; | |
| 18 | import java.net.URL; | |
| 19 | import java.nio.file.Path; | |
| 20 | import java.util.Locale; | |
| 21 | ||
| 22 | import static com.keenwrite.Messages.get; | |
| 23 | import static com.keenwrite.constants.Constants.*; | |
| 24 | import static com.keenwrite.events.Bus.register; | |
| 25 | import static com.keenwrite.events.ScrollLockEvent.fireScrollLockEvent; | |
| 26 | import static com.keenwrite.events.StatusEvent.clue; | |
| 27 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 28 | import static com.keenwrite.ui.fonts.IconFactory.getIconFont; | |
| 29 | import static java.awt.BorderLayout.*; | |
| 30 | import static java.awt.event.KeyEvent.*; | |
| 31 | import static java.lang.Math.max; | |
| 32 | import static java.lang.String.format; | |
| 33 | import static java.lang.Thread.sleep; | |
| 34 | import static javafx.scene.CacheHint.SPEED; | |
| 35 | import static javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW; | |
| 36 | import static javax.swing.KeyStroke.getKeyStroke; | |
| 37 | import static javax.swing.SwingUtilities.invokeLater; | |
| 38 | import static org.controlsfx.glyphfont.FontAwesome.Glyph.LOCK; | |
| 39 | import static org.controlsfx.glyphfont.FontAwesome.Glyph.UNLOCK_ALT; | |
| 40 | ||
| 41 | /** | |
| 42 | * Responsible for parsing an HTML document. | |
| 43 | */ | |
| 44 | public final class HtmlPreview extends SwingNode implements ComponentListener { | |
| 45 | /** | |
| 46 | * Used to populate the {@link #HTML_HEAD} with stylesheet file references. | |
| 47 | */ | |
| 48 | private static final String HTML_STYLESHEET = | |
| 49 | "<link rel='stylesheet' href='%s'/>"; | |
| 50 | ||
| 51 | private static final String HTML_BASE = | |
| 52 | "<base href='%s'/>"; | |
| 53 | ||
| 54 | /** | |
| 55 | * Render CSS using points (pt) not pixels (px) to reduce the chance of | |
| 56 | * poor rendering. The {@link #generateHead()} method fills placeholders. | |
| 57 | * When the user has not set a locale, only one stylesheet is added to | |
| 58 | * the document. In order, the placeholders are as follows: | |
| 59 | * <ol> | |
| 60 | * <li>%s --- language</li> | |
| 61 | * <li>%s --- default stylesheet</li> | |
| 62 | * <li>%s --- language-specific stylesheet</li> | |
| 63 | * <li>%s --- font family</li> | |
| 64 | * <li>%d --- font size (must be pixels, not points due to bug)</li> | |
| 65 | * <li>%s --- base href</li> | |
| 66 | * </p> | |
| 67 | */ | |
| 68 | private static final String HTML_HEAD = | |
| 69 | """ | |
| 70 | <!doctype html> | |
| 71 | <html lang='%s'><head><title> </title><meta charset='utf-8'/> | |
| 72 | %s%s<style>body{font-family:'%s';font-size: %dpx;}</style>%s</head><body> | |
| 73 | """; | |
| 74 | ||
| 75 | private static final String HTML_TAIL = "</body></html>"; | |
| 76 | ||
| 77 | private static final URL HTML_STYLE_PREVIEW = toUrl( STYLESHEET_PREVIEW ); | |
| 78 | ||
| 79 | private final ChainedReplacedElementFactory mFactory; | |
| 80 | ||
| 81 | /** | |
| 82 | * Reusing this buffer prevents repetitious memory re-allocations. | |
| 83 | */ | |
| 84 | private final StringBuilder mDocument = new StringBuilder( 65536 ); | |
| 85 | ||
| 86 | private HtmlPanel mView; | |
| 87 | private JScrollPane mScrollPane; | |
| 88 | private String mBaseUriPath = ""; | |
| 89 | private String mHead = ""; | |
| 90 | ||
| 91 | private volatile boolean mLocked; | |
| 92 | private final JButton mScrollLockButton = new JButton(); | |
| 93 | private final Workspace mWorkspace; | |
| 94 | ||
| 95 | /** | |
| 96 | * Creates a new preview pane that can scroll to the caret position within the | |
| 97 | * document. | |
| 98 | * | |
| 99 | * @param workspace Contains locale and font size information. | |
| 100 | */ | |
| 101 | public HtmlPreview( final Workspace workspace ) { | |
| 102 | mWorkspace = workspace; | |
| 103 | ||
| 104 | // The order is important: SwingReplacedElementFactory replaces SVG images | |
| 105 | // with a blank image, which will cause the chained factory to cache the | |
| 106 | // image and exit. Instead, the SVG must execute first to rasterize the | |
| 107 | // content. Consequently, the chained factory must maintain insertion order. | |
| 108 | mFactory = new ChainedReplacedElementFactory( | |
| 109 | new SvgReplacedElementFactory(), | |
| 110 | new SwingReplacedElementFactory() | |
| 111 | ); | |
| 112 | ||
| 113 | // Attempts to prevent a flash of black un-styled content upon load. | |
| 114 | setStyle( "-fx-background-color: white;" ); | |
| 115 | ||
| 116 | invokeLater( () -> { | |
| 117 | mHead = generateHead(); | |
| 118 | mView = new HtmlPanel(); | |
| 119 | mScrollPane = new JScrollPane( mView ); | |
| 120 | final var verticalBar = mScrollPane.getVerticalScrollBar(); | |
| 121 | final var verticalPanel = new JPanel( new BorderLayout() ); | |
| 122 | ||
| 123 | final var map = verticalBar.getInputMap( WHEN_IN_FOCUSED_WINDOW ); | |
| 124 | addKeyboardEvents( map ); | |
| 125 | ||
| 126 | mScrollLockButton.setFont( getIconFont( 14 ) ); | |
| 127 | mScrollLockButton.setText( getLockText( mLocked ) ); | |
| 128 | mScrollLockButton.setMargin( new Insets( 1, 0, 0, 0 ) ); | |
| 129 | mScrollLockButton.addActionListener( e -> fireScrollLockEvent( !mLocked ) ); | |
| 130 | ||
| 131 | verticalPanel.add( verticalBar, CENTER ); | |
| 132 | verticalPanel.add( mScrollLockButton, PAGE_END ); | |
| 133 | ||
| 134 | final var wrapper = new JPanel( new BorderLayout() ); | |
| 135 | wrapper.add( mScrollPane, CENTER ); | |
| 136 | wrapper.add( verticalPanel, LINE_END ); | |
| 137 | ||
| 138 | // Enabling the cache attempts to prevent black flashes when resizing. | |
| 139 | setCache( true ); | |
| 140 | setCacheHint( SPEED ); | |
| 141 | setContent( wrapper ); | |
| 142 | wrapper.addComponentListener( this ); | |
| 143 | ||
| 144 | final var context = mView.getSharedContext(); | |
| 145 | final var textRenderer = context.getTextRenderer(); | |
| 146 | context.setReplacedElementFactory( mFactory ); | |
| 147 | textRenderer.setSmoothingThreshold( 0 ); | |
| 148 | ||
| 149 | localeProperty().addListener( ( c, o, n ) -> rerender() ); | |
| 150 | fontFamilyProperty().addListener( ( c, o, n ) -> rerender() ); | |
| 151 | fontSizeProperty().addListener( ( c, o, n ) -> rerender() ); | |
| 152 | } ); | |
| 153 | ||
| 154 | register( this ); | |
| 155 | } | |
| 156 | ||
| 157 | @Subscribe | |
| 158 | public void handle( final ScrollLockEvent event ) { | |
| 159 | mLocked = event.isLocked(); | |
| 160 | invokeLater( () -> mScrollLockButton.setText( getLockText( mLocked ) ) ); | |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Updates the internal HTML source shown in the preview pane. | |
| 165 | * | |
| 166 | * @param html The new HTML document to display. | |
| 167 | */ | |
| 168 | public void render( final String html ) { | |
| 169 | mView.render( decorate( html ), getBaseUri() ); | |
| 170 | } | |
| 171 | ||
| 172 | /** | |
| 173 | * Clears the caches then re-renders the content. | |
| 174 | */ | |
| 175 | public void refresh() { | |
| 176 | mFactory.clearCache(); | |
| 177 | rerender(); | |
| 178 | } | |
| 179 | ||
| 180 | /** | |
| 181 | * Recomputes the HTML head then renders the document. | |
| 182 | */ | |
| 183 | private void rerender() { | |
| 184 | mHead = generateHead(); | |
| 185 | render( mDocument.toString() ); | |
| 186 | } | |
| 187 | ||
| 188 | /** | |
| 189 | * Attaches the HTML head prefix and HTML tail suffix to the given HTML | |
| 190 | * string. | |
| 191 | * | |
| 192 | * @param html The HTML to adorn with opening and closing tags. | |
| 193 | * @return A complete HTML document, ready for rendering. | |
| 194 | */ | |
| 195 | private String decorate( final String html ) { | |
| 196 | mDocument.setLength( 0 ); | |
| 197 | mDocument.append( html ); | |
| 198 | ||
| 199 | // Head and tail must be separate from document due to re-rendering. | |
| 200 | return mHead + mDocument + HTML_TAIL; | |
| 201 | } | |
| 202 | ||
| 203 | /** | |
| 204 | * Called when settings are changed that affect the HTML document preamble. | |
| 205 | * This is a minor performance optimization to avoid generating the head | |
| 206 | * each time that the document itself changes. | |
| 207 | * | |
| 208 | * @return A new doctype and HTML {@code head} element. | |
| 209 | */ | |
| 210 | private String generateHead() { | |
| 211 | final var locale = getLocale(); | |
| 212 | final var url = toUrl( locale ); | |
| 213 | final var base = getBaseUri(); | |
| 214 | ||
| 215 | // Point sizes are converted to pixels because of a rendering bug. | |
| 216 | return format( | |
| 217 | HTML_HEAD, | |
| 218 | locale.getLanguage(), | |
| 219 | format( HTML_STYLESHEET, HTML_STYLE_PREVIEW ), | |
| 220 | url == null ? "" : format( HTML_STYLESHEET, url ), | |
| 221 | getFontFamily(), | |
| 222 | toPixels( getFontSize() ), | |
| 223 | base.isBlank() ? "" : format( HTML_BASE, base ) | |
| 224 | ); | |
| 225 | } | |
| 226 | ||
| 227 | /** | |
| 228 | * Clears the preview pane by rendering an empty string. | |
| 229 | */ | |
| 230 | public void clear() { | |
| 231 | render( "" ); | |
| 232 | } | |
| 233 | ||
| 234 | /** | |
| 235 | * Sets the base URI to the containing directory the file being edited. | |
| 236 | * | |
| 237 | * @param path The path to the file being edited. | |
| 238 | */ | |
| 239 | public void setBaseUri( final Path path ) { | |
| 240 | final var parent = path.getParent(); | |
| 241 | mBaseUriPath = parent == null ? "" : parent.toUri().toString(); | |
| 242 | } | |
| 243 | ||
| 244 | /** | |
| 245 | * Scrolls to the closest element matching the given identifier without | |
| 246 | * waiting for the document to be ready. | |
| 247 | * | |
| 248 | * @param id Scroll the preview pane to this unique paragraph identifier. | |
| 249 | */ | |
| 250 | public void scrollTo( final String id ) { | |
| 251 | if( mLocked ) { | |
| 252 | return; | |
| 253 | } | |
| 254 | ||
| 255 | invokeLater( () -> { | |
| 256 | int iter = 0; | |
| 257 | Box box = null; | |
| 258 | ||
| 259 | while( iter++ < 3 && ((box = mView.getBoxById( id )) == null) ) { | |
| 260 | try { | |
| 261 | sleep( 10 ); | |
| 262 | } catch( final Exception ex ) { | |
| 263 | clue( ex ); | |
| 264 | } | |
| 265 | } | |
| 266 | ||
| 267 | scrollTo( box ); | |
| 268 | } ); | |
| 269 | } | |
| 270 | ||
| 271 | /** | |
| 272 | * Scrolls to the location specified by the {@link Box} that corresponds | |
| 273 | * to a point somewhere in the preview pane. If there is no caret, then | |
| 274 | * this will not change the scroll position. Changing the scroll position | |
| 275 | * to the top if the {@link Box} instance is {@code null} will result in | |
| 276 | * jumping around a lot and inconsistent synchronization issues. | |
| 277 | * | |
| 278 | * @param box The rectangular region containing the caret, or {@code null} | |
| 279 | * if the HTML does not have a caret. | |
| 280 | */ | |
| 281 | private void scrollTo( final Box box ) { | |
| 282 | if( box != null ) { | |
| 283 | invokeLater( () -> { | |
| 284 | mView.scrollTo( createPoint( box ) ); | |
| 285 | getScrollPane().repaint(); | |
| 286 | } ); | |
| 287 | } | |
| 288 | } | |
| 289 | ||
| 290 | /** | |
| 291 | * Creates a {@link Point} to use as a reference for scrolling to the area | |
| 292 | * described by the given {@link Box}. The {@link Box} coordinates are used | |
| 293 | * to populate the {@link Point}'s location, with minor adjustments for | |
| 294 | * vertical centering. | |
| 295 | * | |
| 296 | * @param box The {@link Box} that represents a scrolling anchor reference. | |
| 297 | * @return A coordinate suitable for scrolling to. | |
| 298 | */ | |
| 299 | private Point createPoint( final Box box ) { | |
| 300 | assert box != null; | |
| 301 | ||
| 302 | // Scroll back up by half the height of the scroll bar to keep the typing | |
| 303 | // area within the view port. Otherwise the view port will have jumped too | |
| 304 | // high up and the most recently typed letters won't be visible. | |
| 305 | int y = max( box.getAbsY() - getVerticalScrollBarHeight() / 2, 0 ); | |
| 306 | int x = box.getAbsX(); | |
| 307 | ||
| 308 | if( !box.getStyle().isInline() ) { | |
| 309 | final var margin = box.getMargin( mView.getLayoutContext() ); | |
| 310 | y += margin.top(); | |
| 311 | x += margin.left(); | |
| 312 | } | |
| 313 | ||
| 314 | return new Point( x, y ); | |
| 315 | } | |
| 316 | ||
| 317 | private String getBaseUri() { | |
| 318 | return mBaseUriPath; | |
| 319 | } | |
| 320 | ||
| 321 | private JScrollPane getScrollPane() { | |
| 322 | return mScrollPane; | |
| 323 | } | |
| 324 | ||
| 325 | public JScrollBar getVerticalScrollBar() { | |
| 326 | return getScrollPane().getVerticalScrollBar(); | |
| 327 | } | |
| 328 | ||
| 329 | private int getVerticalScrollBarHeight() { | |
| 330 | return getVerticalScrollBar().getHeight(); | |
| 331 | } | |
| 332 | ||
| 333 | /** | |
| 334 | * Returns the ISO 639 alpha-2 or alpha-3 language code followed by a hyphen | |
| 335 | * followed by the ISO 15924 alpha-4 script code, followed by an ISO 3166 | |
| 336 | * alpha-2 country code or UN M.49 numeric-3 area code. For example, this | |
| 337 | * could return "en-Latn-CA" for Canadian English written in the Latin | |
| 338 | * character set. | |
| 339 | * | |
| 340 | * @return Unique identifier for language and country. | |
| 341 | */ | |
| 342 | private static URL toUrl( final Locale locale ) { | |
| 343 | return toUrl( | |
| 344 | get( | |
| 345 | sSettings.getSetting( STYLESHEET_PREVIEW_LOCALE, "" ), | |
| 346 | locale.getLanguage(), | |
| 347 | locale.getScript(), | |
| 348 | locale.getCountry() | |
| 349 | ) | |
| 350 | ); | |
| 351 | } | |
| 352 | ||
| 353 | private static URL toUrl( final String path ) { | |
| 354 | return HtmlPreview.class.getResource( path ); | |
| 355 | } | |
| 356 | ||
| 357 | private Locale getLocale() { | |
| 358 | return localeProperty().toLocale(); | |
| 359 | } | |
| 360 | ||
| 361 | private LocaleProperty localeProperty() { | |
| 362 | return mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE ); | |
| 363 | } | |
| 364 | ||
| 365 | private String getFontFamily() { | |
| 366 | return fontFamilyProperty().get(); | |
| 367 | } | |
| 368 | ||
| 369 | private StringProperty fontFamilyProperty() { | |
| 370 | return mWorkspace.stringProperty( KEY_UI_FONT_PREVIEW_NAME ); | |
| 371 | } | |
| 372 | ||
| 373 | private double getFontSize() { | |
| 374 | return fontSizeProperty().get(); | |
| 375 | } | |
| 376 | ||
| 377 | /** | |
| 378 | * Returns the font size in points. | |
| 379 | * | |
| 380 | * @return The user-defined font size (in pt). | |
| 381 | */ | |
| 382 | private DoubleProperty fontSizeProperty() { | |
| 383 | return mWorkspace.doubleProperty( KEY_UI_FONT_PREVIEW_SIZE ); | |
| 384 | } | |
| 385 | ||
| 386 | private String getLockText( final boolean locked ) { | |
| 387 | return Character.toString( (locked ? LOCK : UNLOCK_ALT).getChar() ); | |
| 388 | } | |
| 389 | ||
| 390 | /** | |
| 391 | * Maps keyboard events to scrollbar commands so that users may control | |
| 392 | * the {@link HtmlPreview} panel using the keyboard. | |
| 393 | * | |
| 394 | * @param map The map to update with keyboard events. | |
| 395 | */ | |
| 396 | private void addKeyboardEvents( final InputMap map ) { | |
| 397 | map.put( getKeyStroke( VK_DOWN, 0 ), "positiveUnitIncrement" ); | |
| 398 | map.put( getKeyStroke( VK_UP, 0 ), "negativeUnitIncrement" ); | |
| 399 | map.put( getKeyStroke( VK_PAGE_DOWN, 0 ), "positiveBlockIncrement" ); | |
| 400 | map.put( getKeyStroke( VK_PAGE_UP, 0 ), "negativeBlockIncrement" ); | |
| 401 | map.put( getKeyStroke( VK_HOME, 0 ), "minScroll" ); | |
| 402 | map.put( getKeyStroke( VK_END, 0 ), "maxScroll" ); | |
| 403 | } | |
| 404 | ||
| 405 | @Override | |
| 406 | public void componentResized( final ComponentEvent e ) { | |
| 407 | if( mWorkspace.toBoolean( KEY_IMAGES_RESIZE ) ) { | |
| 408 | mFactory.clearCache(); | |
| 409 | } | |
| 410 | ||
| 411 | // Force update on the Swing EDT, otherwise the scrollbar and content | |
| 412 | // will not be updated correctly on some platforms. | |
| 413 | invokeLater( () -> getContent().repaint() ); | |
| 414 | } | |
| 415 | ||
| 416 | @Override | |
| 417 | public void componentMoved( final ComponentEvent e ) { } | |
| 418 | ||
| 419 | @Override | |
| 420 | public void componentShown( final ComponentEvent e ) { } | |
| 421 | ||
| 422 | @Override | |
| 423 | public void componentHidden( final ComponentEvent e ) { } | |
| 424 | } | |
| 1 | 425 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preview; | |
| 3 | ||
| 4 | import com.whitemagicsoftware.tex.*; | |
| 5 | import com.whitemagicsoftware.tex.graphics.SvgDomGraphics2D; | |
| 6 | import org.w3c.dom.Document; | |
| 7 | ||
| 8 | import java.util.function.Supplier; | |
| 9 | ||
| 10 | import static com.keenwrite.events.StatusEvent.clue; | |
| 11 | ||
| 12 | /** | |
| 13 | * Responsible for rendering formulas as scalable vector graphics (SVG). | |
| 14 | */ | |
| 15 | public final class MathRenderer { | |
| 16 | ||
| 17 | /** | |
| 18 | * Singleton instance for rendering math symbols. | |
| 19 | */ | |
| 20 | public static final MathRenderer MATH_RENDERER = new MathRenderer(); | |
| 21 | ||
| 22 | /** | |
| 23 | * Default font size in points. | |
| 24 | */ | |
| 25 | private static final float FONT_SIZE = 20f; | |
| 26 | ||
| 27 | private final TeXFont mTeXFont = createDefaultTeXFont( FONT_SIZE ); | |
| 28 | private final TeXEnvironment mEnvironment = createTeXEnvironment( mTeXFont ); | |
| 29 | private final SvgDomGraphics2D mGraphics = createSvgDomGraphics2D(); | |
| 30 | ||
| 31 | private MathRenderer() { | |
| 32 | mGraphics.scale( FONT_SIZE, FONT_SIZE ); | |
| 33 | } | |
| 34 | ||
| 35 | /** | |
| 36 | * This method only takes a few seconds to generate | |
| 37 | * | |
| 38 | * @param equation A mathematical expression to render. | |
| 39 | * @return The given string with all formulas transformed into SVG format. | |
| 40 | */ | |
| 41 | public Document render( final String equation ) { | |
| 42 | final var formula = new TeXFormula( equation ); | |
| 43 | final var box = formula.createBox( mEnvironment ); | |
| 44 | final var l = new TeXLayout( box, FONT_SIZE ); | |
| 45 | ||
| 46 | mGraphics.initialize( l.getWidth(), l.getHeight() ); | |
| 47 | box.draw( mGraphics, l.getX(), l.getY() ); | |
| 48 | return mGraphics.toDom(); | |
| 49 | } | |
| 50 | ||
| 51 | @SuppressWarnings("SameParameterValue") | |
| 52 | private TeXFont createDefaultTeXFont( final float fontSize ) { | |
| 53 | return create( () -> new DefaultTeXFont( fontSize ) ); | |
| 54 | } | |
| 55 | ||
| 56 | private TeXEnvironment createTeXEnvironment( final TeXFont texFont ) { | |
| 57 | return create( () -> new TeXEnvironment( texFont ) ); | |
| 58 | } | |
| 59 | ||
| 60 | private SvgDomGraphics2D createSvgDomGraphics2D() { | |
| 61 | return create( SvgDomGraphics2D::new ); | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Tries to instantiate a given object, returning {@code null} on failure. | |
| 66 | * The failure message is bubbled up to to the user interface. | |
| 67 | * | |
| 68 | * @param supplier Creates an instance. | |
| 69 | * @param <T> The type of instance being created. | |
| 70 | * @return An instance of the parameterized type or {@code null} upon error. | |
| 71 | */ | |
| 72 | private <T> T create( final Supplier<T> supplier ) { | |
| 73 | try { | |
| 74 | return supplier.get(); | |
| 75 | } catch( final Exception ex ) { | |
| 76 | clue( ex ); | |
| 77 | return null; | |
| 78 | } | |
| 79 | } | |
| 80 | } | |
| 1 | 81 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preview; | |
| 3 | ||
| 4 | import com.keenwrite.preview.images.Lanczos3Filter; | |
| 5 | import com.keenwrite.preview.images.ResampleOp; | |
| 6 | import org.xhtmlrenderer.swing.ImageReplacedElement; | |
| 7 | ||
| 8 | import java.awt.*; | |
| 9 | import java.awt.image.BufferedImage; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for scaling an image using a Lanczos-3 filter, typically for | |
| 13 | * down-sampling. | |
| 14 | */ | |
| 15 | public final class SmoothImageReplacedElement extends ImageReplacedElement { | |
| 16 | private final static Lanczos3Filter FILTER = new Lanczos3Filter(); | |
| 17 | ||
| 18 | /** | |
| 19 | * Creates a high-quality rescaled version of the given image. The | |
| 20 | * aspect ratio is maintained if either width or height is less than 1. | |
| 21 | * | |
| 22 | * @param source An instance of {@link BufferedImage} to rescale. | |
| 23 | * @param width Rescale the given image to this width (px). | |
| 24 | * @param height Rescale the given image to this height (px). | |
| 25 | */ | |
| 26 | public SmoothImageReplacedElement( | |
| 27 | final Image source, final int width, final int height ) { | |
| 28 | super._image = rescale( source, width, height ); | |
| 29 | } | |
| 30 | ||
| 31 | private BufferedImage rescale( | |
| 32 | final Image source, final int w, final int h ) { | |
| 33 | final var bi = (BufferedImage) source; | |
| 34 | final var dim = rescaleDimensions( bi, w, h ); | |
| 35 | ||
| 36 | final var resampleOp = new ResampleOp( FILTER, dim.width, dim.height ); | |
| 37 | return resampleOp.filter( bi, null ); | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * Calculates scaled dimensions while maintaining the image aspect ratio. | |
| 42 | */ | |
| 43 | private Dimension rescaleDimensions( | |
| 44 | final BufferedImage bi, final int width, final int height ) { | |
| 45 | final var oldW = bi.getWidth(); | |
| 46 | final var oldH = bi.getHeight(); | |
| 47 | ||
| 48 | int newW = width; | |
| 49 | int newH = height; | |
| 50 | ||
| 51 | if( newW <= 0 ) { | |
| 52 | newW = (int) (oldW * ((double) newH / oldH)); | |
| 53 | } | |
| 54 | ||
| 55 | if( newH <= 0 ) { | |
| 56 | newH = (int) (oldH * ((double) newW / oldW)); | |
| 57 | } | |
| 58 | ||
| 59 | return new Dimension( newW, newH ); | |
| 60 | } | |
| 61 | } | |
| 1 | 62 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preview; | |
| 3 | ||
| 4 | import org.apache.batik.anim.dom.SAXSVGDocumentFactory; | |
| 5 | import org.apache.batik.css.parser.Parser; | |
| 6 | import org.apache.batik.gvt.renderer.ImageRenderer; | |
| 7 | import org.apache.batik.transcoder.TranscoderException; | |
| 8 | import org.apache.batik.transcoder.TranscoderInput; | |
| 9 | import org.apache.batik.transcoder.TranscoderOutput; | |
| 10 | import org.apache.batik.transcoder.image.ImageTranscoder; | |
| 11 | import org.apache.batik.util.XMLResourceDescriptor; | |
| 12 | import org.w3c.css.sac.CSSException; | |
| 13 | import org.w3c.dom.Document; | |
| 14 | import org.w3c.dom.Element; | |
| 15 | ||
| 16 | import java.awt.*; | |
| 17 | import java.awt.image.BufferedImage; | |
| 18 | import java.io.File; | |
| 19 | import java.io.IOException; | |
| 20 | import java.io.InputStream; | |
| 21 | import java.io.StringReader; | |
| 22 | import java.net.URI; | |
| 23 | import java.nio.file.Path; | |
| 24 | import java.text.NumberFormat; | |
| 25 | import java.text.ParseException; | |
| 26 | ||
| 27 | import static com.keenwrite.dom.DocumentParser.transform; | |
| 28 | import static com.keenwrite.events.StatusEvent.clue; | |
| 29 | import static com.keenwrite.preview.HighQualityRenderingHints.RENDERING_HINTS; | |
| 30 | import static java.awt.image.BufferedImage.TYPE_INT_RGB; | |
| 31 | import static java.text.NumberFormat.getIntegerInstance; | |
| 32 | import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH; | |
| 33 | import static org.apache.batik.transcoder.image.ImageTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER; | |
| 34 | import static org.apache.batik.util.XMLResourceDescriptor.getXMLParserClassName; | |
| 35 | ||
| 36 | /** | |
| 37 | * Responsible for converting SVG images into rasterized PNG images. | |
| 38 | */ | |
| 39 | public final class SvgRasterizer { | |
| 40 | /** | |
| 41 | * <a href="https://issues.apache.org/jira/browse/BATIK-1112">Bug fix</a> | |
| 42 | */ | |
| 43 | public static final class InkscapeCssParser extends Parser { | |
| 44 | public void parseStyleDeclaration( final String source ) | |
| 45 | throws CSSException, IOException { | |
| 46 | super.parseStyleDeclaration( | |
| 47 | source.replaceAll( "-inkscape-font-specification:[^;\"]*;", "" ) | |
| 48 | ); | |
| 49 | } | |
| 50 | } | |
| 51 | ||
| 52 | static { | |
| 53 | XMLResourceDescriptor.setCSSParserClassName( | |
| 54 | InkscapeCssParser.class.getName() | |
| 55 | ); | |
| 56 | } | |
| 57 | ||
| 58 | private static final SAXSVGDocumentFactory FACTORY_DOM = | |
| 59 | new SAXSVGDocumentFactory( getXMLParserClassName() ); | |
| 60 | ||
| 61 | private static final NumberFormat INT_FORMAT = getIntegerInstance(); | |
| 62 | ||
| 63 | public static final BufferedImage BROKEN_IMAGE_PLACEHOLDER; | |
| 64 | ||
| 65 | /** | |
| 66 | * A FontAwesome camera icon, cleft asunder. | |
| 67 | */ | |
| 68 | public static final String BROKEN_IMAGE_SVG = | |
| 69 | "<svg height='19pt' viewBox='0 0 25 19' width='25pt' xmlns='http://www" + | |
| 70 | ".w3.org/2000/svg'><g fill='#454545'><path d='m8.042969 11.085938c" + | |
| 71 | ".332031 1.445312 1.660156 2.503906 3.214843 2.558593zm0 0'/><path " + | |
| 72 | "d='m6.792969 9.621094-.300781.226562.242187.195313c.015625-.144531" + | |
| 73 | ".03125-.28125.058594-.421875zm0 0'/><path d='m10.597656.949219-2" + | |
| 74 | ".511718.207031c-.777344.066406-1.429688.582031-1.636719 1.292969l-" + | |
| 75 | ".367188 1.253906-3.414062.28125c-1.027344.085937-1.792969.949219-1" + | |
| 76 | ".699219 1.925781l.976562 10.621094c.089844.976562.996094 1.699219 " + | |
| 77 | "2.023438 1.613281l11.710938-.972656-3.117188-2.484375c-.246094" + | |
| 78 | ".0625-.5.109375-.765625.132812-2.566406.210938-4.835937-1.597656-5" + | |
| 79 | ".0625-4.039062-.023437-.25-.019531-.496094 0-.738281l-.242187-" + | |
| 80 | ".195313.300781-.226562c.359375-1.929688 2.039062-3.472656 4" + | |
| 81 | ".191406-3.652344.207031-.015625.414063-.015625.617187-.007812l" + | |
| 82 | ".933594-.707032zm0 0'/><path d='m10.234375 11.070312 2.964844 2" + | |
| 83 | ".820313c.144531.015625.285156.027344.433593.027344 1.890626 0 3" + | |
| 84 | ".429688-1.460938 3.429688-3.257813 0-1.792968-1.539062-3.257812-3" + | |
| 85 | ".429688-3.257812-1.890624 0-3.429687 1.464844-3.429687 3.257812 0 " + | |
| 86 | ".140625.011719.277344.03125.410156zm0 0'/><path d='m14.488281" + | |
| 87 | ".808594 1.117188 4.554687-1.042969.546875c2.25.476563 3.84375 2" + | |
| 88 | ".472656 3.636719 4.714844-.199219 2.191406-2.050781 3.871094-4" + | |
| 89 | ".285157 4.039062l2.609376 2.957032 4.4375.371094c1.03125.085937 1" + | |
| 90 | ".9375-.640626 2.027343-1.617188l.976563-10.617188c.089844-.980468-" + | |
| 91 | ".667969-1.839843-1.699219-1.925781l-3.414063-.285156-.371093-1" + | |
| 92 | ".253906c-.207031-.710938-.859375-1.226563-1.636719-1.289063zm0 " + | |
| 93 | "0'/></g></svg>"; | |
| 94 | ||
| 95 | static { | |
| 96 | // The width and height cannot be embedded in the SVG above because the | |
| 97 | // path element values are relative to the viewBox dimensions. | |
| 98 | final int w = 75; | |
| 99 | final int h = 75; | |
| 100 | BufferedImage image; | |
| 101 | ||
| 102 | try { | |
| 103 | image = rasterizeString( BROKEN_IMAGE_SVG, w ); | |
| 104 | } catch( final Exception ex ) { | |
| 105 | image = new BufferedImage( w, h, TYPE_INT_RGB ); | |
| 106 | final var graphics = (Graphics2D) image.getGraphics(); | |
| 107 | graphics.setRenderingHints( RENDERING_HINTS ); | |
| 108 | ||
| 109 | // Fall back to a (\) symbol. | |
| 110 | graphics.setColor( new Color( 204, 204, 204 ) ); | |
| 111 | graphics.fillRect( 0, 0, w, h ); | |
| 112 | graphics.setColor( new Color( 255, 204, 204 ) ); | |
| 113 | graphics.setStroke( new BasicStroke( 4 ) ); | |
| 114 | graphics.drawOval( w / 4, h / 4, w / 2, h / 2 ); | |
| 115 | graphics.drawLine( w / 4 + (int) (w / 4 / Math.PI), | |
| 116 | h / 4 + (int) (w / 4 / Math.PI), | |
| 117 | w / 2 + w / 4 - (int) (w / 4 / Math.PI), | |
| 118 | h / 2 + h / 4 - (int) (w / 4 / Math.PI) ); | |
| 119 | } | |
| 120 | ||
| 121 | BROKEN_IMAGE_PLACEHOLDER = image; | |
| 122 | } | |
| 123 | ||
| 124 | /** | |
| 125 | * Responsible for creating a new {@link ImageRenderer} implementation that | |
| 126 | * can render a DOM as an SVG image. | |
| 127 | */ | |
| 128 | private static class BufferedImageTranscoder extends ImageTranscoder { | |
| 129 | private BufferedImage mImage; | |
| 130 | ||
| 131 | @Override | |
| 132 | public BufferedImage createImage( final int w, final int h ) { | |
| 133 | return new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB ); | |
| 134 | } | |
| 135 | ||
| 136 | @Override | |
| 137 | public void writeImage( | |
| 138 | final BufferedImage image, final TranscoderOutput output ) { | |
| 139 | mImage = image; | |
| 140 | } | |
| 141 | ||
| 142 | public BufferedImage getImage() { | |
| 143 | return mImage; | |
| 144 | } | |
| 145 | ||
| 146 | @Override | |
| 147 | protected ImageRenderer createRenderer() { | |
| 148 | final ImageRenderer renderer = super.createRenderer(); | |
| 149 | final RenderingHints hints = renderer.getRenderingHints(); | |
| 150 | hints.putAll( RENDERING_HINTS ); | |
| 151 | renderer.setRenderingHints( hints ); | |
| 152 | ||
| 153 | return renderer; | |
| 154 | } | |
| 155 | } | |
| 156 | ||
| 157 | /** | |
| 158 | * Rasterizes the given SVG input stream into an image at 96 DPI. | |
| 159 | * | |
| 160 | * @param svg The SVG data to rasterize, must be closed by caller. | |
| 161 | * @return The given input stream converted to a rasterized image. | |
| 162 | */ | |
| 163 | public static BufferedImage rasterize( final InputStream svg ) | |
| 164 | throws TranscoderException { | |
| 165 | return rasterize( svg, 96 ); | |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * Rasterizes the given SVG input stream into an image. | |
| 170 | * | |
| 171 | * @param svg The SVG data to rasterize, must be closed by caller. | |
| 172 | * @param dpi Resolution to use when rasterizing (default is 96 DPI). | |
| 173 | * @return The given input stream converted to a rasterized image at the | |
| 174 | * given resolution. | |
| 175 | */ | |
| 176 | public static BufferedImage rasterize( | |
| 177 | final InputStream svg, final float dpi ) throws TranscoderException { | |
| 178 | final var transcoder = new BufferedImageTranscoder(); | |
| 179 | transcoder.addTranscodingHint( | |
| 180 | KEY_PIXEL_UNIT_TO_MILLIMETER, 1f / dpi * 25.4f ); | |
| 181 | transcoder.transcode( new TranscoderInput( svg ), null ); | |
| 182 | return transcoder.getImage(); | |
| 183 | } | |
| 184 | ||
| 185 | /** | |
| 186 | * Rasterizes the given document into an image. | |
| 187 | * | |
| 188 | * @param svg The SVG {@link Document} to rasterize. | |
| 189 | * @param width The rasterized image's width (in pixels). | |
| 190 | * @return The rasterized image. | |
| 191 | */ | |
| 192 | public static BufferedImage rasterize( final Document svg, final int width ) | |
| 193 | throws TranscoderException { | |
| 194 | final var transcoder = new BufferedImageTranscoder(); | |
| 195 | transcoder.addTranscodingHint( KEY_WIDTH, (float) width ); | |
| 196 | transcoder.transcode( new TranscoderInput( svg ), null ); | |
| 197 | return transcoder.getImage(); | |
| 198 | } | |
| 199 | ||
| 200 | /** | |
| 201 | * Rasterizes the given vector graphic file using the width dimension | |
| 202 | * specified by the document's width attribute. | |
| 203 | * | |
| 204 | * @param document The {@link Document} containing a vector graphic. | |
| 205 | * @return A rasterized image as an instance of {@link BufferedImage}, or | |
| 206 | * {@link #BROKEN_IMAGE_PLACEHOLDER} if the graphic could not be rasterized. | |
| 207 | */ | |
| 208 | public static BufferedImage rasterize( final Document document ) | |
| 209 | throws ParseException, TranscoderException { | |
| 210 | final var root = document.getDocumentElement(); | |
| 211 | final var width = root.getAttribute( "width" ); | |
| 212 | return rasterize( document, INT_FORMAT.parse( width ).intValue() ); | |
| 213 | } | |
| 214 | ||
| 215 | /** | |
| 216 | * Rasterizes the vector graphic file at the given URI. If any exception | |
| 217 | * happens, a broken image icon is returned instead. | |
| 218 | * | |
| 219 | * @param path The {@link Path} to a vector graphic file. | |
| 220 | * @param width Scale the image to the given width (px); aspect ratio is | |
| 221 | * maintained. | |
| 222 | * @return A rasterized image as an instance of {@link BufferedImage}. | |
| 223 | */ | |
| 224 | public static BufferedImage rasterize( final Path path, final int width ) { | |
| 225 | return rasterize( path.toUri(), width ); | |
| 226 | } | |
| 227 | ||
| 228 | /** | |
| 229 | * Rasterizes the vector graphic file at the given URI. If any exception | |
| 230 | * happens, a broken image icon is returned instead. | |
| 231 | * | |
| 232 | * @param uri The URI to a vector graphic file, which must include the | |
| 233 | * protocol scheme (such as file:// or https://). | |
| 234 | * @param width Scale the image to the given width (px); aspect ratio is | |
| 235 | * maintained. | |
| 236 | * @return A rasterized image as an instance of {@link BufferedImage}. | |
| 237 | */ | |
| 238 | public static BufferedImage rasterize( final String uri, final int width ) { | |
| 239 | return rasterize( new File( uri ).toURI(), width ); | |
| 240 | } | |
| 241 | ||
| 242 | /** | |
| 243 | * Converts an SVG drawing into a rasterized image that can be drawn on | |
| 244 | * a graphics context. | |
| 245 | * | |
| 246 | * @param uri The path to the image (can be web address). | |
| 247 | * @param width Scale the image to the given width (px); aspect ratio is | |
| 248 | * maintained. | |
| 249 | * @return The vector graphic transcoded into a raster image format. | |
| 250 | */ | |
| 251 | public static BufferedImage rasterize( final URI uri, final int width ) { | |
| 252 | try { | |
| 253 | return rasterize( FACTORY_DOM.createDocument( uri.toString() ), width ); | |
| 254 | } catch( final Exception ex ) { | |
| 255 | clue( ex ); | |
| 256 | } | |
| 257 | ||
| 258 | return BROKEN_IMAGE_PLACEHOLDER; | |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Converts an SVG string into a rasterized image that can be drawn on | |
| 263 | * a graphics context. The dimensions are determined from the document. | |
| 264 | * | |
| 265 | * @param xml The SVG xml document. | |
| 266 | * @return The vector graphic transcoded into a raster image format. | |
| 267 | */ | |
| 268 | public static BufferedImage rasterizeString( final String xml ) | |
| 269 | throws ParseException, TranscoderException { | |
| 270 | final var document = toDocument( xml ); | |
| 271 | final var root = document.getDocumentElement(); | |
| 272 | final var width = root.getAttribute( "width" ); | |
| 273 | return rasterizeString( xml, INT_FORMAT.parse( width ).intValue() ); | |
| 274 | } | |
| 275 | ||
| 276 | /** | |
| 277 | * Converts an SVG string into a rasterized image that can be drawn on | |
| 278 | * a graphics context. | |
| 279 | * | |
| 280 | * @param svg The SVG xml document. | |
| 281 | * @param w Scale the image width to this size (aspect ratio is | |
| 282 | * maintained). | |
| 283 | * @return The vector graphic transcoded into a raster image format. | |
| 284 | */ | |
| 285 | public static BufferedImage rasterizeString( final String svg, final int w ) | |
| 286 | throws TranscoderException { | |
| 287 | return rasterize( toDocument( svg ), w ); | |
| 288 | } | |
| 289 | ||
| 290 | /** | |
| 291 | * Given a document object model (DOM) {@link Element}, this will convert that | |
| 292 | * element to a string. | |
| 293 | * | |
| 294 | * @param root The DOM node to convert to a string. | |
| 295 | * @return The DOM node as an escaped, plain text string. | |
| 296 | */ | |
| 297 | public static String toSvg( final Element root ) { | |
| 298 | try { | |
| 299 | return transform( root ).replaceAll( "xmlns=\"\" ", "" ); | |
| 300 | } catch( final Exception ex ) { | |
| 301 | clue( ex ); | |
| 302 | } | |
| 303 | ||
| 304 | return BROKEN_IMAGE_SVG; | |
| 305 | } | |
| 306 | ||
| 307 | /** | |
| 308 | * Converts an SVG XML string into a new {@link Document} instance. | |
| 309 | * | |
| 310 | * @param xml The XML containing SVG elements. | |
| 311 | * @return The SVG contents parsed into a {@link Document} object model. | |
| 312 | */ | |
| 313 | private static Document toDocument( final String xml ) { | |
| 314 | try( final var reader = new StringReader( xml ) ) { | |
| 315 | return FACTORY_DOM.createSVGDocument( | |
| 316 | "http://www.w3.org/2000/svg", reader ); | |
| 317 | } catch( final Exception ex ) { | |
| 318 | throw new IllegalArgumentException( ex ); | |
| 319 | } | |
| 320 | } | |
| 321 | } | |
| 1 | 322 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.preview; | |
| 3 | ||
| 4 | import com.keenwrite.io.MediaType; | |
| 5 | import com.keenwrite.ui.adapters.ReplacedElementAdapter; | |
| 6 | import org.xhtmlrenderer.extend.ReplacedElement; | |
| 7 | import org.xhtmlrenderer.extend.UserAgentCallback; | |
| 8 | import org.xhtmlrenderer.layout.LayoutContext; | |
| 9 | import org.xhtmlrenderer.render.BlockBox; | |
| 10 | import org.xhtmlrenderer.swing.ImageReplacedElement; | |
| 11 | ||
| 12 | import java.awt.image.BufferedImage; | |
| 13 | import java.net.URI; | |
| 14 | import java.nio.file.Path; | |
| 15 | ||
| 16 | import static com.keenwrite.events.StatusEvent.clue; | |
| 17 | import static com.keenwrite.io.HttpFacade.httpGet; | |
| 18 | import static com.keenwrite.preview.MathRenderer.MATH_RENDERER; | |
| 19 | import static com.keenwrite.preview.SvgRasterizer.BROKEN_IMAGE_PLACEHOLDER; | |
| 20 | import static com.keenwrite.preview.SvgRasterizer.rasterize; | |
| 21 | import static com.keenwrite.processors.markdown.extensions.tex.TexNode.HTML_TEX; | |
| 22 | import static com.keenwrite.util.ProtocolScheme.getProtocol; | |
| 23 | ||
| 24 | /** | |
| 25 | * Responsible for running {@link SvgRasterizer} on SVG images detected within | |
| 26 | * a document to transform them into rasterized versions. | |
| 27 | */ | |
| 28 | public final class SvgReplacedElementFactory extends ReplacedElementAdapter { | |
| 29 | ||
| 30 | public static final String HTML_IMAGE = "img"; | |
| 31 | public static final String HTML_IMAGE_SRC = "src"; | |
| 32 | ||
| 33 | private static final ImageReplacedElement BROKEN_IMAGE = | |
| 34 | createImageReplacedElement( BROKEN_IMAGE_PLACEHOLDER ); | |
| 35 | ||
| 36 | @Override | |
| 37 | public ReplacedElement createReplacedElement( | |
| 38 | final LayoutContext c, | |
| 39 | final BlockBox box, | |
| 40 | final UserAgentCallback uac, | |
| 41 | final int cssWidth, | |
| 42 | final int cssHeight ) { | |
| 43 | final var e = box.getElement(); | |
| 44 | ||
| 45 | ImageReplacedElement image = null; | |
| 46 | ||
| 47 | try { | |
| 48 | BufferedImage raster = null; | |
| 49 | ||
| 50 | switch( e.getNodeName() ) { | |
| 51 | case HTML_IMAGE -> { | |
| 52 | final var source = e.getAttribute( HTML_IMAGE_SRC ); | |
| 53 | var mediaType = MediaType.fromFilename( source ); | |
| 54 | URI uri = null; | |
| 55 | ||
| 56 | if( getProtocol( source ).isHttp() ) { | |
| 57 | if( mediaType.isSvg() || mediaType.isUndefined() ) { | |
| 58 | uri = new URI( source ); | |
| 59 | ||
| 60 | try( final var response = httpGet( uri ) ) { | |
| 61 | mediaType = response.getMediaType(); | |
| 62 | } | |
| 63 | ||
| 64 | // Attempt to rasterize SVG depending on URL resource content. | |
| 65 | if( !mediaType.isSvg() ) { | |
| 66 | uri = null; | |
| 67 | } | |
| 68 | } | |
| 69 | } | |
| 70 | else if( mediaType.isSvg() ) { | |
| 71 | // Attempt to rasterize based on file name. | |
| 72 | final var path = Path.of( new URI( source ).getPath() ); | |
| 73 | ||
| 74 | if( path.isAbsolute() ) { | |
| 75 | uri = path.toUri(); | |
| 76 | } | |
| 77 | else { | |
| 78 | final var base = new URI( e.getBaseURI() ).getPath(); | |
| 79 | uri = Path.of( base, source ).toUri(); | |
| 80 | } | |
| 81 | } | |
| 82 | ||
| 83 | if( uri != null ) { | |
| 84 | raster = rasterize( uri, box.getContentWidth() ); | |
| 85 | } | |
| 86 | } | |
| 87 | case HTML_TEX -> | |
| 88 | // Convert the TeX element to a raster graphic. | |
| 89 | raster = rasterize( MATH_RENDERER.render( e.getTextContent() ) ); | |
| 90 | } | |
| 91 | ||
| 92 | if( raster != null ) { | |
| 93 | image = createImageReplacedElement( raster ); | |
| 94 | } | |
| 95 | } catch( final Exception ex ) { | |
| 96 | image = BROKEN_IMAGE; | |
| 97 | clue( ex ); | |
| 98 | } | |
| 99 | ||
| 100 | return image; | |
| 101 | } | |
| 102 | ||
| 103 | private static ImageReplacedElement createImageReplacedElement( | |
| 104 | final BufferedImage bi ) { | |
| 105 | return new ImageReplacedElement( bi, bi.getWidth(), bi.getHeight() ); | |
| 106 | } | |
| 107 | } | |
| 1 | 108 |
| 1 | /* | |
| 2 | * Copyright 2013, Morten Nobel-Joergensen | |
| 3 | * | |
| 4 | * License: The BSD 3-Clause License | |
| 5 | * http://opensource.org/licenses/BSD-3-Clause | |
| 6 | */ | |
| 7 | package com.keenwrite.preview.images; | |
| 8 | ||
| 9 | import java.awt.*; | |
| 10 | import java.awt.geom.Point2D; | |
| 11 | import java.awt.geom.Rectangle2D; | |
| 12 | import java.awt.image.BufferedImage; | |
| 13 | import java.awt.image.BufferedImageOp; | |
| 14 | import java.awt.image.ColorModel; | |
| 15 | ||
| 16 | /** | |
| 17 | * @author Morten Nobel-Joergensen | |
| 18 | */ | |
| 19 | public abstract class AdvancedResizeOp implements BufferedImageOp { | |
| 20 | private final ConstrainedDimension dimensionConstrain; | |
| 21 | ||
| 22 | public AdvancedResizeOp( ConstrainedDimension dimensionConstrain ) { | |
| 23 | this.dimensionConstrain = dimensionConstrain; | |
| 24 | } | |
| 25 | ||
| 26 | public final BufferedImage filter( BufferedImage src, BufferedImage dest ) { | |
| 27 | Dimension dstDimension = dimensionConstrain.getDimension( | |
| 28 | new Dimension( src.getWidth(), src.getHeight() ) ); | |
| 29 | int dstWidth = dstDimension.width; | |
| 30 | int dstHeight = dstDimension.height; | |
| 31 | ||
| 32 | return doFilter( src, dest, dstWidth, dstHeight ); | |
| 33 | } | |
| 34 | ||
| 35 | protected abstract BufferedImage doFilter( | |
| 36 | BufferedImage src, BufferedImage dest, int dstWidth, int dstHeight ); | |
| 37 | ||
| 38 | @Override | |
| 39 | public final Rectangle2D getBounds2D( BufferedImage src ) { | |
| 40 | return new Rectangle( 0, 0, src.getWidth(), src.getHeight() ); | |
| 41 | } | |
| 42 | ||
| 43 | @Override | |
| 44 | public final BufferedImage createCompatibleDestImage( | |
| 45 | BufferedImage src, ColorModel destCM ) { | |
| 46 | if( destCM == null ) { | |
| 47 | destCM = src.getColorModel(); | |
| 48 | } | |
| 49 | ||
| 50 | return new BufferedImage( | |
| 51 | destCM, | |
| 52 | destCM.createCompatibleWritableRaster( src.getWidth(), src.getHeight() ), | |
| 53 | destCM.isAlphaPremultiplied(), | |
| 54 | null ); | |
| 55 | } | |
| 56 | ||
| 57 | @Override | |
| 58 | public final Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) { | |
| 59 | return (Point2D) srcPt.clone(); | |
| 60 | } | |
| 61 | ||
| 62 | @Override | |
| 63 | public final RenderingHints getRenderingHints() { | |
| 64 | return null; | |
| 65 | } | |
| 66 | } | |
| 1 | 67 |
| 1 | /* | |
| 2 | * Copyright 2013, Morten Nobel-Joergensen | |
| 3 | * | |
| 4 | * License: The BSD 3-Clause License | |
| 5 | * http://opensource.org/licenses/BSD-3-Clause | |
| 6 | */ | |
| 7 | package com.keenwrite.preview.images; | |
| 8 | ||
| 9 | import java.awt.*; | |
| 10 | ||
| 11 | /** | |
| 12 | * This class let you create dimension constrains based on a actual image. | |
| 13 | */ | |
| 14 | public class ConstrainedDimension { | |
| 15 | private ConstrainedDimension() { | |
| 16 | } | |
| 17 | ||
| 18 | /** | |
| 19 | * Will always return a dimension with positive width and height; | |
| 20 | * | |
| 21 | * @param dimension of the unscaled image | |
| 22 | * @return the dimension of the scaled image | |
| 23 | */ | |
| 24 | public Dimension getDimension( Dimension dimension ) { | |
| 25 | return dimension; | |
| 26 | } | |
| 27 | ||
| 28 | /** | |
| 29 | * Used when the destination size is fixed. This may not keep the image | |
| 30 | * aspect radio. | |
| 31 | * | |
| 32 | * @param width destination dimension width | |
| 33 | * @param height destination dimension height | |
| 34 | * @return destination dimension (width x height) | |
| 35 | */ | |
| 36 | public static ConstrainedDimension createAbsolutionDimension( | |
| 37 | final int width, final int height ) { | |
| 38 | assert width > 0 && height > 0 : "Dimensions must be positive integers"; | |
| 39 | return new ConstrainedDimension() { | |
| 40 | public Dimension getDimension( Dimension dimension ) { | |
| 41 | return new Dimension( width, height ); | |
| 42 | } | |
| 43 | }; | |
| 44 | } | |
| 45 | } | |
| 1 | 46 |
| 1 | /* | |
| 2 | * Copyright 2013, Morten Nobel-Joergensen | |
| 3 | * | |
| 4 | * License: The BSD 3-Clause License | |
| 5 | * http://opensource.org/licenses/BSD-3-Clause | |
| 6 | */ | |
| 7 | package com.keenwrite.preview.images; | |
| 8 | ||
| 9 | import java.awt.*; | |
| 10 | import java.awt.image.BufferedImage; | |
| 11 | import java.awt.image.Raster; | |
| 12 | import java.awt.image.WritableRaster; | |
| 13 | ||
| 14 | import static java.awt.image.BufferedImage.*; | |
| 15 | ||
| 16 | /** | |
| 17 | * @author Heinz Doerr | |
| 18 | * @author Morten Nobel-Joergensen | |
| 19 | */ | |
| 20 | public final class ImageUtils { | |
| 21 | @SuppressWarnings( "DuplicateBranchesInSwitch" ) | |
| 22 | static int nrChannels( final BufferedImage img ) { | |
| 23 | return switch( img.getType() ) { | |
| 24 | case TYPE_3BYTE_BGR -> 3; | |
| 25 | case TYPE_4BYTE_ABGR -> 4; | |
| 26 | case TYPE_BYTE_GRAY -> 1; | |
| 27 | case TYPE_INT_BGR -> 3; | |
| 28 | case TYPE_INT_ARGB -> 4; | |
| 29 | case TYPE_INT_RGB -> 3; | |
| 30 | case TYPE_CUSTOM -> 4; | |
| 31 | case TYPE_4BYTE_ABGR_PRE -> 4; | |
| 32 | case TYPE_INT_ARGB_PRE -> 4; | |
| 33 | case TYPE_USHORT_555_RGB -> 3; | |
| 34 | case TYPE_USHORT_565_RGB -> 3; | |
| 35 | case TYPE_USHORT_GRAY -> 1; | |
| 36 | default -> 0; | |
| 37 | }; | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * returns one row (height == 1) of byte packed image data in BGR or AGBR form | |
| 42 | * | |
| 43 | * @param temp must be either null or a array with length of w*h | |
| 44 | */ | |
| 45 | static void getPixelsBGR( | |
| 46 | BufferedImage img, int y, int w, byte[] array, int[] temp ) { | |
| 47 | final int x = 0; | |
| 48 | final int h = 1; | |
| 49 | ||
| 50 | assert array.length == temp.length * nrChannels( img ); | |
| 51 | assert (temp.length == w); | |
| 52 | ||
| 53 | final Raster raster; | |
| 54 | switch( img.getType() ) { | |
| 55 | case TYPE_3BYTE_BGR, TYPE_4BYTE_ABGR, | |
| 56 | TYPE_4BYTE_ABGR_PRE, TYPE_BYTE_GRAY -> { | |
| 57 | raster = img.getRaster(); | |
| 58 | //int ttype= raster.getTransferType(); | |
| 59 | raster.getDataElements( x, y, w, h, array ); | |
| 60 | } | |
| 61 | case TYPE_INT_BGR -> { | |
| 62 | raster = img.getRaster(); | |
| 63 | raster.getDataElements( x, y, w, h, temp ); | |
| 64 | ints2bytes( temp, array, 0, 1, 2 ); // bgr --> bgr | |
| 65 | } | |
| 66 | case TYPE_INT_RGB -> { | |
| 67 | raster = img.getRaster(); | |
| 68 | raster.getDataElements( x, y, w, h, temp ); | |
| 69 | ints2bytes( temp, array, 2, 1, 0 ); // rgb --> bgr | |
| 70 | } | |
| 71 | case TYPE_INT_ARGB, TYPE_INT_ARGB_PRE -> { | |
| 72 | raster = img.getRaster(); | |
| 73 | raster.getDataElements( x, y, w, h, temp ); | |
| 74 | ints2bytes( temp, array, 2, 1, 0, 3 ); // argb --> abgr | |
| 75 | } | |
| 76 | case TYPE_CUSTOM -> { | |
| 77 | // loader, but else ??? | |
| 78 | img.getRGB( x, y, w, h, temp, 0, w ); | |
| 79 | ints2bytes( temp, array, 2, 1, 0, 3 ); // argb --> abgr | |
| 80 | } | |
| 81 | default -> { | |
| 82 | img.getRGB( x, y, w, h, temp, 0, w ); | |
| 83 | ints2bytes( temp, array, 2, 1, 0 ); // rgb --> bgr | |
| 84 | } | |
| 85 | } | |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * converts and copies byte packed BGR or ABGR into the img buffer, | |
| 90 | * the img type may vary (e.g. RGB or BGR, int or byte packed) | |
| 91 | * but the number of components (w/o alpha, w alpha, gray) must match | |
| 92 | * <p> | |
| 93 | * does not unmange the image for all (A)RGN and (A)BGR and gray imaged | |
| 94 | */ | |
| 95 | public static void setBGRPixels( byte[] bgrPixels, BufferedImage img, int x, | |
| 96 | int y, int w, int h ) { | |
| 97 | int imageType = img.getType(); | |
| 98 | WritableRaster raster = img.getRaster(); | |
| 99 | ||
| 100 | if( imageType == TYPE_3BYTE_BGR || | |
| 101 | imageType == TYPE_4BYTE_ABGR || | |
| 102 | imageType == TYPE_4BYTE_ABGR_PRE || | |
| 103 | imageType == TYPE_BYTE_GRAY ) { | |
| 104 | raster.setDataElements( x, y, w, h, bgrPixels ); | |
| 105 | } | |
| 106 | else { | |
| 107 | int[] pixels; | |
| 108 | if( imageType == TYPE_INT_BGR ) { | |
| 109 | pixels = bytes2int( bgrPixels, 2, 1, 0 ); // bgr --> bgr | |
| 110 | } | |
| 111 | else if( imageType == TYPE_INT_ARGB || | |
| 112 | imageType == TYPE_INT_ARGB_PRE ) { | |
| 113 | pixels = bytes2int( bgrPixels, 3, 0, 1, 2 ); // abgr --> argb | |
| 114 | } | |
| 115 | else { | |
| 116 | pixels = bytes2int( bgrPixels, 0, 1, 2 ); // bgr --> rgb | |
| 117 | } | |
| 118 | if( w == 0 || h == 0 ) { | |
| 119 | return; | |
| 120 | } | |
| 121 | else if( pixels.length < w * h ) { | |
| 122 | throw new IllegalArgumentException( "pixels array must have a length" + " >= w*h" ); | |
| 123 | } | |
| 124 | if( imageType == TYPE_INT_ARGB || | |
| 125 | imageType == TYPE_INT_RGB || | |
| 126 | imageType == TYPE_INT_ARGB_PRE || | |
| 127 | imageType == TYPE_INT_BGR ) { | |
| 128 | raster.setDataElements( x, y, w, h, pixels ); | |
| 129 | } | |
| 130 | else { | |
| 131 | // Unmanages the image | |
| 132 | img.setRGB( x, y, w, h, pixels, 0, w ); | |
| 133 | } | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | public static void ints2bytes( int[] in, byte[] out, int index1, int index2, | |
| 138 | int index3 ) { | |
| 139 | for( int i = 0; i < in.length; i++ ) { | |
| 140 | int index = i * 3; | |
| 141 | int value = in[ i ]; | |
| 142 | out[ index + index1 ] = (byte) value; | |
| 143 | value = value >> 8; | |
| 144 | out[ index + index2 ] = (byte) value; | |
| 145 | value = value >> 8; | |
| 146 | out[ index + index3 ] = (byte) value; | |
| 147 | } | |
| 148 | } | |
| 149 | ||
| 150 | public static void ints2bytes( int[] in, byte[] out, int index1, int index2, | |
| 151 | int index3, int index4 ) { | |
| 152 | for( int i = 0; i < in.length; i++ ) { | |
| 153 | int index = i * 4; | |
| 154 | int value = in[ i ]; | |
| 155 | out[ index + index1 ] = (byte) value; | |
| 156 | value = value >> 8; | |
| 157 | out[ index + index2 ] = (byte) value; | |
| 158 | value = value >> 8; | |
| 159 | out[ index + index3 ] = (byte) value; | |
| 160 | value = value >> 8; | |
| 161 | out[ index + index4 ] = (byte) value; | |
| 162 | } | |
| 163 | } | |
| 164 | ||
| 165 | public static int[] bytes2int( byte[] in, int index1, int index2, | |
| 166 | int index3 ) { | |
| 167 | int[] out = new int[ in.length / 3 ]; | |
| 168 | for( int i = 0; i < out.length; i++ ) { | |
| 169 | int index = i * 3; | |
| 170 | int b1 = (in[ index + index1 ] & 0xff) << 16; | |
| 171 | int b2 = (in[ index + index2 ] & 0xff) << 8; | |
| 172 | int b3 = in[ index + index3 ] & 0xff; | |
| 173 | out[ i ] = b1 | b2 | b3; | |
| 174 | } | |
| 175 | return out; | |
| 176 | } | |
| 177 | ||
| 178 | public static int[] bytes2int( byte[] in, int index1, int index2, int index3, | |
| 179 | int index4 ) { | |
| 180 | int[] out = new int[ in.length / 4 ]; | |
| 181 | for( int i = 0; i < out.length; i++ ) { | |
| 182 | int index = i * 4; | |
| 183 | int b1 = (in[ index + index1 ] & 0xff) << 24; | |
| 184 | int b2 = (in[ index + index2 ] & 0xff) << 16; | |
| 185 | int b3 = (in[ index + index3 ] & 0xff) << 8; | |
| 186 | int b4 = in[ index + index4 ] & 0xff; | |
| 187 | out[ i ] = b1 | b2 | b3 | b4; | |
| 188 | } | |
| 189 | return out; | |
| 190 | } | |
| 191 | ||
| 192 | public static BufferedImage convert( BufferedImage src, int bufImgType ) { | |
| 193 | BufferedImage img = new BufferedImage( src.getWidth(), | |
| 194 | src.getHeight(), | |
| 195 | bufImgType ); | |
| 196 | Graphics2D g2d = img.createGraphics(); | |
| 197 | g2d.drawImage( src, 0, 0, null ); | |
| 198 | g2d.dispose(); | |
| 199 | return img; | |
| 200 | } | |
| 201 | } | |
| 1 | 202 |
| 1 | package com.keenwrite.preview.images; | |
| 2 | ||
| 3 | import java.awt.image.BufferedImage; | |
| 4 | ||
| 5 | /** | |
| 6 | * Unused. Needs to extract image data from {@link BufferedImage} and create | |
| 7 | * down-sampled version. | |
| 8 | */ | |
| 9 | public class Lanczos3 { | |
| 10 | static double sinc( double x ) { | |
| 11 | x *= Math.PI; | |
| 12 | ||
| 13 | if( (x < 0.01f) && (x > -0.01f) ) { | |
| 14 | return 1.0f + x * x * (-1.0f / 6.0f + x * x * 1.0f / 120.0f); | |
| 15 | } | |
| 16 | ||
| 17 | return Math.sin( x ) / x; | |
| 18 | } | |
| 19 | ||
| 20 | static float clip( double t ) { | |
| 21 | final float eps = .0000125f; | |
| 22 | ||
| 23 | if( Math.abs( t ) < eps ) { return 0.0f; } | |
| 24 | ||
| 25 | return (float) t; | |
| 26 | } | |
| 27 | ||
| 28 | static float lancos( float t ) { | |
| 29 | if( t < 0.0f ) { t = -t; } | |
| 30 | ||
| 31 | if( t < 3.0f ) { return clip( sinc( t ) * sinc( t / 3.0f ) ); } | |
| 32 | else { return (0.0f); } | |
| 33 | } | |
| 34 | ||
| 35 | static float lancos3_resample_x( | |
| 36 | int[][] arr, int src_w, int src_h, int y, int x, float xscale ) { | |
| 37 | float s = 0; | |
| 38 | float coef_sum = 0.0f; | |
| 39 | float coef; | |
| 40 | float pix; | |
| 41 | int i; | |
| 42 | ||
| 43 | int l, r; | |
| 44 | float c; | |
| 45 | float hw; | |
| 46 | ||
| 47 | // For the reduction of the situation hw is equivalent to expanding the | |
| 48 | // number of pixels in the field, if you do not do this, the final | |
| 49 | // reduction of the image effect is not much different from the recent | |
| 50 | // field interpolation method, the effect is equivalent to the first | |
| 51 | // low-pass filtering, and then interpolate | |
| 52 | if( xscale > 1.0f ) { hw = 3.0f; } | |
| 53 | else { hw = 3.0f / xscale; } | |
| 54 | ||
| 55 | c = (float) x / xscale; | |
| 56 | l = (int) Math.floor( c - hw ); | |
| 57 | r = (int) Math.ceil( c + hw ); | |
| 58 | ||
| 59 | if( y < 0 ) { y = 0; } | |
| 60 | if( y >= src_h ) { y = src_h - 1; } | |
| 61 | if( xscale > 1.0f ) { xscale = 1.0f; } | |
| 62 | for( i = l; i <= r; i++ ) { | |
| 63 | x = Math.max( i, 0 ); | |
| 64 | if( i >= src_w ) { x = src_w - 1; } | |
| 65 | pix = arr[ y ][ x ]; | |
| 66 | coef = lancos( (c - i) * xscale ); | |
| 67 | s += pix * coef; | |
| 68 | coef_sum += coef; | |
| 69 | } | |
| 70 | s /= coef_sum; | |
| 71 | return s; | |
| 72 | } | |
| 73 | ||
| 74 | static class uint8_2d { | |
| 75 | int[][] arr; | |
| 76 | int rows; | |
| 77 | int cols; | |
| 78 | ||
| 79 | public uint8_2d( final int h1, final int w1 ) { | |
| 80 | arr = new int[ h1 ][ w1 ]; | |
| 81 | rows = h1; | |
| 82 | cols = w1; | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | void img_resize_using_lancos3( uint8_2d src, uint8_2d dst ) { | |
| 87 | if( src == null || dst == null ) { return; } | |
| 88 | ||
| 89 | int src_rows, src_cols; | |
| 90 | int dst_rows, dst_cols; | |
| 91 | int i, j; | |
| 92 | int[][] src_arr; | |
| 93 | int[][] dst_arr; | |
| 94 | float xratio; | |
| 95 | float yratio; | |
| 96 | int val; | |
| 97 | int k; | |
| 98 | float hw; | |
| 99 | ||
| 100 | src_arr = src.arr; | |
| 101 | dst_arr = dst.arr; | |
| 102 | src_rows = src.rows; | |
| 103 | src_cols = src.cols; | |
| 104 | dst_rows = dst.rows; | |
| 105 | dst_cols = dst.cols; | |
| 106 | ||
| 107 | xratio = (float) (dst_cols) / (float) src_cols; | |
| 108 | yratio = (float) (dst_rows) / (float) src_rows; | |
| 109 | ||
| 110 | float scale; | |
| 111 | ||
| 112 | if( yratio > 1.0f ) { | |
| 113 | hw = 3.0f; | |
| 114 | scale = 1.0f; | |
| 115 | } | |
| 116 | else { | |
| 117 | hw = 3.0f / yratio; | |
| 118 | scale = yratio; | |
| 119 | } | |
| 120 | ||
| 121 | for( i = 0; i < dst_rows; i++ ) { | |
| 122 | for( j = 0; j < dst_cols; j++ ) { | |
| 123 | int t, b; | |
| 124 | float c; | |
| 125 | ||
| 126 | float s = 0; | |
| 127 | float coef_sum = 0.0f; | |
| 128 | float coef; | |
| 129 | float pix; | |
| 130 | ||
| 131 | c = (float) i / yratio; | |
| 132 | t = (int) Math.floor( c - hw ); | |
| 133 | b = (int) Math.ceil( c + hw ); | |
| 134 | // Interpolate in the x direction first, then interpolate in the y | |
| 135 | // direction. | |
| 136 | for( k = t; k <= b; k++ ) { | |
| 137 | pix = lancos3_resample_x( src_arr, src_cols, src_rows, k, j, xratio ); | |
| 138 | coef = lancos( (c - k) * scale ); | |
| 139 | coef_sum += coef; | |
| 140 | pix *= coef; | |
| 141 | s += pix; | |
| 142 | } | |
| 143 | val = (int) (s / coef_sum); | |
| 144 | if( val < 0 ) { val = 0; } | |
| 145 | if( val > 255 ) { val = 255; } | |
| 146 | dst_arr[ i ][ j ] = val; | |
| 147 | } | |
| 148 | } | |
| 149 | } | |
| 150 | ||
| 151 | BufferedImage test_lancos3_resize( BufferedImage img, float factor ) { | |
| 152 | assert img != null; | |
| 153 | ||
| 154 | uint8_2d r = null; | |
| 155 | uint8_2d g = null; | |
| 156 | uint8_2d b = null; | |
| 157 | ||
| 158 | BufferedImage out = null; | |
| 159 | // TODO: Split buffered image into RGB components. | |
| 160 | //split_img_data( img, r, g, b ); | |
| 161 | ||
| 162 | int w, h; | |
| 163 | int w1, h1; | |
| 164 | w = img.getWidth(); | |
| 165 | h = img.getHeight(); | |
| 166 | ||
| 167 | // TODO: Maintain aspect ratio. | |
| 168 | w1 = (int) (factor * w); | |
| 169 | h1 = (int) (factor * h); | |
| 170 | ||
| 171 | uint8_2d r1 = new uint8_2d( h1, w1 ); | |
| 172 | uint8_2d g1 = new uint8_2d( h1, w1 ); | |
| 173 | uint8_2d b1 = new uint8_2d( h1, w1 ); | |
| 174 | ||
| 175 | img_resize_using_lancos3( r, r1 ); | |
| 176 | img_resize_using_lancos3( g, g1 ); | |
| 177 | img_resize_using_lancos3( b, b1 ); | |
| 178 | ||
| 179 | // TODO: Combine rescaled image into RGB components. | |
| 180 | //merge_img_data( r1, g1, b1, out); | |
| 181 | ||
| 182 | return out; | |
| 183 | } | |
| 184 | } | |
| 1 | 185 |
| 1 | /* | |
| 2 | * Copyright 2013, Morten Nobel-Joergensen | |
| 3 | * | |
| 4 | * License: The BSD 3-Clause License | |
| 5 | * http://opensource.org/licenses/BSD-3-Clause | |
| 6 | */ | |
| 7 | package com.keenwrite.preview.images; | |
| 8 | ||
| 9 | public final class Lanczos3Filter implements ResampleFilter { | |
| 10 | private final static float PI_FLOAT = (float) Math.PI; | |
| 11 | ||
| 12 | private float sincModified( float value ) { | |
| 13 | return (float) Math.sin( value ) / value; | |
| 14 | } | |
| 15 | ||
| 16 | public final float apply( float value ) { | |
| 17 | if( value == 0 ) { | |
| 18 | return 1.0f; | |
| 19 | } | |
| 20 | ||
| 21 | if( value < 0.0f ) { | |
| 22 | value = -value; | |
| 23 | } | |
| 24 | ||
| 25 | if( value < 3.0f ) { | |
| 26 | value *= PI_FLOAT; | |
| 27 | return sincModified( value ) * sincModified( value / 3.0f ); | |
| 28 | } | |
| 29 | ||
| 30 | return 0.0f; | |
| 31 | } | |
| 32 | ||
| 33 | public float getSamplingRadius() { | |
| 34 | return 3.0f; | |
| 35 | } | |
| 36 | } | |
| 1 | 37 |
| 1 | /* | |
| 2 | * Copyright 2013, Morten Nobel-Joergensen | |
| 3 | * | |
| 4 | * License: The BSD 3-Clause License | |
| 5 | * http://opensource.org/licenses/BSD-3-Clause | |
| 6 | */ | |
| 7 | package com.keenwrite.preview.images; | |
| 8 | ||
| 9 | public interface ResampleFilter { | |
| 10 | float getSamplingRadius(); | |
| 11 | ||
| 12 | float apply(float v); | |
| 13 | } | |
| 1 | 14 |
| 1 | /* | |
| 2 | * Copyright 2013, Morten Nobel-Joergensen | |
| 3 | * | |
| 4 | * License: The BSD 3-Clause License | |
| 5 | * http://opensource.org/licenses/BSD-3-Clause | |
| 6 | */ | |
| 7 | package com.keenwrite.preview.images; | |
| 8 | ||
| 9 | import java.awt.image.BufferedImage; | |
| 10 | import java.util.concurrent.atomic.AtomicInteger; | |
| 11 | ||
| 12 | import static com.keenwrite.preview.images.ConstrainedDimension.createAbsolutionDimension; | |
| 13 | import static java.awt.image.BufferedImage.*; | |
| 14 | import static java.awt.image.DataBuffer.TYPE_USHORT; | |
| 15 | import static java.lang.Runtime.getRuntime; | |
| 16 | import static java.lang.String.format; | |
| 17 | import static java.lang.Thread.currentThread; | |
| 18 | ||
| 19 | /** | |
| 20 | * Based on <a href="http://schmidt.devlib.org/jiu/">Java Image Util</a>. | |
| 21 | * <p> | |
| 22 | * Note that the filter method is not thread-safe. | |
| 23 | * </p> | |
| 24 | * | |
| 25 | * @author Morten Nobel-Joergensen | |
| 26 | * @author Heinz Doerr | |
| 27 | */ | |
| 28 | public class ResampleOp extends AdvancedResizeOp { | |
| 29 | private static final int MAX_CHANNEL_VALUE = 255; | |
| 30 | ||
| 31 | private int nrChannels; | |
| 32 | private int srcWidth; | |
| 33 | private int srcHeight; | |
| 34 | private int dstWidth; | |
| 35 | private int dstHeight; | |
| 36 | ||
| 37 | static class SubSamplingData { | |
| 38 | // individual - per row or per column - nr of contributions | |
| 39 | private final int[] arrN; | |
| 40 | // 2Dim: [wid or hei][contrib] | |
| 41 | private final int[] arrPixel; | |
| 42 | // 2Dim: [wid or hei][contrib] | |
| 43 | private final float[] arrWeight; | |
| 44 | // the primary index length for the 2Dim arrays : arrPixel and arrWeight | |
| 45 | private final int numContributors; | |
| 46 | ||
| 47 | private SubSamplingData( int[] arrN, int[] arrPixel, float[] arrWeight, | |
| 48 | int numContributors ) { | |
| 49 | this.arrN = arrN; | |
| 50 | this.arrPixel = arrPixel; | |
| 51 | this.arrWeight = arrWeight; | |
| 52 | this.numContributors = numContributors; | |
| 53 | } | |
| 54 | ||
| 55 | public int getNumContributors() { | |
| 56 | return numContributors; | |
| 57 | } | |
| 58 | ||
| 59 | public int[] getArrN() { | |
| 60 | return arrN; | |
| 61 | } | |
| 62 | ||
| 63 | public float[] getArrWeight() { | |
| 64 | return arrWeight; | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | private SubSamplingData horizontalSubsamplingData; | |
| 69 | private SubSamplingData verticalSubsamplingData; | |
| 70 | ||
| 71 | private final int threadCount = getRuntime().availableProcessors(); | |
| 72 | private final AtomicInteger multipleInvocationLock = new AtomicInteger(); | |
| 73 | private final ResampleFilter mFilter; | |
| 74 | ||
| 75 | public ResampleOp( | |
| 76 | final ResampleFilter filter, final int destWidth, final int destHeight ) { | |
| 77 | this( filter, | |
| 78 | createAbsolutionDimension( destWidth, destHeight ) ); | |
| 79 | } | |
| 80 | ||
| 81 | public ResampleOp( | |
| 82 | final ResampleFilter filter, ConstrainedDimension dimensionConstrain ) { | |
| 83 | super( dimensionConstrain ); | |
| 84 | mFilter = filter; | |
| 85 | } | |
| 86 | ||
| 87 | public BufferedImage doFilter( | |
| 88 | BufferedImage srcImg, BufferedImage dest, int dstWidth, int dstHeight ) { | |
| 89 | this.dstWidth = dstWidth; | |
| 90 | this.dstHeight = dstHeight; | |
| 91 | ||
| 92 | if( dstWidth < 3 || dstHeight < 3 ) { | |
| 93 | throw new IllegalArgumentException( "Target must be at least 3x3." ); | |
| 94 | } | |
| 95 | ||
| 96 | assert multipleInvocationLock.incrementAndGet() == 1 : | |
| 97 | "Multiple concurrent invocations detected"; | |
| 98 | ||
| 99 | final var srcType = srcImg.getType(); | |
| 100 | ||
| 101 | if( srcType == TYPE_BYTE_BINARY || | |
| 102 | srcType == TYPE_BYTE_INDEXED || | |
| 103 | srcType == TYPE_CUSTOM ) { | |
| 104 | srcImg = ImageUtils.convert( | |
| 105 | srcImg, | |
| 106 | srcImg.getColorModel().hasAlpha() ? TYPE_4BYTE_ABGR : TYPE_3BYTE_BGR ); | |
| 107 | } | |
| 108 | ||
| 109 | this.nrChannels = ImageUtils.nrChannels( srcImg ); | |
| 110 | assert nrChannels > 0; | |
| 111 | this.srcWidth = srcImg.getWidth(); | |
| 112 | this.srcHeight = srcImg.getHeight(); | |
| 113 | ||
| 114 | byte[][] workPixels = new byte[ srcHeight ][ dstWidth * nrChannels ]; | |
| 115 | ||
| 116 | // Pre-calculate sub-sampling | |
| 117 | horizontalSubsamplingData = createSubSampling( | |
| 118 | mFilter, srcWidth, dstWidth ); | |
| 119 | verticalSubsamplingData = createSubSampling( | |
| 120 | mFilter, srcHeight, dstHeight ); | |
| 121 | ||
| 122 | final BufferedImage scrImgCopy = srcImg; | |
| 123 | final byte[][] workPixelsCopy = workPixels; | |
| 124 | final Thread[] threads = new Thread[ threadCount - 1 ]; | |
| 125 | ||
| 126 | for( int i = 1; i < threadCount; i++ ) { | |
| 127 | final int finalI = i; | |
| 128 | threads[ i - 1 ] = new Thread( () -> horizontallyFromSrcToWork( | |
| 129 | scrImgCopy, workPixelsCopy, finalI, threadCount ) ); | |
| 130 | threads[ i - 1 ].start(); | |
| 131 | } | |
| 132 | ||
| 133 | horizontallyFromSrcToWork( scrImgCopy, workPixelsCopy, 0, threadCount ); | |
| 134 | waitForAllThreads( threads ); | |
| 135 | ||
| 136 | byte[] outPixels = new byte[ dstWidth * dstHeight * nrChannels ]; | |
| 137 | ||
| 138 | // -------------------------------------------------- | |
| 139 | // Apply filter to sample vertically from Work to Dst | |
| 140 | // -------------------------------------------------- | |
| 141 | final byte[] outPixelsCopy = outPixels; | |
| 142 | for( int i = 1; i < threadCount; i++ ) { | |
| 143 | final int finalI = i; | |
| 144 | threads[ i - 1 ] = new Thread( () -> verticalFromWorkToDst( | |
| 145 | workPixelsCopy, outPixelsCopy, finalI, threadCount ) ); | |
| 146 | threads[ i - 1 ].start(); | |
| 147 | } | |
| 148 | verticalFromWorkToDst( workPixelsCopy, outPixelsCopy, 0, threadCount ); | |
| 149 | waitForAllThreads( threads ); | |
| 150 | ||
| 151 | //noinspection UnusedAssignment | |
| 152 | workPixels = null; // free memory | |
| 153 | final BufferedImage out; | |
| 154 | if( dest != null && dstWidth == dest.getWidth() && dstHeight == dest.getHeight() ) { | |
| 155 | out = dest; | |
| 156 | int nrDestChannels = ImageUtils.nrChannels( dest ); | |
| 157 | if( nrDestChannels != nrChannels ) { | |
| 158 | final var errorMgs = format( | |
| 159 | "Destination image must be compatible width source image. Source " + | |
| 160 | "image had %d channels destination image had %d channels", | |
| 161 | nrChannels, nrDestChannels ); | |
| 162 | throw new RuntimeException( errorMgs ); | |
| 163 | } | |
| 164 | } | |
| 165 | else { | |
| 166 | out = new BufferedImage( | |
| 167 | dstWidth, dstHeight, getResultBufferedImageType( srcImg ) ); | |
| 168 | } | |
| 169 | ||
| 170 | ImageUtils.setBGRPixels( outPixels, out, 0, 0, dstWidth, dstHeight ); | |
| 171 | ||
| 172 | assert multipleInvocationLock.decrementAndGet() == 0 : "Multiple " + | |
| 173 | "concurrent invocations detected"; | |
| 174 | ||
| 175 | return out; | |
| 176 | } | |
| 177 | ||
| 178 | private void waitForAllThreads( final Thread[] threads ) { | |
| 179 | try { | |
| 180 | for( final Thread thread : threads ) { | |
| 181 | thread.join( Long.MAX_VALUE ); | |
| 182 | } | |
| 183 | } catch( final InterruptedException e ) { | |
| 184 | currentThread().interrupt(); | |
| 185 | throw new RuntimeException( e ); | |
| 186 | } | |
| 187 | } | |
| 188 | ||
| 189 | static SubSamplingData createSubSampling( | |
| 190 | ResampleFilter filter, int srcSize, int dstSize ) { | |
| 191 | final float scale = (float) dstSize / (float) srcSize; | |
| 192 | final int[] arrN = new int[ dstSize ]; | |
| 193 | final int numContributors; | |
| 194 | final float[] arrWeight; | |
| 195 | final int[] arrPixel; | |
| 196 | ||
| 197 | final float fwidth = filter.getSamplingRadius(); | |
| 198 | ||
| 199 | float centerOffset = 0.5f / scale; | |
| 200 | ||
| 201 | if( scale < 1.0f ) { | |
| 202 | final float width = fwidth / scale; | |
| 203 | // Add 2 to be safe with the ceiling | |
| 204 | numContributors = (int) (width * 2.0f + 2); | |
| 205 | arrWeight = new float[ dstSize * numContributors ]; | |
| 206 | arrPixel = new int[ dstSize * numContributors ]; | |
| 207 | ||
| 208 | final float fNormFac = (float) (1f / (Math.ceil( width ) / fwidth)); | |
| 209 | ||
| 210 | for( int i = 0; i < dstSize; i++ ) { | |
| 211 | final int subindex = i * numContributors; | |
| 212 | float center = i / scale + centerOffset; | |
| 213 | int left = (int) Math.floor( center - width ); | |
| 214 | int right = (int) Math.ceil( center + width ); | |
| 215 | for( int j = left; j <= right; j++ ) { | |
| 216 | float weight; | |
| 217 | weight = filter.apply( (center - j) * fNormFac ); | |
| 218 | ||
| 219 | if( weight == 0.0f ) { | |
| 220 | continue; | |
| 221 | } | |
| 222 | int n; | |
| 223 | if( j < 0 ) { | |
| 224 | n = -j; | |
| 225 | } | |
| 226 | else if( j >= srcSize ) { | |
| 227 | n = srcSize - j + srcSize - 1; | |
| 228 | } | |
| 229 | else { | |
| 230 | n = j; | |
| 231 | } | |
| 232 | int k = arrN[ i ]; | |
| 233 | //assert k == j-left:String.format("%s = %s %s", k,j,left); | |
| 234 | arrN[ i ]++; | |
| 235 | if( n < 0 || n >= srcSize ) { | |
| 236 | weight = 0.0f;// Flag that cell should not be used | |
| 237 | } | |
| 238 | arrPixel[ subindex + k ] = n; | |
| 239 | arrWeight[ subindex + k ] = weight; | |
| 240 | } | |
| 241 | // normalize the filter's weight's so the sum equals to 1.0, very | |
| 242 | // important for avoiding box type of artifacts | |
| 243 | final int max = arrN[ i ]; | |
| 244 | float tot = 0; | |
| 245 | for( int k = 0; k < max; k++ ) { tot += arrWeight[ subindex + k ]; } | |
| 246 | if( tot != 0f ) { // 0 should never happen except bug in filter | |
| 247 | for( int k = 0; k < max; k++ ) { arrWeight[ subindex + k ] /= tot; } | |
| 248 | } | |
| 249 | } | |
| 250 | } | |
| 251 | else { | |
| 252 | // super-sampling | |
| 253 | // Scales from smaller to bigger height | |
| 254 | numContributors = (int) (fwidth * 2.0f + 1); | |
| 255 | arrWeight = new float[ dstSize * numContributors ]; | |
| 256 | arrPixel = new int[ dstSize * numContributors ]; | |
| 257 | // | |
| 258 | for( int i = 0; i < dstSize; i++ ) { | |
| 259 | final int subindex = i * numContributors; | |
| 260 | float center = i / scale + centerOffset; | |
| 261 | int left = (int) Math.floor( center - fwidth ); | |
| 262 | int right = (int) Math.ceil( center + fwidth ); | |
| 263 | for( int j = left; j <= right; j++ ) { | |
| 264 | float weight = filter.apply( center - j ); | |
| 265 | if( weight == 0.0f ) { | |
| 266 | continue; | |
| 267 | } | |
| 268 | int n; | |
| 269 | if( j < 0 ) { | |
| 270 | n = -j; | |
| 271 | } | |
| 272 | else if( j >= srcSize ) { | |
| 273 | n = srcSize - j + srcSize - 1; | |
| 274 | } | |
| 275 | else { | |
| 276 | n = j; | |
| 277 | } | |
| 278 | int k = arrN[ i ]; | |
| 279 | arrN[ i ]++; | |
| 280 | if( n < 0 || n >= srcSize ) { | |
| 281 | weight = 0.0f;// Flag that cell should not be used | |
| 282 | } | |
| 283 | arrPixel[ subindex + k ] = n; | |
| 284 | arrWeight[ subindex + k ] = weight; | |
| 285 | } | |
| 286 | // normalize the filter's weight's so the sum equals to 1.0, very | |
| 287 | // important for avoiding box type of artifacts | |
| 288 | final int max = arrN[ i ]; | |
| 289 | float tot = 0; | |
| 290 | for( int k = 0; k < max; k++ ) { tot += arrWeight[ subindex + k ]; } | |
| 291 | assert tot != 0 : "should never happen except bug in filter"; | |
| 292 | if( tot != 0f ) { | |
| 293 | for( int k = 0; k < max; k++ ) { arrWeight[ subindex + k ] /= tot; } | |
| 294 | } | |
| 295 | } | |
| 296 | } | |
| 297 | return new SubSamplingData( arrN, arrPixel, arrWeight, numContributors ); | |
| 298 | } | |
| 299 | ||
| 300 | private void verticalFromWorkToDst( byte[][] workPixels, byte[] outPixels, | |
| 301 | int start, int delta ) { | |
| 302 | if( nrChannels == 1 ) { | |
| 303 | verticalFromWorkToDstGray( | |
| 304 | workPixels, outPixels, start, threadCount ); | |
| 305 | return; | |
| 306 | } | |
| 307 | boolean useChannel3 = nrChannels > 3; | |
| 308 | for( int x = start; x < dstWidth; x += delta ) { | |
| 309 | final int xLocation = x * nrChannels; | |
| 310 | for( int y = dstHeight - 1; y >= 0; y-- ) { | |
| 311 | final int yTimesNumContributors = | |
| 312 | y * verticalSubsamplingData.numContributors; | |
| 313 | final int max = verticalSubsamplingData.arrN[ y ]; | |
| 314 | final int sampleLocation = (y * dstWidth + x) * nrChannels; | |
| 315 | ||
| 316 | float sample0 = 0.0f; | |
| 317 | float sample1 = 0.0f; | |
| 318 | float sample2 = 0.0f; | |
| 319 | float sample3 = 0.0f; | |
| 320 | int index = yTimesNumContributors; | |
| 321 | for( int j = max - 1; j >= 0; j-- ) { | |
| 322 | int valueLocation = verticalSubsamplingData.arrPixel[ index ]; | |
| 323 | float arrWeight = verticalSubsamplingData.arrWeight[ index ]; | |
| 324 | sample0 += (workPixels[ valueLocation ][ xLocation ] & 0xff) * arrWeight; | |
| 325 | sample1 += (workPixels[ valueLocation ][ xLocation + 1 ] & 0xff) * arrWeight; | |
| 326 | sample2 += (workPixels[ valueLocation ][ xLocation + 2 ] & 0xff) * arrWeight; | |
| 327 | if( useChannel3 ) { | |
| 328 | sample3 += (workPixels[ valueLocation ][ xLocation + 3 ] & 0xff) * arrWeight; | |
| 329 | } | |
| 330 | ||
| 331 | index++; | |
| 332 | } | |
| 333 | ||
| 334 | outPixels[ sampleLocation ] = toByte( sample0 ); | |
| 335 | outPixels[ sampleLocation + 1 ] = toByte( sample1 ); | |
| 336 | outPixels[ sampleLocation + 2 ] = toByte( sample2 ); | |
| 337 | ||
| 338 | if( useChannel3 ) { | |
| 339 | outPixels[ sampleLocation + 3 ] = toByte( sample3 ); | |
| 340 | } | |
| 341 | } | |
| 342 | } | |
| 343 | } | |
| 344 | ||
| 345 | private void verticalFromWorkToDstGray( | |
| 346 | byte[][] workPixels, byte[] outPixels, int start, int delta ) { | |
| 347 | for( int x = start; x < dstWidth; x += delta ) { | |
| 348 | for( int y = dstHeight - 1; y >= 0; y-- ) { | |
| 349 | final int yTimesNumContributors = | |
| 350 | y * verticalSubsamplingData.numContributors; | |
| 351 | final int max = verticalSubsamplingData.arrN[ y ]; | |
| 352 | final int sampleLocation = y * dstWidth + x; | |
| 353 | float sample0 = 0.0f; | |
| 354 | int index = yTimesNumContributors; | |
| 355 | ||
| 356 | for( int j = max - 1; j >= 0; j-- ) { | |
| 357 | int valueLocation = verticalSubsamplingData.arrPixel[ index ]; | |
| 358 | float arrWeight = verticalSubsamplingData.arrWeight[ index ]; | |
| 359 | sample0 += (workPixels[ valueLocation ][ x ] & 0xff) * arrWeight; | |
| 360 | ||
| 361 | index++; | |
| 362 | } | |
| 363 | ||
| 364 | outPixels[ sampleLocation ] = toByte( sample0 ); | |
| 365 | } | |
| 366 | } | |
| 367 | } | |
| 368 | ||
| 369 | /** | |
| 370 | * Apply filter to sample horizontally from Src to Work | |
| 371 | */ | |
| 372 | private void horizontallyFromSrcToWork( | |
| 373 | BufferedImage srcImg, byte[][] workPixels, int start, int delta ) { | |
| 374 | if( nrChannels == 1 ) { | |
| 375 | horizontallyFromSrcToWorkGray( srcImg, workPixels, start, delta ); | |
| 376 | return; | |
| 377 | } | |
| 378 | ||
| 379 | // Used if we work on int based bitmaps, later used to keep channel values | |
| 380 | final int[] tempPixels = new int[ srcWidth ]; | |
| 381 | // create reusable row to minimize memory overhead | |
| 382 | final byte[] srcPixels = new byte[ srcWidth * nrChannels ]; | |
| 383 | final boolean useChannel3 = nrChannels > 3; | |
| 384 | ||
| 385 | for( int k = start; k < srcHeight; k = k + delta ) { | |
| 386 | ImageUtils.getPixelsBGR( srcImg, k, srcWidth, srcPixels, tempPixels ); | |
| 387 | ||
| 388 | for( int i = dstWidth - 1; i >= 0; i-- ) { | |
| 389 | int sampleLocation = i * nrChannels; | |
| 390 | final int max = horizontalSubsamplingData.arrN[ i ]; | |
| 391 | ||
| 392 | float sample0 = 0.0f; | |
| 393 | float sample1 = 0.0f; | |
| 394 | float sample2 = 0.0f; | |
| 395 | float sample3 = 0.0f; | |
| 396 | int index = i * horizontalSubsamplingData.numContributors; | |
| 397 | for( int j = max - 1; j >= 0; j-- ) { | |
| 398 | float arrWeight = horizontalSubsamplingData.arrWeight[ index ]; | |
| 399 | int pixelIndex = | |
| 400 | horizontalSubsamplingData.arrPixel[ index ] * nrChannels; | |
| 401 | ||
| 402 | sample0 += (srcPixels[ pixelIndex ] & 0xff) * arrWeight; | |
| 403 | sample1 += (srcPixels[ pixelIndex + 1 ] & 0xff) * arrWeight; | |
| 404 | sample2 += (srcPixels[ pixelIndex + 2 ] & 0xff) * arrWeight; | |
| 405 | if( useChannel3 ) { | |
| 406 | sample3 += (srcPixels[ pixelIndex + 3 ] & 0xff) * arrWeight; | |
| 407 | } | |
| 408 | index++; | |
| 409 | } | |
| 410 | ||
| 411 | workPixels[ k ][ sampleLocation ] = toByte( sample0 ); | |
| 412 | workPixels[ k ][ sampleLocation + 1 ] = toByte( sample1 ); | |
| 413 | workPixels[ k ][ sampleLocation + 2 ] = toByte( sample2 ); | |
| 414 | if( useChannel3 ) { | |
| 415 | workPixels[ k ][ sampleLocation + 3 ] = toByte( sample3 ); | |
| 416 | } | |
| 417 | } | |
| 418 | } | |
| 419 | } | |
| 420 | ||
| 421 | /** | |
| 422 | * Apply filter to sample horizontally from Src to Work | |
| 423 | */ | |
| 424 | private void horizontallyFromSrcToWorkGray( | |
| 425 | BufferedImage srcImg, byte[][] workPixels, int start, int delta ) { | |
| 426 | // Used if we work on int based bitmaps, later used to keep channel values | |
| 427 | final int[] tempPixels = new int[ srcWidth ]; | |
| 428 | // create reusable row to minimize memory overhead | |
| 429 | final byte[] srcPixels = new byte[ srcWidth ]; | |
| 430 | ||
| 431 | for( int k = start; k < srcHeight; k = k + delta ) { | |
| 432 | ImageUtils.getPixelsBGR( srcImg, k, srcWidth, srcPixels, tempPixels ); | |
| 433 | ||
| 434 | for( int i = dstWidth - 1; i >= 0; i-- ) { | |
| 435 | final int max = horizontalSubsamplingData.arrN[ i ]; | |
| 436 | ||
| 437 | float sample0 = 0.0f; | |
| 438 | int index = i * horizontalSubsamplingData.numContributors; | |
| 439 | for( int j = max - 1; j >= 0; j-- ) { | |
| 440 | float arrWeight = horizontalSubsamplingData.arrWeight[ index ]; | |
| 441 | int pixelIndex = horizontalSubsamplingData.arrPixel[ index ]; | |
| 442 | ||
| 443 | sample0 += (srcPixels[ pixelIndex ] & 0xff) * arrWeight; | |
| 444 | index++; | |
| 445 | } | |
| 446 | ||
| 447 | workPixels[ k ][ i ] = toByte( sample0 ); | |
| 448 | } | |
| 449 | } | |
| 450 | } | |
| 451 | ||
| 452 | private static byte toByte( final float f ) { | |
| 453 | if( f < 0 ) { | |
| 454 | return 0; | |
| 455 | } | |
| 456 | ||
| 457 | return (byte) (f > MAX_CHANNEL_VALUE ? MAX_CHANNEL_VALUE : f + 0.5f); | |
| 458 | } | |
| 459 | ||
| 460 | protected int getResultBufferedImageType( BufferedImage srcImg ) { | |
| 461 | return nrChannels == 3 | |
| 462 | ? TYPE_3BYTE_BGR | |
| 463 | : nrChannels == 4 | |
| 464 | ? TYPE_4BYTE_ABGR | |
| 465 | : srcImg.getSampleModel().getDataType() == TYPE_USHORT | |
| 466 | ? TYPE_USHORT_GRAY | |
| 467 | : TYPE_BYTE_GRAY; | |
| 468 | } | |
| 469 | } | |
| 1 | 470 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | import java.util.Map; | |
| 5 | import java.util.function.Function; | |
| 6 | ||
| 7 | import static com.keenwrite.processors.text.TextReplacementFactory.replace; | |
| 8 | ||
| 9 | /** | |
| 10 | * Processes interpolated string definitions in the document and inserts | |
| 11 | * their values into the post-processed text. The default variable syntax is | |
| 12 | * {@code $variable$}. | |
| 13 | */ | |
| 14 | public class DefinitionProcessor | |
| 15 | extends ExecutorProcessor<String> implements Function<String, String> { | |
| 16 | ||
| 17 | private final Map<String, String> mDefinitions; | |
| 18 | ||
| 19 | /** | |
| 20 | * Constructs a processor capable of interpolating string definitions. | |
| 21 | * | |
| 22 | * @param successor Subsequent link in the processing chain. | |
| 23 | * @param context Contains resolved definitions map. | |
| 24 | */ | |
| 25 | public DefinitionProcessor( | |
| 26 | final Processor<String> successor, | |
| 27 | final ProcessorContext context ) { | |
| 28 | super( successor ); | |
| 29 | mDefinitions = context.getResolvedMap(); | |
| 30 | } | |
| 31 | ||
| 32 | /** | |
| 33 | * Processes the given text document by replacing variables with their values. | |
| 34 | * | |
| 35 | * @param text The document text that includes variables that should be | |
| 36 | * replaced with values when rendered as HTML. | |
| 37 | * @return The text with all variables replaced. | |
| 38 | */ | |
| 39 | @Override | |
| 40 | public String apply( final String text ) { | |
| 41 | return replace( text, getDefinitions() ); | |
| 42 | } | |
| 43 | ||
| 44 | /** | |
| 45 | * Returns the map to use for variable substitution. | |
| 46 | * | |
| 47 | * @return A map of variable names to values. | |
| 48 | */ | |
| 49 | protected Map<String, String> getDefinitions() { | |
| 50 | return mDefinitions; | |
| 51 | } | |
| 52 | } | |
| 1 | 53 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | import java.util.Optional; | |
| 5 | import java.util.concurrent.atomic.AtomicReference; | |
| 6 | ||
| 7 | /** | |
| 8 | * Responsible for transforming data through a variety of chained handlers. | |
| 9 | * | |
| 10 | * @param <T> The data type to process. | |
| 11 | */ | |
| 12 | public class ExecutorProcessor<T> implements Processor<T> { | |
| 13 | ||
| 14 | /** | |
| 15 | * The next link in the processing chain. | |
| 16 | */ | |
| 17 | private final Processor<T> mNext; | |
| 18 | ||
| 19 | protected ExecutorProcessor() { | |
| 20 | this( null ); | |
| 21 | } | |
| 22 | ||
| 23 | /** | |
| 24 | * Constructs a new processor having a given successor. | |
| 25 | * | |
| 26 | * @param successor The next processor in the chain. | |
| 27 | */ | |
| 28 | public ExecutorProcessor( final Processor<T> successor ) { | |
| 29 | mNext = successor; | |
| 30 | } | |
| 31 | ||
| 32 | /** | |
| 33 | * Calls every link in the chain to process the given data. | |
| 34 | * | |
| 35 | * @param data The data to transform. | |
| 36 | * @return The data after processing by every link in the chain. | |
| 37 | */ | |
| 38 | @Override | |
| 39 | public T apply( final T data ) { | |
| 40 | // Start processing using the first processor after the executor. | |
| 41 | Optional<Processor<T>> handler = next(); | |
| 42 | final var result = new MutableReference( data ); | |
| 43 | ||
| 44 | while( handler.isPresent() ) { | |
| 45 | handler = handler.flatMap( p -> { | |
| 46 | result.set( p.apply( result.get() ) ); | |
| 47 | return p.next(); | |
| 48 | } ); | |
| 49 | } | |
| 50 | ||
| 51 | return result.get(); | |
| 52 | } | |
| 53 | ||
| 54 | @Override | |
| 55 | public Optional<Processor<T>> next() { | |
| 56 | return Optional.ofNullable( mNext ); | |
| 57 | } | |
| 58 | ||
| 59 | /** | |
| 60 | * A minor micro-optimization, since the processors cannot be run in parallel, | |
| 61 | * avoid using an {@link AtomicReference} during processor execution. This | |
| 62 | * is about twice as fast for the first four processor links in the chain. | |
| 63 | */ | |
| 64 | private final class MutableReference { | |
| 65 | private T mObject; | |
| 66 | ||
| 67 | MutableReference( final T object ) { | |
| 68 | set( object ); | |
| 69 | } | |
| 70 | ||
| 71 | void set( final T object ) { | |
| 72 | mObject = object; | |
| 73 | } | |
| 74 | ||
| 75 | T get() { | |
| 76 | return mObject; | |
| 77 | } | |
| 78 | } | |
| 79 | } | |
| 1 | 80 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | import com.keenwrite.preview.HtmlPreview; | |
| 5 | ||
| 6 | /** | |
| 7 | * Responsible for notifying the {@link HtmlPreview} when the succession | |
| 8 | * chain has updated. This decouples knowledge of changes to the editor panel | |
| 9 | * from the HTML preview panel as well as any processing that takes place | |
| 10 | * before the final HTML preview is rendered. This is the last link in the | |
| 11 | * processor chain. | |
| 12 | */ | |
| 13 | public final class HtmlPreviewProcessor extends ExecutorProcessor<String> { | |
| 14 | /** | |
| 15 | * There is only one preview panel. | |
| 16 | */ | |
| 17 | private static HtmlPreview sHtmlPreviewPane; | |
| 18 | ||
| 19 | /** | |
| 20 | * Constructs the end of a processing chain. | |
| 21 | * | |
| 22 | * @param htmlPreviewPane The pane to update with the post-processed document. | |
| 23 | */ | |
| 24 | public HtmlPreviewProcessor( final HtmlPreview htmlPreviewPane ) { | |
| 25 | sHtmlPreviewPane = htmlPreviewPane; | |
| 26 | } | |
| 27 | ||
| 28 | /** | |
| 29 | * Update the preview panel using HTML from the succession chain. | |
| 30 | * | |
| 31 | * @param html The document content to render in the preview pane. The HTML | |
| 32 | * should not contain a doctype, head, or body tag. | |
| 33 | * @return The given {@code html} string. | |
| 34 | */ | |
| 35 | @Override | |
| 36 | public String apply( final String html ) { | |
| 37 | assert html != null; | |
| 38 | ||
| 39 | getHtmlPreviewPane().render( html ); | |
| 40 | return html; | |
| 41 | } | |
| 42 | ||
| 43 | private HtmlPreview getHtmlPreviewPane() { | |
| 44 | return sHtmlPreviewPane; | |
| 45 | } | |
| 46 | } | |
| 1 | 47 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | /** | |
| 5 | * Responsible for transforming a string into itself. This is used at the | |
| 6 | * end of a processing chain when no more processing is required. | |
| 7 | */ | |
| 8 | public final class IdentityProcessor extends ExecutorProcessor<String> { | |
| 9 | public static final IdentityProcessor IDENTITY = new IdentityProcessor(); | |
| 10 | ||
| 11 | /** | |
| 12 | * Constructs a new instance having no successor (the default successor is | |
| 13 | * {@code null}). | |
| 14 | */ | |
| 15 | private IdentityProcessor() { | |
| 16 | } | |
| 17 | ||
| 18 | /** | |
| 19 | * Returns the given string without modification. | |
| 20 | * | |
| 21 | * @param s The string to return. | |
| 22 | * @return The value of s. | |
| 23 | */ | |
| 24 | @Override | |
| 25 | public String apply( final String s ) { | |
| 26 | return s; | |
| 27 | } | |
| 28 | } | |
| 1 | 29 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | import com.keenwrite.typesetting.Typesetter; | |
| 5 | ||
| 6 | import java.io.IOException; | |
| 7 | ||
| 8 | import static com.keenwrite.Bootstrap.APP_TITLE_LOWERCASE; | |
| 9 | import static com.keenwrite.events.StatusEvent.clue; | |
| 10 | import static com.keenwrite.io.MediaType.TEXT_XML; | |
| 11 | import static java.nio.file.Files.deleteIfExists; | |
| 12 | import static java.nio.file.Files.writeString; | |
| 13 | ||
| 14 | /** | |
| 15 | * Responsible for using a typesetting engine to convert an XHTML document | |
| 16 | * into a PDF file. This must not be run from the JavaFX thread. | |
| 17 | */ | |
| 18 | public final class PdfProcessor extends ExecutorProcessor<String> { | |
| 19 | private final ProcessorContext mContext; | |
| 20 | ||
| 21 | public PdfProcessor( final ProcessorContext context ) { | |
| 22 | assert context != null; | |
| 23 | mContext = context; | |
| 24 | } | |
| 25 | ||
| 26 | /** | |
| 27 | * Converts a document by calling a third-party library to typeset the given | |
| 28 | * XHTML document. | |
| 29 | * | |
| 30 | * @param xhtml The document to convert to a PDF file. | |
| 31 | * @return {@code null} because there is no valid return value from generating | |
| 32 | * a PDF file. | |
| 33 | */ | |
| 34 | public String apply( final String xhtml ) { | |
| 35 | try { | |
| 36 | clue( "Main.status.typeset.create" ); | |
| 37 | final var document = TEXT_XML.createTemporaryFile( APP_TITLE_LOWERCASE ); | |
| 38 | final var pathInput = writeString( document, xhtml ); | |
| 39 | final var pathOutput = mContext.getExportPath(); | |
| 40 | final var typesetter = new Typesetter( mContext.getWorkspace() ); | |
| 41 | ||
| 42 | typesetter.typeset( pathInput, pathOutput ); | |
| 43 | ||
| 44 | // Smote the temporary file after typesetting the document. | |
| 45 | if( typesetter.autoclean() ) { | |
| 46 | deleteIfExists( document ); | |
| 47 | } | |
| 48 | else { | |
| 49 | document.toFile().deleteOnExit(); | |
| 50 | } | |
| 51 | } catch( final IOException | InterruptedException ex ) { | |
| 52 | // Typesetter runtime exceptions will pass up the call stack. | |
| 53 | clue( "Main.status.typeset.failed", ex ); | |
| 54 | } | |
| 55 | ||
| 56 | // Do not continue processing (the document was typeset into a binary). | |
| 57 | return null; | |
| 58 | } | |
| 59 | } | |
| 1 | 60 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | /** | |
| 5 | * This is the default processor used when an unknown file name extension is | |
| 6 | * encountered. It processes the text by enclosing it in an HTML {@code <pre>} | |
| 7 | * element. | |
| 8 | */ | |
| 9 | public final class PreformattedProcessor extends ExecutorProcessor<String> { | |
| 10 | ||
| 11 | /** | |
| 12 | * Passes the link to the super constructor. | |
| 13 | * | |
| 14 | * @param successor The next processor in the chain to use for text | |
| 15 | * processing. | |
| 16 | */ | |
| 17 | public PreformattedProcessor( final Processor<String> successor ) { | |
| 18 | super( successor ); | |
| 19 | } | |
| 20 | ||
| 21 | /** | |
| 22 | * Returns the given string, modified with "pre" tags. | |
| 23 | * | |
| 24 | * @param t The string to return, enclosed in "pre" tags. | |
| 25 | * @return The value of t wrapped in "pre" tags. | |
| 26 | */ | |
| 27 | @Override | |
| 28 | public String apply( final String t ) { | |
| 29 | return "<pre>" + t + "</pre>"; | |
| 30 | } | |
| 31 | } | |
| 1 | 32 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | import java.util.Optional; | |
| 5 | import java.util.function.UnaryOperator; | |
| 6 | ||
| 7 | /** | |
| 8 | * Responsible for processing documents from one known format to another. | |
| 9 | * Processes the given content providing a transformation from one document | |
| 10 | * format into another. For example, this could convert Markdown to HTML. | |
| 11 | * | |
| 12 | * @param <T> The data type to process. | |
| 13 | */ | |
| 14 | public interface Processor<T> extends UnaryOperator<T> { | |
| 15 | ||
| 16 | /** | |
| 17 | * Returns the next link in the processor chain. | |
| 18 | * | |
| 19 | * @return The processor intended to transform the data after this instance | |
| 20 | * has finished processing, or {@link Optional#empty} if this is the last | |
| 21 | * link in the chain. | |
| 22 | */ | |
| 23 | default Optional<Processor<T>> next() { | |
| 24 | return Optional.empty(); | |
| 25 | } | |
| 26 | } | |
| 1 | 27 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | import com.keenwrite.Caret; | |
| 5 | import com.keenwrite.ExportFormat; | |
| 6 | import com.keenwrite.constants.Constants; | |
| 7 | import com.keenwrite.io.FileType; | |
| 8 | import com.keenwrite.preferences.Workspace; | |
| 9 | import com.keenwrite.preview.HtmlPreview; | |
| 10 | ||
| 11 | import java.nio.file.Path; | |
| 12 | import java.util.Map; | |
| 13 | ||
| 14 | import static com.keenwrite.AbstractFileFactory.lookup; | |
| 15 | import static com.keenwrite.constants.Constants.DEFAULT_DIRECTORY; | |
| 16 | ||
| 17 | /** | |
| 18 | * Provides a context for configuring a chain of {@link Processor} instances. | |
| 19 | */ | |
| 20 | public final class ProcessorContext { | |
| 21 | private final HtmlPreview mHtmlPreview; | |
| 22 | private final Map<String, String> mResolvedMap; | |
| 23 | private final Path mDocumentPath; | |
| 24 | private final Path mExportPath; | |
| 25 | private final Caret mCaret; | |
| 26 | private final ExportFormat mExportFormat; | |
| 27 | private final Workspace mWorkspace; | |
| 28 | ||
| 29 | /** | |
| 30 | * Creates a new context for use by the {@link ProcessorFactory} when | |
| 31 | * instantiating new {@link Processor} instances. Although all the | |
| 32 | * parameters are required, not all {@link Processor} instances will use | |
| 33 | * all parameters. | |
| 34 | * | |
| 35 | * @param htmlPreview Where to display the final (HTML) output. | |
| 36 | * @param resolvedMap Fully expanded interpolated strings. | |
| 37 | * @param documentPath Path to the document to process. | |
| 38 | * @param exportPath Fully qualified filename to use when exporting. | |
| 39 | * @param exportFormat Indicate configuration options for export format. | |
| 40 | * @param workspace Persistent user preferences settings. | |
| 41 | * @param caret Location of the caret in the edited document, which is | |
| 42 | * used to synchronize the scrollbars. | |
| 43 | */ | |
| 44 | public ProcessorContext( | |
| 45 | final HtmlPreview htmlPreview, | |
| 46 | final Map<String, String> resolvedMap, | |
| 47 | final Path documentPath, | |
| 48 | final Path exportPath, | |
| 49 | final ExportFormat exportFormat, | |
| 50 | final Workspace workspace, | |
| 51 | final Caret caret ) { | |
| 52 | assert htmlPreview != null; | |
| 53 | assert resolvedMap != null; | |
| 54 | assert documentPath != null; | |
| 55 | assert exportFormat != null; | |
| 56 | assert workspace != null; | |
| 57 | assert caret != null; | |
| 58 | ||
| 59 | mHtmlPreview = htmlPreview; | |
| 60 | mResolvedMap = resolvedMap; | |
| 61 | mDocumentPath = documentPath; | |
| 62 | mCaret = caret; | |
| 63 | mExportPath = exportPath; | |
| 64 | mExportFormat = exportFormat; | |
| 65 | mWorkspace = workspace; | |
| 66 | } | |
| 67 | ||
| 68 | public boolean isExportFormat( final ExportFormat format ) { | |
| 69 | return mExportFormat == format; | |
| 70 | } | |
| 71 | ||
| 72 | HtmlPreview getPreview() { | |
| 73 | return mHtmlPreview; | |
| 74 | } | |
| 75 | ||
| 76 | /** | |
| 77 | * Returns the variable map of interpolated definitions. | |
| 78 | * | |
| 79 | * @return A map to help dereference variables. | |
| 80 | */ | |
| 81 | Map<String, String> getResolvedMap() { | |
| 82 | return mResolvedMap; | |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * Fully qualified file name to use when exporting (e.g., document.pdf). | |
| 87 | * | |
| 88 | * @return Full path to a file name. | |
| 89 | */ | |
| 90 | public Path getExportPath() { | |
| 91 | return mExportPath; | |
| 92 | } | |
| 93 | ||
| 94 | public ExportFormat getExportFormat() { | |
| 95 | return mExportFormat; | |
| 96 | } | |
| 97 | ||
| 98 | /** | |
| 99 | * Returns the current caret position in the document being edited and is | |
| 100 | * always up-to-date. | |
| 101 | * | |
| 102 | * @return Caret position in the document. | |
| 103 | */ | |
| 104 | public Caret getCaret() { | |
| 105 | return mCaret; | |
| 106 | } | |
| 107 | ||
| 108 | /** | |
| 109 | * Returns the directory that contains the file being edited. | |
| 110 | * When {@link Constants#DOCUMENT_DEFAULT} is created, the parent path is | |
| 111 | * {@code null}. This will get absolute path to the file before trying to | |
| 112 | * get te parent path, which should always be a valid path. In the unlikely | |
| 113 | * event that the base path cannot be determined by the path alone, the | |
| 114 | * default user directory is returned. This is necessary for the creation | |
| 115 | * of new files. | |
| 116 | * | |
| 117 | * @return Path to the directory containing a file being edited, or the | |
| 118 | * default user directory if the base path cannot be determined. | |
| 119 | */ | |
| 120 | public Path getBaseDir() { | |
| 121 | final var path = getDocumentPath().toAbsolutePath().getParent(); | |
| 122 | return path == null ? DEFAULT_DIRECTORY : path; | |
| 123 | } | |
| 124 | ||
| 125 | public Path getDocumentPath() { | |
| 126 | return mDocumentPath; | |
| 127 | } | |
| 128 | ||
| 129 | FileType getFileType() { | |
| 130 | return lookup( getDocumentPath() ); | |
| 131 | } | |
| 132 | ||
| 133 | public Workspace getWorkspace() { | |
| 134 | return mWorkspace; | |
| 135 | } | |
| 136 | } | |
| 1 | 137 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | import com.keenwrite.AbstractFileFactory; | |
| 5 | import com.keenwrite.preview.HtmlPreview; | |
| 6 | import com.keenwrite.processors.markdown.MarkdownProcessor; | |
| 7 | ||
| 8 | import static com.keenwrite.ExportFormat.*; | |
| 9 | import static com.keenwrite.processors.IdentityProcessor.IDENTITY; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for creating processors capable of parsing, transforming, | |
| 13 | * interpolating, and rendering known file types. | |
| 14 | */ | |
| 15 | public final class ProcessorFactory extends AbstractFileFactory { | |
| 16 | ||
| 17 | private final ProcessorContext mContext; | |
| 18 | ||
| 19 | /** | |
| 20 | * Constructs a factory with the ability to create processors that can perform | |
| 21 | * text and caret processing to generate a final preview. | |
| 22 | * | |
| 23 | * @param context Parameters needed to construct various processors. | |
| 24 | */ | |
| 25 | private ProcessorFactory( final ProcessorContext context ) { | |
| 26 | mContext = context; | |
| 27 | } | |
| 28 | ||
| 29 | private Processor<String> createProcessor() { | |
| 30 | final var context = getProcessorContext(); | |
| 31 | ||
| 32 | // If the content is not to be exported, then the successor processor | |
| 33 | // is one that parses Markdown into HTML and passes the string to the | |
| 34 | // HTML preview pane. | |
| 35 | // | |
| 36 | // Otherwise, bolt on a processor that---after the interpolation and | |
| 37 | // substitution phase, which includes text strings or R code---will | |
| 38 | // generate HTML or plain Markdown. HTML has a few output formats: | |
| 39 | // with embedded SVG representing formulas, or without any conversion | |
| 40 | // to SVG. Without conversion would require client-side rendering of | |
| 41 | // math (such as using the JavaScript-based KaTeX engine). | |
| 42 | final var successor = context.isExportFormat( NONE ) | |
| 43 | ? createHtmlPreviewProcessor( context ) | |
| 44 | : context.isExportFormat( XHTML_TEX ) | |
| 45 | ? createXhtmlProcessor( context ) | |
| 46 | : context.isExportFormat( APPLICATION_PDF ) | |
| 47 | ? createPdfProcessor( context ) | |
| 48 | : createIdentityProcessor( context ); | |
| 49 | ||
| 50 | final var processor = switch( context.getFileType() ) { | |
| 51 | case SOURCE, RMARKDOWN -> createMarkdownProcessor( successor ); | |
| 52 | default -> createPreformattedProcessor( successor ); | |
| 53 | }; | |
| 54 | ||
| 55 | return new ExecutorProcessor<>( processor ); | |
| 56 | } | |
| 57 | ||
| 58 | /** | |
| 59 | * Creates a new {@link Processor} chain suitable for parsing and rendering | |
| 60 | * the file opened at the given tab. | |
| 61 | * | |
| 62 | * @param context The tab containing a text editor, path, and caret position. | |
| 63 | * @return A processor that can render the given tab's text. | |
| 64 | */ | |
| 65 | public static Processor<String> createProcessors( | |
| 66 | final ProcessorContext context ) { | |
| 67 | return new ProcessorFactory( context ).createProcessor(); | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Instantiates a new {@link Processor} that has no successor and returns | |
| 72 | * the string it was given without modification. | |
| 73 | * | |
| 74 | * @return An instance of {@link Processor} that performs no processing. | |
| 75 | */ | |
| 76 | @SuppressWarnings( "unused" ) | |
| 77 | private Processor<String> createIdentityProcessor( | |
| 78 | final ProcessorContext ignored ) { | |
| 79 | return IDENTITY; | |
| 80 | } | |
| 81 | ||
| 82 | /** | |
| 83 | * Instantiates a new {@link Processor} that passes an incoming HTML | |
| 84 | * string to a user interface widget that can render HTML as a web page. | |
| 85 | * | |
| 86 | * @return An instance of {@link Processor} that forwards HTML for display. | |
| 87 | */ | |
| 88 | @SuppressWarnings( "unused" ) | |
| 89 | private Processor<String> createHtmlPreviewProcessor( | |
| 90 | final ProcessorContext ignored ) { | |
| 91 | return new HtmlPreviewProcessor( getPreviewPane() ); | |
| 92 | } | |
| 93 | ||
| 94 | /** | |
| 95 | * Instantiates a {@link Processor} responsible for parsing Markdown and | |
| 96 | * definitions. | |
| 97 | * | |
| 98 | * @return A chain of {@link Processor}s for processing Markdown and | |
| 99 | * definitions. | |
| 100 | */ | |
| 101 | private Processor<String> createMarkdownProcessor( | |
| 102 | final Processor<String> successor ) { | |
| 103 | final var dp = createDefinitionProcessor( successor ); | |
| 104 | return MarkdownProcessor.create( dp, getProcessorContext() ); | |
| 105 | } | |
| 106 | ||
| 107 | private Processor<String> createDefinitionProcessor( | |
| 108 | final Processor<String> successor ) { | |
| 109 | return new DefinitionProcessor( successor, getProcessorContext() ); | |
| 110 | } | |
| 111 | ||
| 112 | /** | |
| 113 | * Instantiates a new {@link Processor} that wraps an HTML document into | |
| 114 | * its final, well-formed state (including head and body tags). This is | |
| 115 | * useful for generating XHTML documents suitable for typesetting (using | |
| 116 | * an engine such as LuaTeX). | |
| 117 | * | |
| 118 | * @return An instance of {@link Processor} that completes an HTML document. | |
| 119 | */ | |
| 120 | private Processor<String> createXhtmlProcessor( | |
| 121 | final ProcessorContext context ) { | |
| 122 | return createXhtmlProcessor( IDENTITY, context ); | |
| 123 | } | |
| 124 | ||
| 125 | private Processor<String> createXhtmlProcessor( | |
| 126 | final Processor<String> successor, final ProcessorContext context ) { | |
| 127 | return new XhtmlProcessor( successor, context ); | |
| 128 | } | |
| 129 | ||
| 130 | private Processor<String> createPdfProcessor( | |
| 131 | final ProcessorContext context ) { | |
| 132 | final var pdfp = new PdfProcessor( context ); | |
| 133 | return createXhtmlProcessor( pdfp, context ); | |
| 134 | } | |
| 135 | ||
| 136 | private Processor<String> createPreformattedProcessor( | |
| 137 | final Processor<String> successor ) { | |
| 138 | return new PreformattedProcessor( successor ); | |
| 139 | } | |
| 140 | ||
| 141 | private ProcessorContext getProcessorContext() { | |
| 142 | return mContext; | |
| 143 | } | |
| 144 | ||
| 145 | private HtmlPreview getPreviewPane() { | |
| 146 | return getProcessorContext().getPreview(); | |
| 147 | } | |
| 148 | } | |
| 1 | 149 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors; | |
| 3 | ||
| 4 | import com.keenwrite.dom.DocumentParser; | |
| 5 | import com.keenwrite.preferences.Key; | |
| 6 | import com.keenwrite.preferences.Workspace; | |
| 7 | import com.keenwrite.ui.heuristics.WordCounter; | |
| 8 | import com.whitemagicsoftware.keenquotes.Converter; | |
| 9 | import javafx.beans.property.StringProperty; | |
| 10 | import org.w3c.dom.Document; | |
| 11 | ||
| 12 | import java.io.FileNotFoundException; | |
| 13 | import java.nio.file.Path; | |
| 14 | import java.util.Locale; | |
| 15 | import java.util.Map; | |
| 16 | import java.util.regex.Pattern; | |
| 17 | ||
| 18 | import static com.keenwrite.Bootstrap.APP_TITLE_LOWERCASE; | |
| 19 | import static com.keenwrite.dom.DocumentParser.*; | |
| 20 | import static com.keenwrite.events.StatusEvent.clue; | |
| 21 | import static com.keenwrite.io.HttpFacade.httpGet; | |
| 22 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 23 | import static com.keenwrite.processors.text.TextReplacementFactory.replace; | |
| 24 | import static com.keenwrite.util.ProtocolScheme.getProtocol; | |
| 25 | import static com.whitemagicsoftware.keenquotes.Converter.CHARS; | |
| 26 | import static com.whitemagicsoftware.keenquotes.ParserFactory.ParserType.PARSER_XML; | |
| 27 | import static java.lang.String.format; | |
| 28 | import static java.lang.String.valueOf; | |
| 29 | import static java.nio.file.Files.copy; | |
| 30 | import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; | |
| 31 | import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS; | |
| 32 | import static java.util.regex.Pattern.compile; | |
| 33 | ||
| 34 | /** | |
| 35 | * Responsible for making an XHTML document complete by wrapping it with html | |
| 36 | * and body elements. This doesn't have to be super-efficient because it's | |
| 37 | * not run in real-time. | |
| 38 | */ | |
| 39 | public final class XhtmlProcessor extends ExecutorProcessor<String> { | |
| 40 | private final static Pattern BLANK = | |
| 41 | compile( "\\p{Blank}", UNICODE_CHARACTER_CLASS ); | |
| 42 | ||
| 43 | private final static Converter sTypographer = | |
| 44 | new Converter( lex -> clue( lex.toString() ), CHARS, PARSER_XML ); | |
| 45 | ||
| 46 | private final ProcessorContext mContext; | |
| 47 | ||
| 48 | public XhtmlProcessor( | |
| 49 | final Processor<String> successor, final ProcessorContext context ) { | |
| 50 | super( successor ); | |
| 51 | ||
| 52 | assert context != null; | |
| 53 | mContext = context; | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Responsible for producing a well-formed XML document complete with | |
| 58 | * metadata (title, author, keywords, copyright, and date). | |
| 59 | * | |
| 60 | * @param html The HTML document to transform into an XHTML document. | |
| 61 | * @return The transformed HTML document. | |
| 62 | */ | |
| 63 | @Override | |
| 64 | public String apply( final String html ) { | |
| 65 | clue( "Main.status.typeset.xhtml" ); | |
| 66 | ||
| 67 | try { | |
| 68 | final var doc = DocumentParser.parse( decorate( html ) ); | |
| 69 | setMetaData( doc ); | |
| 70 | ||
| 71 | walk( doc, "//img", node -> { | |
| 72 | try { | |
| 73 | final var attrs = node.getAttributes(); | |
| 74 | ||
| 75 | if( attrs != null ) { | |
| 76 | final var attr = attrs.getNamedItem( "src" ); | |
| 77 | ||
| 78 | if( attr != null ) { | |
| 79 | final var imageFile = exportImage( attr.getTextContent() ); | |
| 80 | ||
| 81 | attr.setTextContent( imageFile.toString() ); | |
| 82 | } | |
| 83 | } | |
| 84 | } catch( final Exception ex ) { | |
| 85 | clue( ex ); | |
| 86 | } | |
| 87 | } ); | |
| 88 | ||
| 89 | final var document = DocumentParser.toString( doc ); | |
| 90 | ||
| 91 | return curl() ? sTypographer.apply( document ) : document; | |
| 92 | } catch( final Exception ex ) { | |
| 93 | clue( ex ); | |
| 94 | } | |
| 95 | ||
| 96 | return html; | |
| 97 | } | |
| 98 | ||
| 99 | /** | |
| 100 | * Applies the metadata fields to the document. | |
| 101 | * | |
| 102 | * @param doc The document to adorn with metadata. | |
| 103 | */ | |
| 104 | private void setMetaData( final Document doc ) { | |
| 105 | final var metadata = createMetaData( doc ); | |
| 106 | ||
| 107 | walk( doc, "/html/head", node -> | |
| 108 | metadata.entrySet() | |
| 109 | .forEach( entry -> node.appendChild( createMeta( doc, entry ) ) ) | |
| 110 | ); | |
| 111 | walk( doc, "/html/head/title", node -> node.setTextContent( title() ) ); | |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * Generates document metadata, including word count. | |
| 116 | * | |
| 117 | * @param doc The document containing the text to tally. | |
| 118 | * @return A map of metadata key/value pairs. | |
| 119 | */ | |
| 120 | private Map<String, String> createMetaData( final Document doc ) { | |
| 121 | return Map.of( | |
| 122 | "author", author(), | |
| 123 | "byline", byLine(), | |
| 124 | "address", address(), | |
| 125 | "phone", phone(), | |
| 126 | "email", email(), | |
| 127 | "count", wordCount( doc ), | |
| 128 | "keywords", keywords(), | |
| 129 | "copyright", copyright(), | |
| 130 | "date", date() | |
| 131 | ); | |
| 132 | } | |
| 133 | ||
| 134 | /** | |
| 135 | * For a given src URI, this method will attempt to normalize it such that a | |
| 136 | * third-party application can find the file. Normalization could entail | |
| 137 | * downloading from the Internet or finding a suitable file name extension. | |
| 138 | * | |
| 139 | * @param src A path, local or remote, to a partial or complete file name. | |
| 140 | * @return A local file system path to the source path. | |
| 141 | * @throws Exception Could not read from, write to, or find a file. | |
| 142 | */ | |
| 143 | private Path exportImage( final String src ) throws Exception { | |
| 144 | Path imageFile = null; | |
| 145 | ||
| 146 | final var protocol = getProtocol( src ); | |
| 147 | ||
| 148 | // Download remote resources into temporary files. | |
| 149 | if( protocol.isRemote() ) { | |
| 150 | final var response = httpGet( src ); | |
| 151 | final var mediaType = response.getMediaType(); | |
| 152 | ||
| 153 | imageFile = mediaType.createTemporaryFile( APP_TITLE_LOWERCASE ); | |
| 154 | ||
| 155 | try( final var image = response.getInputStream() ) { | |
| 156 | copy( image, imageFile, REPLACE_EXISTING ); | |
| 157 | } | |
| 158 | ||
| 159 | // Strip comments, superfluous whitespace, DOCTYPE, and XML declarations. | |
| 160 | if( mediaType.isSvg() ) { | |
| 161 | DocumentParser.sanitize( imageFile ); | |
| 162 | } | |
| 163 | } | |
| 164 | else { | |
| 165 | final var extensions = " " + getImageOrder().trim(); | |
| 166 | var imagePath = getImagePath(); | |
| 167 | var found = false; | |
| 168 | ||
| 169 | // By including " " in the extensions, the first element returned | |
| 170 | // will be the empty string. Thus the first extension to try is the | |
| 171 | // file's default extension. Subsequent iterations will try to find | |
| 172 | // a file that has a name matching one of the preferred extensions. | |
| 173 | for( final var extension : BLANK.split( extensions ) ) { | |
| 174 | final var filename = format( | |
| 175 | "%s%s%s", src, extension.isBlank() ? "" : ".", extension ); | |
| 176 | imageFile = Path.of( imagePath, filename ); | |
| 177 | ||
| 178 | if( imageFile.toFile().exists() ) { | |
| 179 | found = true; | |
| 180 | break; | |
| 181 | } | |
| 182 | } | |
| 183 | ||
| 184 | if( !found ) { | |
| 185 | imagePath = getDocumentDir().toString(); | |
| 186 | imageFile = Path.of( imagePath, src ); | |
| 187 | ||
| 188 | if( !imageFile.toFile().exists() ) { | |
| 189 | throw new FileNotFoundException( imageFile.toString() ); | |
| 190 | } | |
| 191 | } | |
| 192 | } | |
| 193 | ||
| 194 | return imageFile; | |
| 195 | } | |
| 196 | ||
| 197 | private String getImagePath() { | |
| 198 | return getWorkspace().toFile( KEY_IMAGES_DIR ).toString(); | |
| 199 | } | |
| 200 | ||
| 201 | private String getImageOrder() { | |
| 202 | return getWorkspace().toString( KEY_IMAGES_ORDER ); | |
| 203 | } | |
| 204 | ||
| 205 | /** | |
| 206 | * Returns the absolute path to the document being edited, which can be used | |
| 207 | * to find files included using relative paths. | |
| 208 | * | |
| 209 | * @return The directory containing the edited file. | |
| 210 | */ | |
| 211 | private Path getDocumentDir() { | |
| 212 | return mContext.getBaseDir(); | |
| 213 | } | |
| 214 | ||
| 215 | private Workspace getWorkspace() { | |
| 216 | return mContext.getWorkspace(); | |
| 217 | } | |
| 218 | ||
| 219 | private Locale locale() { return getWorkspace().getLocale(); } | |
| 220 | ||
| 221 | private String title() { | |
| 222 | return resolve( KEY_DOC_TITLE ); | |
| 223 | } | |
| 224 | ||
| 225 | private String author() { | |
| 226 | return resolve( KEY_DOC_AUTHOR ); | |
| 227 | } | |
| 228 | ||
| 229 | private String byLine() { | |
| 230 | return resolve( KEY_DOC_BYLINE ); | |
| 231 | } | |
| 232 | ||
| 233 | private String address() { | |
| 234 | return resolve( KEY_DOC_ADDRESS ).replaceAll( "\n", "\\\\\\break{}" ); | |
| 235 | } | |
| 236 | ||
| 237 | private String phone() { | |
| 238 | return resolve( KEY_DOC_PHONE ); | |
| 239 | } | |
| 240 | ||
| 241 | private String email() { | |
| 242 | return resolve( KEY_DOC_EMAIL ); | |
| 243 | } | |
| 244 | ||
| 245 | private String wordCount( final Document doc ) { | |
| 246 | final var sb = new StringBuilder( 65536 * 10 ); | |
| 247 | ||
| 248 | walk( | |
| 249 | doc, | |
| 250 | "//*[normalize-space( text() ) != '']", | |
| 251 | node -> sb.append( node.getTextContent() ) | |
| 252 | ); | |
| 253 | ||
| 254 | return valueOf( WordCounter.create( locale() ).count( sb.toString() ) ); | |
| 255 | } | |
| 256 | ||
| 257 | private String keywords() { | |
| 258 | return resolve( KEY_DOC_KEYWORDS ); | |
| 259 | } | |
| 260 | ||
| 261 | private String copyright() { | |
| 262 | return resolve( KEY_DOC_COPYRIGHT ); | |
| 263 | } | |
| 264 | ||
| 265 | private String date() { | |
| 266 | return resolve( KEY_DOC_DATE ); | |
| 267 | } | |
| 268 | ||
| 269 | /** | |
| 270 | * Answers whether straight quotation marks should be curled. | |
| 271 | * | |
| 272 | * @return {@code false} to prevent curling straight quotes. | |
| 273 | */ | |
| 274 | private boolean curl() { | |
| 275 | return getWorkspace().toBoolean( KEY_TYPESET_TYPOGRAPHY_QUOTES ); | |
| 276 | } | |
| 277 | ||
| 278 | private String resolve( final Key key ) { | |
| 279 | return replace( asString( key ), mContext.getResolvedMap() ); | |
| 280 | } | |
| 281 | ||
| 282 | private String asString( final Key key ) { | |
| 283 | return stringProperty( key ).get(); | |
| 284 | } | |
| 285 | ||
| 286 | private StringProperty stringProperty( final Key key ) { | |
| 287 | return getWorkspace().stringProperty( key ); | |
| 288 | } | |
| 289 | } | |
| 1 | 290 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown; | |
| 3 | ||
| 4 | import com.keenwrite.processors.ExecutorProcessor; | |
| 5 | import com.keenwrite.processors.Processor; | |
| 6 | import com.keenwrite.processors.ProcessorContext; | |
| 7 | import com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension; | |
| 8 | import com.keenwrite.processors.markdown.extensions.r.RExtension; | |
| 9 | import com.vladsch.flexmark.ext.definition.DefinitionExtension; | |
| 10 | import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension; | |
| 11 | import com.vladsch.flexmark.ext.superscript.SuperscriptExtension; | |
| 12 | import com.vladsch.flexmark.ext.tables.TablesExtension; | |
| 13 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 14 | import com.vladsch.flexmark.parser.Parser; | |
| 15 | import com.vladsch.flexmark.util.ast.IParse; | |
| 16 | import com.vladsch.flexmark.util.ast.IRender; | |
| 17 | import com.vladsch.flexmark.util.ast.Node; | |
| 18 | import com.vladsch.flexmark.util.misc.Extension; | |
| 19 | ||
| 20 | import java.util.ArrayList; | |
| 21 | import java.util.List; | |
| 22 | ||
| 23 | /** | |
| 24 | * Responsible for parsing and rendering Markdown into HTML. This is required | |
| 25 | * to break a circular dependency between the {@link MarkdownProcessor} and | |
| 26 | * {@link RExtension}. | |
| 27 | */ | |
| 28 | public class BaseMarkdownProcessor extends ExecutorProcessor<String> { | |
| 29 | ||
| 30 | private final IParse mParser; | |
| 31 | private final IRender mRenderer; | |
| 32 | ||
| 33 | public BaseMarkdownProcessor( | |
| 34 | final Processor<String> successor, final ProcessorContext context ) { | |
| 35 | super( successor ); | |
| 36 | ||
| 37 | final var builder = Parser.builder(); | |
| 38 | final var extensions = createExtensions( context ); | |
| 39 | mParser = builder.extensions( extensions ).build(); | |
| 40 | mRenderer = HtmlRenderer.builder().extensions( extensions ).build(); | |
| 41 | } | |
| 42 | ||
| 43 | /** | |
| 44 | * Instantiates a number of extensions to be applied when parsing. | |
| 45 | * | |
| 46 | * @param context The context that subclasses use to configure custom | |
| 47 | * extension behaviour. | |
| 48 | * @return A {@link List} of {@link Extension} instances that change the | |
| 49 | * {@link Parser}'s behaviour. | |
| 50 | */ | |
| 51 | List<Extension> createExtensions( final ProcessorContext context ) { | |
| 52 | final var extensions = new ArrayList<Extension>(); | |
| 53 | ||
| 54 | extensions.add( DefinitionExtension.create() ); | |
| 55 | extensions.add( StrikethroughSubscriptExtension.create() ); | |
| 56 | extensions.add( SuperscriptExtension.create() ); | |
| 57 | extensions.add( TablesExtension.create() ); | |
| 58 | extensions.add( FencedDivExtension.create() ); | |
| 59 | ||
| 60 | return extensions; | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * Converts the given Markdown string into HTML, without the doctype, html, | |
| 65 | * head, and body tags. | |
| 66 | * | |
| 67 | * @param markdown The string to convert from Markdown to HTML. | |
| 68 | * @return The HTML representation of the Markdown document. | |
| 69 | */ | |
| 70 | @Override | |
| 71 | public String apply( final String markdown ) { | |
| 72 | return toHtml( parse( markdown ) ); | |
| 73 | } | |
| 74 | ||
| 75 | /** | |
| 76 | * Returns the AST in the form of a node for the given Markdown document. This | |
| 77 | * can be used, for example, to determine if a hyperlink exists inside of a | |
| 78 | * paragraph. | |
| 79 | * | |
| 80 | * @param markdown The Markdown to convert into an AST. | |
| 81 | * @return The Markdown AST for the given text (usually a paragraph). | |
| 82 | */ | |
| 83 | public Node toNode( final String markdown ) { | |
| 84 | return parse( markdown ); | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Returns the result of converting the given AST into an HTML string. | |
| 89 | * | |
| 90 | * @param node The AST {@link Node} to convert to an HTML string. | |
| 91 | * @return The given {@link Node} as an HTML string. | |
| 92 | */ | |
| 93 | public String toHtml( final Node node ) { | |
| 94 | return getRenderer().render( node ); | |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * Helper method to create an AST given some Markdown. | |
| 99 | * | |
| 100 | * @param markdown The Markdown to parse. | |
| 101 | * @return The root node of the Markdown tree. | |
| 102 | */ | |
| 103 | private Node parse( final String markdown ) { | |
| 104 | return getParser().parse( markdown ); | |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Creates the Markdown document processor. | |
| 109 | * | |
| 110 | * @return An instance of {@link IParse} for building abstract syntax trees. | |
| 111 | */ | |
| 112 | private IParse getParser() { | |
| 113 | return mParser; | |
| 114 | } | |
| 115 | ||
| 116 | private IRender getRenderer() { | |
| 117 | return mRenderer; | |
| 118 | } | |
| 119 | } | |
| 1 | 120 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown; | |
| 3 | ||
| 4 | import com.keenwrite.io.MediaType; | |
| 5 | import com.keenwrite.processors.DefinitionProcessor; | |
| 6 | import com.keenwrite.processors.Processor; | |
| 7 | import com.keenwrite.processors.ProcessorContext; | |
| 8 | import com.keenwrite.processors.markdown.extensions.*; | |
| 9 | import com.keenwrite.processors.markdown.extensions.fences.FencedBlockExtension; | |
| 10 | import com.keenwrite.processors.markdown.extensions.r.RExtension; | |
| 11 | import com.keenwrite.processors.markdown.extensions.tex.TeXExtension; | |
| 12 | import com.keenwrite.processors.r.RProcessor; | |
| 13 | import com.vladsch.flexmark.util.misc.Extension; | |
| 14 | ||
| 15 | import java.util.ArrayList; | |
| 16 | import java.util.List; | |
| 17 | ||
| 18 | import static com.keenwrite.io.MediaType.TEXT_R_MARKDOWN; | |
| 19 | import static com.keenwrite.processors.IdentityProcessor.IDENTITY; | |
| 20 | ||
| 21 | /** | |
| 22 | * Responsible for parsing a Markdown document and rendering it as HTML. | |
| 23 | */ | |
| 24 | public final class MarkdownProcessor extends BaseMarkdownProcessor { | |
| 25 | ||
| 26 | private MarkdownProcessor( | |
| 27 | final Processor<String> successor, final ProcessorContext context ) { | |
| 28 | super( successor, context ); | |
| 29 | } | |
| 30 | ||
| 31 | public static MarkdownProcessor create( final ProcessorContext context ) { | |
| 32 | return create( IDENTITY, context ); | |
| 33 | } | |
| 34 | ||
| 35 | public static MarkdownProcessor create( | |
| 36 | final Processor<String> successor, final ProcessorContext context ) { | |
| 37 | return new MarkdownProcessor( successor, context ); | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * Creating extensions based using an instance of {@link ProcessorContext} | |
| 42 | * indicates that the {@link CaretExtension} should be used to inject the | |
| 43 | * caret position into the final HTML document. This enables the HTML | |
| 44 | * preview pane to scroll to the same position, relatively speaking, within | |
| 45 | * the main document. Scrolling is developed this way to decouple the | |
| 46 | * document being edited from the preview pane so that multiple document | |
| 47 | * formats can be edited. | |
| 48 | * | |
| 49 | * @param context Contains necessary information needed to create | |
| 50 | * extensions used by the Markdown parser. | |
| 51 | * @return {@link List} of extensions invoked when parsing Markdown. | |
| 52 | */ | |
| 53 | @Override | |
| 54 | List<Extension> createExtensions( final ProcessorContext context ) { | |
| 55 | final var editorFile = context.getDocumentPath(); | |
| 56 | final var mediaType = MediaType.valueFrom( editorFile ); | |
| 57 | final Processor<String> processor; | |
| 58 | final List<Extension> extensions = new ArrayList<>(); | |
| 59 | ||
| 60 | if( mediaType == TEXT_R_MARKDOWN ) { | |
| 61 | final var rProcessor = new RProcessor( context ); | |
| 62 | extensions.add( RExtension.create( rProcessor, context ) ); | |
| 63 | processor = rProcessor; | |
| 64 | } | |
| 65 | else { | |
| 66 | processor = new DefinitionProcessor( IDENTITY, context ); | |
| 67 | } | |
| 68 | ||
| 69 | // Add typographic, table, strikethrough, and similar extensions. | |
| 70 | extensions.addAll( super.createExtensions( context ) ); | |
| 71 | ||
| 72 | extensions.add( ImageLinkExtension.create( context ) ); | |
| 73 | extensions.add( TeXExtension.create( processor, context ) ); | |
| 74 | extensions.add( FencedBlockExtension.create( processor, context ) ); | |
| 75 | extensions.add( CaretExtension.create( context ) ); | |
| 76 | extensions.add( DocumentOutlineExtension.create( processor ) ); | |
| 77 | return extensions; | |
| 78 | } | |
| 79 | } | |
| 1 | 80 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions; | |
| 3 | ||
| 4 | import com.keenwrite.Caret; | |
| 5 | import com.keenwrite.constants.Constants; | |
| 6 | import com.keenwrite.processors.ProcessorContext; | |
| 7 | import com.vladsch.flexmark.ext.tables.TableBlock; | |
| 8 | import com.vladsch.flexmark.html.AttributeProvider; | |
| 9 | import com.vladsch.flexmark.html.AttributeProviderFactory; | |
| 10 | import com.vladsch.flexmark.html.IndependentAttributeProviderFactory; | |
| 11 | import com.vladsch.flexmark.html.renderer.AttributablePart; | |
| 12 | import com.vladsch.flexmark.html.renderer.LinkResolverContext; | |
| 13 | import com.vladsch.flexmark.util.ast.Node; | |
| 14 | import com.vladsch.flexmark.util.html.AttributeImpl; | |
| 15 | import com.vladsch.flexmark.util.html.MutableAttributes; | |
| 16 | import org.jetbrains.annotations.NotNull; | |
| 17 | ||
| 18 | import static com.keenwrite.constants.Constants.CARET_ID; | |
| 19 | import static com.keenwrite.processors.markdown.extensions.EmptyNode.EMPTY_NODE; | |
| 20 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 21 | ||
| 22 | /** | |
| 23 | * Responsible for giving most block-level elements a unique identifier | |
| 24 | * attribute. The identifier is used to coordinate scrolling. | |
| 25 | */ | |
| 26 | public class CaretExtension extends HtmlRendererAdapter { | |
| 27 | ||
| 28 | private final Caret mCaret; | |
| 29 | ||
| 30 | private CaretExtension( final ProcessorContext context ) { | |
| 31 | mCaret = context.getCaret(); | |
| 32 | } | |
| 33 | ||
| 34 | public static CaretExtension create( final ProcessorContext context ) { | |
| 35 | return new CaretExtension( context ); | |
| 36 | } | |
| 37 | ||
| 38 | @Override | |
| 39 | public void extend( @NotNull final Builder builder, | |
| 40 | @NotNull final String rendererType ) { | |
| 41 | builder.attributeProviderFactory( | |
| 42 | IdAttributeProvider.createFactory( mCaret ) ); | |
| 43 | } | |
| 44 | ||
| 45 | /** | |
| 46 | * Responsible for creating the id attribute. This class is instantiated | |
| 47 | * once: for the HTML element containing the {@link Constants#CARET_ID}. | |
| 48 | */ | |
| 49 | public static class IdAttributeProvider implements AttributeProvider { | |
| 50 | private final Caret mCaret; | |
| 51 | private boolean mAdded; | |
| 52 | ||
| 53 | public IdAttributeProvider( final Caret caret ) { | |
| 54 | mCaret = caret; | |
| 55 | } | |
| 56 | ||
| 57 | private static AttributeProviderFactory createFactory( final Caret caret ) { | |
| 58 | return new IndependentAttributeProviderFactory() { | |
| 59 | @Override | |
| 60 | public @NotNull AttributeProvider apply( | |
| 61 | @NotNull final LinkResolverContext context ) { | |
| 62 | return new IdAttributeProvider( caret ); | |
| 63 | } | |
| 64 | }; | |
| 65 | } | |
| 66 | ||
| 67 | @Override | |
| 68 | public void setAttributes( @NotNull Node curr, | |
| 69 | @NotNull AttributablePart part, | |
| 70 | @NotNull MutableAttributes attributes ) { | |
| 71 | // Optimization: if a caret is inserted, don't try to find another. | |
| 72 | if( mAdded ) { | |
| 73 | return; | |
| 74 | } | |
| 75 | ||
| 76 | // If a table block has been earmarked with an empty node, it means | |
| 77 | // another extension has generated code from an external source. The | |
| 78 | // Markdown processor won't be able to determine the caret position | |
| 79 | // with any semblance of accuracy, so skip the element. This usually | |
| 80 | // happens with tables, but in theory any Markdown generated from an | |
| 81 | // external source (e.g., an R script) could produce text that has no | |
| 82 | // caret position that can be calculated. | |
| 83 | var table = curr; | |
| 84 | ||
| 85 | if( !(curr instanceof TableBlock) ) { | |
| 86 | table = curr.getAncestorOfType( TableBlock.class ); | |
| 87 | } | |
| 88 | ||
| 89 | // The table was generated outside the document | |
| 90 | if( table != null && table.getLastChild() == EMPTY_NODE ) { | |
| 91 | return; | |
| 92 | } | |
| 93 | ||
| 94 | final var outside = mCaret.isAfterText() ? 1 : 0; | |
| 95 | final var began = curr.getStartOffset(); | |
| 96 | final var ended = curr.getEndOffset() + outside; | |
| 97 | final var prev = curr.getPrevious(); | |
| 98 | ||
| 99 | // If the caret is within the bounds of the current node or the | |
| 100 | // caret is within the bounds of the end of the previous node and | |
| 101 | // the start of the current node, then mark the current node with | |
| 102 | // a caret indicator. | |
| 103 | if( mCaret.isBetweenText( began, ended ) || | |
| 104 | prev != null && mCaret.isBetweenText( prev.getEndOffset(), began ) ) { | |
| 105 | // This line empowers synchronizing the text editor with the preview. | |
| 106 | attributes.addValue( AttributeImpl.of( "id", CARET_ID ) ); | |
| 107 | ||
| 108 | // We're done until the user moves the caret (micro-optimization) | |
| 109 | mAdded = true; | |
| 110 | } | |
| 111 | } | |
| 112 | } | |
| 113 | } | |
| 1 | 114 |
| 1 | package com.keenwrite.processors.markdown.extensions; | |
| 2 | ||
| 3 | import com.keenwrite.processors.Processor; | |
| 4 | import com.vladsch.flexmark.ast.Heading; | |
| 5 | import com.vladsch.flexmark.parser.Parser.Builder; | |
| 6 | import com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 7 | import com.vladsch.flexmark.parser.block.NodePostProcessor; | |
| 8 | import com.vladsch.flexmark.parser.block.NodePostProcessorFactory; | |
| 9 | import com.vladsch.flexmark.util.ast.Document; | |
| 10 | import com.vladsch.flexmark.util.ast.Node; | |
| 11 | import com.vladsch.flexmark.util.ast.NodeTracker; | |
| 12 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 13 | import org.jetbrains.annotations.NotNull; | |
| 14 | ||
| 15 | import java.util.regex.Pattern; | |
| 16 | ||
| 17 | import static com.keenwrite.events.ParseHeadingEvent.fireNewHeadingEvent; | |
| 18 | import static com.keenwrite.events.ParseHeadingEvent.fireNewOutlineEvent; | |
| 19 | ||
| 20 | public final class DocumentOutlineExtension implements ParserExtension { | |
| 21 | private static final Pattern sRegex = Pattern.compile( "^(#+)" ); | |
| 22 | ||
| 23 | private final Processor<String> mProcessor; | |
| 24 | ||
| 25 | private DocumentOutlineExtension( final Processor<String> processor ) { | |
| 26 | mProcessor = processor; | |
| 27 | } | |
| 28 | ||
| 29 | @Override | |
| 30 | public void parserOptions( final MutableDataHolder options ) {} | |
| 31 | ||
| 32 | @Override | |
| 33 | public void extend( final Builder builder ) { | |
| 34 | builder.postProcessorFactory( new Factory() ); | |
| 35 | } | |
| 36 | ||
| 37 | public static DocumentOutlineExtension create( | |
| 38 | final Processor<String> processor ) { | |
| 39 | return new DocumentOutlineExtension( processor ); | |
| 40 | } | |
| 41 | ||
| 42 | private class HeadingNodePostProcessor extends NodePostProcessor { | |
| 43 | @Override | |
| 44 | public void process( | |
| 45 | @NotNull final NodeTracker state, @NotNull final Node node ) { | |
| 46 | final var heading = mProcessor.apply( node.getChars().toString() ); | |
| 47 | final var matcher = sRegex.matcher( heading ); | |
| 48 | ||
| 49 | if( matcher.find() ) { | |
| 50 | final var level = matcher.group().length(); | |
| 51 | final var text = heading.substring( level ); | |
| 52 | final var offset = node.getStartOffset(); | |
| 53 | fireNewHeadingEvent( level, text, offset ); | |
| 54 | } | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | public class Factory extends NodePostProcessorFactory { | |
| 59 | public Factory() { | |
| 60 | super( false ); | |
| 61 | addNodes( Heading.class ); | |
| 62 | } | |
| 63 | ||
| 64 | @NotNull | |
| 65 | @Override | |
| 66 | public NodePostProcessor apply( @NotNull final Document document ) { | |
| 67 | fireNewOutlineEvent(); | |
| 68 | return new HeadingNodePostProcessor(); | |
| 69 | } | |
| 70 | } | |
| 71 | } | |
| 1 | 72 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.util.ast.Node; | |
| 5 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 6 | import org.jetbrains.annotations.NotNull; | |
| 7 | ||
| 8 | /** | |
| 9 | * The singleton is injected into the abstract syntax tree to mark an instance | |
| 10 | * of {@link Node} such that it must not be processed normally. Using a wrapper | |
| 11 | * for a given {@link Node} cannot work because the class type is used by | |
| 12 | * the parsing library for processing. | |
| 13 | */ | |
| 14 | public final class EmptyNode extends Node { | |
| 15 | public static final Node EMPTY_NODE = new EmptyNode(); | |
| 16 | ||
| 17 | private static final BasedSequence[] BASE_SEQ = new BasedSequence[ 0 ]; | |
| 18 | ||
| 19 | private EmptyNode() { | |
| 20 | } | |
| 21 | ||
| 22 | @Override | |
| 23 | public @NotNull BasedSequence[] getSegments() { | |
| 24 | return BASE_SEQ; | |
| 25 | } | |
| 26 | } | |
| 1 | 27 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 5 | import org.jetbrains.annotations.NotNull; | |
| 6 | ||
| 7 | import static com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension; | |
| 8 | ||
| 9 | /** | |
| 10 | * Hides the {@link #rendererOptions(MutableDataHolder)} from subclasses | |
| 11 | * that would otherwise implement the {@link HtmlRendererExtension} interface. | |
| 12 | */ | |
| 13 | public abstract class HtmlRendererAdapter implements HtmlRendererExtension { | |
| 14 | /** | |
| 15 | * Empty, unused. | |
| 16 | * | |
| 17 | * @param options Ignored. | |
| 18 | */ | |
| 19 | @Override | |
| 20 | public void rendererOptions( @NotNull final MutableDataHolder options ) { | |
| 21 | } | |
| 22 | } | |
| 1 | 23 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions; | |
| 3 | ||
| 4 | import com.keenwrite.ExportFormat; | |
| 5 | import com.keenwrite.preferences.Workspace; | |
| 6 | import com.keenwrite.processors.ProcessorContext; | |
| 7 | import com.vladsch.flexmark.ast.Image; | |
| 8 | import com.vladsch.flexmark.html.IndependentLinkResolverFactory; | |
| 9 | import com.vladsch.flexmark.html.LinkResolver; | |
| 10 | import com.vladsch.flexmark.html.renderer.LinkResolverBasicContext; | |
| 11 | import com.vladsch.flexmark.html.renderer.ResolvedLink; | |
| 12 | import com.vladsch.flexmark.util.ast.Node; | |
| 13 | import org.jetbrains.annotations.NotNull; | |
| 14 | ||
| 15 | import java.io.File; | |
| 16 | import java.nio.file.Path; | |
| 17 | ||
| 18 | import static com.keenwrite.ExportFormat.NONE; | |
| 19 | import static com.keenwrite.events.StatusEvent.clue; | |
| 20 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_IMAGES_DIR; | |
| 21 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_IMAGES_ORDER; | |
| 22 | import static com.keenwrite.util.ProtocolScheme.getProtocol; | |
| 23 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 24 | import static com.vladsch.flexmark.html.renderer.LinkStatus.VALID; | |
| 25 | import static org.renjin.repackaged.guava.base.Splitter.on; | |
| 26 | ||
| 27 | /** | |
| 28 | * Responsible for ensuring that images can be rendered relative to a path. | |
| 29 | * This allows images to be located virtually anywhere. | |
| 30 | */ | |
| 31 | public class ImageLinkExtension extends HtmlRendererAdapter { | |
| 32 | ||
| 33 | private final Path mBaseDir; | |
| 34 | private final Workspace mWorkspace; | |
| 35 | private final ExportFormat mExportFormat; | |
| 36 | ||
| 37 | private ImageLinkExtension( | |
| 38 | @NotNull final ProcessorContext context ) { | |
| 39 | mBaseDir = context.getBaseDir(); | |
| 40 | mWorkspace = context.getWorkspace(); | |
| 41 | mExportFormat = context.getExportFormat(); | |
| 42 | } | |
| 43 | ||
| 44 | /** | |
| 45 | * Creates an extension capable of using a relative path to embed images. | |
| 46 | * | |
| 47 | * @param context Contains the base directory to search in for images. | |
| 48 | * @return The new {@link ImageLinkExtension}, not {@code null}. | |
| 49 | */ | |
| 50 | public static ImageLinkExtension create( | |
| 51 | @NotNull final ProcessorContext context ) { | |
| 52 | return new ImageLinkExtension( context ); | |
| 53 | } | |
| 54 | ||
| 55 | @Override | |
| 56 | public void extend( @NotNull final Builder builder, | |
| 57 | @NotNull final String rendererType ) { | |
| 58 | builder.linkResolverFactory( new ResolverFactory() ); | |
| 59 | } | |
| 60 | ||
| 61 | private final class ResolverFactory extends IndependentLinkResolverFactory { | |
| 62 | @Override | |
| 63 | public @NotNull LinkResolver apply( | |
| 64 | @NotNull final LinkResolverBasicContext context ) { | |
| 65 | return new ImageLinkResolver(); | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | private class ImageLinkResolver implements LinkResolver { | |
| 70 | public ImageLinkResolver() { | |
| 71 | } | |
| 72 | ||
| 73 | @NotNull | |
| 74 | @Override | |
| 75 | public ResolvedLink resolveLink( | |
| 76 | @NotNull final Node node, | |
| 77 | @NotNull final LinkResolverBasicContext context, | |
| 78 | @NotNull final ResolvedLink link ) { | |
| 79 | return node instanceof Image ? forImage( link, node ) : link; | |
| 80 | } | |
| 81 | ||
| 82 | /** | |
| 83 | * Algorithm: | |
| 84 | * <ol> | |
| 85 | * <li>Accept remote URLs as valid links.</li> | |
| 86 | * <li>Accept existing readable files as valid links.</li> | |
| 87 | * <li>Accept non-{@link ExportFormat#NONE} exports as valid links.</li> | |
| 88 | * <li>Append the images dir to the edited file's dir (baseDir).</li> | |
| 89 | * <li>Search for images by extension.</li> | |
| 90 | * </ol> | |
| 91 | * | |
| 92 | * @param link The link URL to resolve. | |
| 93 | * @param node The document node containing the URL. | |
| 94 | * @return The {@link ResolvedLink} instance used to render the link. | |
| 95 | */ | |
| 96 | private ResolvedLink forImage( final ResolvedLink link, final Node node ) { | |
| 97 | var uri = link.getUrl(); | |
| 98 | final var protocol = getProtocol( uri ); | |
| 99 | ||
| 100 | if( protocol.isRemote() ) { | |
| 101 | return valid( link, uri ); | |
| 102 | } | |
| 103 | ||
| 104 | final var baseDir = getBaseDir(); | |
| 105 | ||
| 106 | // Determine the fully-qualified file name (fqfn). | |
| 107 | final var fqfn = Path.of( baseDir.toString(), uri ).toFile(); | |
| 108 | ||
| 109 | if( fqfn.isFile() && fqfn.canRead() ) { | |
| 110 | return valid( link, uri ); | |
| 111 | } | |
| 112 | ||
| 113 | if( mExportFormat != NONE ) { | |
| 114 | return valid( link, uri ); | |
| 115 | } | |
| 116 | ||
| 117 | try { | |
| 118 | // Compute the path to the image file. The base directory should | |
| 119 | // be an absolute path to the file being edited, without an extension. | |
| 120 | final var imagesDir = getUserImagesDir(); | |
| 121 | final var relativeDir = imagesDir.toString().isEmpty() | |
| 122 | ? imagesDir : baseDir.relativize( imagesDir ); | |
| 123 | final var imageFile = Path.of( | |
| 124 | baseDir.toString(), relativeDir.toString(), uri ); | |
| 125 | ||
| 126 | for( final var ext : getImageExtensions() ) { | |
| 127 | var file = new File( imageFile.toString() + '.' + ext ); | |
| 128 | ||
| 129 | if( file.exists() && file.canRead() ) { | |
| 130 | uri = file.toURI().toString(); | |
| 131 | return valid( link, uri ); | |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | clue( "Main.status.error.file.missing.near", | |
| 136 | imageFile + ".*", node.getLineNumber() | |
| 137 | ); | |
| 138 | } catch( final Exception ex ) { | |
| 139 | clue( ex ); | |
| 140 | } | |
| 141 | ||
| 142 | return link; | |
| 143 | } | |
| 144 | ||
| 145 | private ResolvedLink valid( final ResolvedLink link, final String url ) { | |
| 146 | return link.withStatus( VALID ).withUrl( url ); | |
| 147 | } | |
| 148 | ||
| 149 | private Path getUserImagesDir() { | |
| 150 | return mWorkspace.toFile( KEY_IMAGES_DIR ).toPath(); | |
| 151 | } | |
| 152 | ||
| 153 | private Iterable<String> getImageExtensions() { | |
| 154 | return on( ' ' ).split( mWorkspace.toString( KEY_IMAGES_ORDER ) ); | |
| 155 | } | |
| 156 | ||
| 157 | private Path getBaseDir() { | |
| 158 | return mBaseDir; | |
| 159 | } | |
| 160 | } | |
| 161 | } | |
| 1 | 162 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.fences; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 5 | ||
| 6 | /** | |
| 7 | * Responsible for helping to generate a closing div element. | |
| 8 | */ | |
| 9 | class ClosingDivBlock extends DivBlock { | |
| 10 | @Override | |
| 11 | void export( final HtmlWriter html ) { | |
| 12 | html.closeTag( HTML_DIV ); | |
| 13 | } | |
| 14 | } | |
| 1 | 15 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.fences; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 5 | import com.vladsch.flexmark.util.ast.Block; | |
| 6 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 7 | import org.jetbrains.annotations.NotNull; | |
| 8 | ||
| 9 | abstract class DivBlock extends Block { | |
| 10 | static final CharSequence HTML_DIV = "div"; | |
| 11 | ||
| 12 | @Override | |
| 13 | @NotNull | |
| 14 | public BasedSequence[] getSegments() { | |
| 15 | return EMPTY_SEGMENTS; | |
| 16 | } | |
| 17 | ||
| 18 | /** | |
| 19 | * Append an opening or closing HTML div element to the given writer. | |
| 20 | * | |
| 21 | * @param html Builds the HTML document to be written. | |
| 22 | */ | |
| 23 | abstract void export( HtmlWriter html ); | |
| 24 | } | |
| 1 | 25 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.fences; | |
| 3 | ||
| 4 | import com.keenwrite.preferences.Workspace; | |
| 5 | import com.keenwrite.processors.DefinitionProcessor; | |
| 6 | import com.keenwrite.processors.Processor; | |
| 7 | import com.keenwrite.processors.ProcessorContext; | |
| 8 | import com.keenwrite.processors.markdown.MarkdownProcessor; | |
| 9 | import com.keenwrite.processors.markdown.extensions.HtmlRendererAdapter; | |
| 10 | import com.vladsch.flexmark.ast.FencedCodeBlock; | |
| 11 | import com.vladsch.flexmark.html.renderer.DelegatingNodeRendererFactory; | |
| 12 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 13 | import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; | |
| 14 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 15 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 16 | import org.jetbrains.annotations.NotNull; | |
| 17 | ||
| 18 | import java.io.ByteArrayOutputStream; | |
| 19 | import java.util.HashSet; | |
| 20 | import java.util.Set; | |
| 21 | import java.util.zip.Deflater; | |
| 22 | ||
| 23 | import static com.keenwrite.events.StatusEvent.clue; | |
| 24 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_IMAGES_SERVER; | |
| 25 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 26 | import static com.vladsch.flexmark.html.renderer.LinkType.LINK; | |
| 27 | import static java.lang.String.format; | |
| 28 | import static java.util.Base64.getUrlEncoder; | |
| 29 | import static java.util.zip.Deflater.BEST_COMPRESSION; | |
| 30 | import static java.util.zip.Deflater.FULL_FLUSH; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for converting textual diagram descriptions into HTML image | |
| 34 | * elements. | |
| 35 | */ | |
| 36 | public class FencedBlockExtension extends HtmlRendererAdapter { | |
| 37 | private final static String DIAGRAM_STYLE = "diagram-"; | |
| 38 | private final static int DIAGRAM_STYLE_LEN = DIAGRAM_STYLE.length(); | |
| 39 | ||
| 40 | private final Processor<String> mProcessor; | |
| 41 | private final ProcessorContext mContext; | |
| 42 | ||
| 43 | public FencedBlockExtension( | |
| 44 | final Processor<String> processor, final ProcessorContext context ) { | |
| 45 | assert processor != null; | |
| 46 | assert context != null; | |
| 47 | mProcessor = processor; | |
| 48 | mContext = context; | |
| 49 | } | |
| 50 | ||
| 51 | /** | |
| 52 | * Creates a new parser for fenced blocks. This calls out to a web service | |
| 53 | * to generate SVG files of text diagrams. | |
| 54 | * <p> | |
| 55 | * Internally, this creates a {@link DefinitionProcessor} to substitute | |
| 56 | * variable definitions. This is necessary because the order of processors | |
| 57 | * matters. If the {@link DefinitionProcessor} comes before an instance of | |
| 58 | * {@link MarkdownProcessor}, for example, then the caret position in the | |
| 59 | * preview pane will not align with the caret position in the editor | |
| 60 | * pane. The {@link MarkdownProcessor} must come before all else. However, | |
| 61 | * when parsing fenced blocks, the variables within the block must be | |
| 62 | * interpolated before being sent to the diagram web service. | |
| 63 | * </p> | |
| 64 | * | |
| 65 | * @param processor Used to pre-process the text. | |
| 66 | * @return A new {@link FencedBlockExtension} capable of shunting ASCII | |
| 67 | * diagrams to a service for conversion to SVG. | |
| 68 | */ | |
| 69 | public static FencedBlockExtension create( | |
| 70 | final Processor<String> processor, final ProcessorContext context ) { | |
| 71 | return new FencedBlockExtension( processor, context ); | |
| 72 | } | |
| 73 | ||
| 74 | @Override | |
| 75 | public void extend( | |
| 76 | @NotNull final Builder builder, @NotNull final String rendererType ) { | |
| 77 | builder.nodeRendererFactory( new Factory() ); | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Converts the given {@link BasedSequence} to a lowercase value. | |
| 82 | * | |
| 83 | * @param text The character string to convert to lowercase. | |
| 84 | * @return The lowercase text value, or the empty string for no text. | |
| 85 | */ | |
| 86 | private static String sanitize( final BasedSequence text ) { | |
| 87 | assert text != null; | |
| 88 | return text.toString().toLowerCase(); | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Responsible for generating images from a fenced block that contains a | |
| 93 | * diagram reference. | |
| 94 | */ | |
| 95 | private class CustomRenderer implements NodeRenderer { | |
| 96 | ||
| 97 | @Override | |
| 98 | public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 99 | final var set = new HashSet<NodeRenderingHandler<?>>(); | |
| 100 | ||
| 101 | set.add( new NodeRenderingHandler<>( | |
| 102 | FencedCodeBlock.class, ( node, context, html ) -> { | |
| 103 | final var style = sanitize( node.getInfo() ); | |
| 104 | ||
| 105 | if( style.startsWith( DIAGRAM_STYLE ) ) { | |
| 106 | final var type = style.substring( DIAGRAM_STYLE_LEN ); | |
| 107 | final var content = node.getContentChars().normalizeEOL(); | |
| 108 | final var text = mProcessor.apply( content ); | |
| 109 | final var encoded = encode( text ); | |
| 110 | final var source = getSourceUrl( type, encoded ); | |
| 111 | final var link = context.resolveLink( LINK, source, false ); | |
| 112 | ||
| 113 | html.attr( "src", source ); | |
| 114 | html.withAttr( link ); | |
| 115 | html.tagVoid( "img" ); | |
| 116 | } | |
| 117 | else { | |
| 118 | context.delegateRender(); | |
| 119 | } | |
| 120 | } ) ); | |
| 121 | ||
| 122 | return set; | |
| 123 | } | |
| 124 | ||
| 125 | private byte[] compress( byte[] source ) { | |
| 126 | final var inLen = source.length; | |
| 127 | final var result = new byte[ inLen ]; | |
| 128 | final var compressor = new Deflater( BEST_COMPRESSION ); | |
| 129 | ||
| 130 | compressor.setInput( source, 0, inLen ); | |
| 131 | compressor.finish(); | |
| 132 | final var outLen = compressor.deflate( result, 0, inLen, FULL_FLUSH ); | |
| 133 | compressor.end(); | |
| 134 | ||
| 135 | try( final var out = new ByteArrayOutputStream() ) { | |
| 136 | out.write( result, 0, outLen ); | |
| 137 | return out.toByteArray(); | |
| 138 | } catch( final Exception ex ) { | |
| 139 | clue( ex ); | |
| 140 | throw new RuntimeException( ex ); | |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | private String encode( final String decoded ) { | |
| 145 | return getUrlEncoder().encodeToString( compress( decoded.getBytes() ) ); | |
| 146 | } | |
| 147 | ||
| 148 | private String getSourceUrl( final String type, final String encoded ) { | |
| 149 | return | |
| 150 | format( "https://%s/%s/svg/%s", getDiagramServerName(), type, encoded ); | |
| 151 | } | |
| 152 | ||
| 153 | private Workspace getWorkspace() { | |
| 154 | return mContext.getWorkspace(); | |
| 155 | } | |
| 156 | ||
| 157 | private String getDiagramServerName() { | |
| 158 | return getWorkspace().toString( KEY_IMAGES_SERVER ); | |
| 159 | } | |
| 160 | } | |
| 161 | ||
| 162 | private class Factory implements DelegatingNodeRendererFactory { | |
| 163 | public Factory() {} | |
| 164 | ||
| 165 | @NotNull | |
| 166 | @Override | |
| 167 | public NodeRenderer apply( @NotNull final DataHolder options ) { | |
| 168 | return new CustomRenderer(); | |
| 169 | } | |
| 170 | ||
| 171 | /** | |
| 172 | * Return {@code null} to indicate this may delegate to the core renderer. | |
| 173 | */ | |
| 174 | @Override | |
| 175 | public Set<Class<?>> getDelegates() { | |
| 176 | return null; | |
| 177 | } | |
| 178 | } | |
| 179 | } | |
| 1 | 180 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.fences; | |
| 3 | ||
| 4 | import com.keenwrite.processors.markdown.extensions.HtmlRendererAdapter; | |
| 5 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 6 | import com.vladsch.flexmark.parser.Parser; | |
| 7 | import com.vladsch.flexmark.parser.block.*; | |
| 8 | import com.vladsch.flexmark.util.ast.Block; | |
| 9 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 10 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 11 | import com.vladsch.flexmark.util.html.Attribute; | |
| 12 | import com.vladsch.flexmark.util.html.AttributeImpl; | |
| 13 | import org.jetbrains.annotations.NotNull; | |
| 14 | import org.jetbrains.annotations.Nullable; | |
| 15 | ||
| 16 | import java.util.ArrayList; | |
| 17 | import java.util.Set; | |
| 18 | import java.util.regex.Pattern; | |
| 19 | ||
| 20 | import static com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 21 | ||
| 22 | /** | |
| 23 | * Responsible for parsing div block syntax into HTML div tags. Fenced div | |
| 24 | * blocks start with three or more consecutive colons, followed by a space, | |
| 25 | * followed by attributes. The attributes can be either a single word, or | |
| 26 | * multiple words nested in braces. For example: | |
| 27 | * | |
| 28 | * <p> | |
| 29 | * ::: poem | |
| 30 | * Tyger Tyger, burning bright, | |
| 31 | * In the forests of the night; | |
| 32 | * What immortal hand or eye, | |
| 33 | * Could frame thy fearful symmetry? | |
| 34 | * ::: | |
| 35 | * </p> | |
| 36 | * <p> | |
| 37 | * As well as: | |
| 38 | * </p> | |
| 39 | * <p> | |
| 40 | * ::: {#verse .p .d k=v author="Emily Dickinson"} | |
| 41 | * Because I could not stop for Death -- | |
| 42 | * He kindly stopped for me -- | |
| 43 | * The Carriage held but just Ourselves -- | |
| 44 | * And Immortality. | |
| 45 | * ::: | |
| 46 | * </p> | |
| 47 | * | |
| 48 | * <p> | |
| 49 | * The second example produces the following starting {@code div} element: | |
| 50 | * </p> | |
| 51 | * <p> | |
| 52 | * <div id="verse" class="p d" data-k="v" data-author="Emily Dickson"> | |
| 53 | * </p> | |
| 54 | */ | |
| 55 | public class FencedDivExtension extends HtmlRendererAdapter | |
| 56 | implements ParserExtension { | |
| 57 | /** | |
| 58 | * Matches any number of colons at start of line. This will match both the | |
| 59 | * opening and closing fences, with any number of colons. | |
| 60 | */ | |
| 61 | private static final Pattern FENCE = Pattern.compile( "^:::.*" ); | |
| 62 | ||
| 63 | /** | |
| 64 | * After a fenced div is detected, this will match the opening fence. | |
| 65 | */ | |
| 66 | private static final Pattern FENCE_OPENING = Pattern.compile( | |
| 67 | "^:::+\\s+([\\p{IsAlphabetic}\\p{IsDigit}-_]+|\\{.+})\\s*$" ); | |
| 68 | ||
| 69 | /** | |
| 70 | * Matches whether extended syntax is being used. | |
| 71 | */ | |
| 72 | private static final Pattern ATTR_CSS = Pattern.compile( "\\{(.+)}" ); | |
| 73 | ||
| 74 | /** | |
| 75 | * Matches either individual CSS definitions (id/class, {@code <d>}) or | |
| 76 | * key/value pairs ({@code <k>} and {@link <v>}). The key/value pair | |
| 77 | * will match optional quotes. | |
| 78 | */ | |
| 79 | private static final Pattern ATTR_PAIRS = Pattern.compile( | |
| 80 | "\\s*" + | |
| 81 | "(?<d>[#.][\\p{IsAlphabetic}\\p{IsDigit}-_]+[^\\s=])|" + | |
| 82 | "((?<k>[\\p{IsAlphabetic}\\p{IsDigit}-_]+)=" + | |
| 83 | "\"*(?<v>(?<=\")[^\"]+(?=\")|([^\\s]+))\"*)" ); | |
| 84 | ||
| 85 | public static FencedDivExtension create() { | |
| 86 | return new FencedDivExtension(); | |
| 87 | } | |
| 88 | ||
| 89 | @Override | |
| 90 | public void parserOptions( final MutableDataHolder options ) { | |
| 91 | } | |
| 92 | ||
| 93 | @Override | |
| 94 | public void extend( final Parser.Builder builder ) { | |
| 95 | builder.customBlockParserFactory( new Factory() ); | |
| 96 | } | |
| 97 | ||
| 98 | /** | |
| 99 | * Creates a renderer that can generate HTML div elements. | |
| 100 | * | |
| 101 | * @param builder The document builder. | |
| 102 | * @param rendererType Indicates the document type to be built. | |
| 103 | */ | |
| 104 | @Override | |
| 105 | public void extend( @NotNull final HtmlRenderer.Builder builder, | |
| 106 | @NotNull final String rendererType ) { | |
| 107 | if( "HTML".equalsIgnoreCase( rendererType ) ) { | |
| 108 | builder.nodeRendererFactory( new FencedDivRenderer.Factory() ); | |
| 109 | } | |
| 110 | } | |
| 111 | ||
| 112 | /** | |
| 113 | * Responsible for creating an instance of {@link ParserFactory}. | |
| 114 | */ | |
| 115 | private static class Factory implements CustomBlockParserFactory { | |
| 116 | @Override | |
| 117 | public @NotNull BlockParserFactory apply( | |
| 118 | @NotNull final DataHolder options ) { | |
| 119 | return new ParserFactory( options ); | |
| 120 | } | |
| 121 | ||
| 122 | @Override | |
| 123 | public @Nullable Set<Class<?>> getAfterDependents() { return null; } | |
| 124 | ||
| 125 | @Override | |
| 126 | public @Nullable Set<Class<?>> getBeforeDependents() { return null; } | |
| 127 | ||
| 128 | @Override | |
| 129 | public boolean affectsGlobalScope() { return false; } | |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Responsible for creating a fenced div parser that is appropriate for the | |
| 134 | * type of fenced div encountered: opening or closing. | |
| 135 | */ | |
| 136 | private static class ParserFactory extends AbstractBlockParserFactory { | |
| 137 | public ParserFactory( final DataHolder options ) { | |
| 138 | super( options ); | |
| 139 | } | |
| 140 | ||
| 141 | /** | |
| 142 | * Try to match an opening or closing fenced div. | |
| 143 | * | |
| 144 | * @param state Block parser state. | |
| 145 | * @param matchedBlockParser Last matched open block parser. | |
| 146 | * @return Wrapper for the opening or closing parser, upon finding :::. | |
| 147 | */ | |
| 148 | @Override | |
| 149 | public BlockStart tryStart( | |
| 150 | final ParserState state, final MatchedBlockParser matchedBlockParser ) { | |
| 151 | return | |
| 152 | state.getIndent() == 0 && FENCE.matcher( state.getLine() ).matches() | |
| 153 | ? parseFence( state ) | |
| 154 | : BlockStart.none(); | |
| 155 | } | |
| 156 | ||
| 157 | /** | |
| 158 | * After finding a fenced div, this will further disambiguate an opening | |
| 159 | * from a closing fence. | |
| 160 | * | |
| 161 | * @param state Block parser state, contains line to parse. | |
| 162 | * @return Wrapper for the opening or closing parser, upon finding :::. | |
| 163 | */ | |
| 164 | private BlockStart parseFence( final ParserState state ) { | |
| 165 | final var fence = FENCE_OPENING.matcher( state.getLine() ); | |
| 166 | ||
| 167 | return BlockStart.of( | |
| 168 | fence.matches() | |
| 169 | ? new OpeningParser( fence.group( 1 ) ) | |
| 170 | : new ClosingParser() | |
| 171 | ).atIndex( state.getIndex() ); | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * Abstracts common {@link OpeningParser} and {@link ClosingParser} methods. | |
| 177 | */ | |
| 178 | private static abstract class DivBlockParser extends AbstractBlockParser { | |
| 179 | @Override | |
| 180 | public BlockContinue tryContinue( final ParserState state ) { | |
| 181 | return BlockContinue.none(); | |
| 182 | } | |
| 183 | ||
| 184 | @Override | |
| 185 | public void closeBlock( final ParserState state ) {} | |
| 186 | } | |
| 187 | ||
| 188 | /** | |
| 189 | * Responsible for creating an instance of {@link OpeningDivBlock}. | |
| 190 | */ | |
| 191 | private static class OpeningParser extends DivBlockParser { | |
| 192 | private final OpeningDivBlock mBlock; | |
| 193 | ||
| 194 | /** | |
| 195 | * Parses the arguments upon construction. | |
| 196 | * | |
| 197 | * @param args Text after :::, excluding leading/trailing whitespace. | |
| 198 | */ | |
| 199 | public OpeningParser( final String args ) { | |
| 200 | final var attrs = new ArrayList<Attribute>(); | |
| 201 | final var cssMatcher = ATTR_CSS.matcher( args ); | |
| 202 | ||
| 203 | if( cssMatcher.matches() ) { | |
| 204 | // Split the text between braces into tokens and/or key-value pairs. | |
| 205 | final var pairMatcher = ATTR_PAIRS.matcher( cssMatcher.group( 1 ) ); | |
| 206 | ||
| 207 | while( pairMatcher.find() ) { | |
| 208 | final var cssDef = pairMatcher.group( "d" ); | |
| 209 | String cssAttrKey = "class"; | |
| 210 | String cssAttrVal; | |
| 211 | ||
| 212 | // When no regular CSS definition (id or class), use key/value pairs. | |
| 213 | if( cssDef == null ) { | |
| 214 | cssAttrKey = "data-" + pairMatcher.group( "k" ); | |
| 215 | cssAttrVal = pairMatcher.group( "v" ); | |
| 216 | } | |
| 217 | else { | |
| 218 | // This will strip the "#" and "." off the start of CSS definition. | |
| 219 | var index = 1; | |
| 220 | ||
| 221 | // Default CSS attribute name is "class", switch to "id" for #. | |
| 222 | if( cssDef.startsWith( "#" ) ) { | |
| 223 | cssAttrKey = "id"; | |
| 224 | } | |
| 225 | else if( !cssDef.startsWith( "." ) ) { | |
| 226 | index = 0; | |
| 227 | } | |
| 228 | ||
| 229 | cssAttrVal = cssDef.substring( index ); | |
| 230 | } | |
| 231 | ||
| 232 | attrs.add( AttributeImpl.of( cssAttrKey, cssAttrVal ) ); | |
| 233 | } | |
| 234 | } | |
| 235 | else { | |
| 236 | attrs.add( AttributeImpl.of( "class", args ) ); | |
| 237 | } | |
| 238 | ||
| 239 | mBlock = new OpeningDivBlock( attrs ); | |
| 240 | } | |
| 241 | ||
| 242 | @Override | |
| 243 | public Block getBlock() { | |
| 244 | return mBlock; | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | /** | |
| 249 | * Responsible for creating an instance of {@link ClosingDivBlock}. | |
| 250 | */ | |
| 251 | private static class ClosingParser extends DivBlockParser { | |
| 252 | private final ClosingDivBlock mBlock = new ClosingDivBlock(); | |
| 253 | ||
| 254 | @Override | |
| 255 | public Block getBlock() { | |
| 256 | return mBlock; | |
| 257 | } | |
| 258 | } | |
| 259 | } | |
| 1 | 260 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.fences; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 5 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 6 | import com.vladsch.flexmark.html.renderer.NodeRendererContext; | |
| 7 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; | |
| 9 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 10 | import org.jetbrains.annotations.NotNull; | |
| 11 | import org.jetbrains.annotations.Nullable; | |
| 12 | ||
| 13 | import java.util.Set; | |
| 14 | ||
| 15 | /** | |
| 16 | * Responsible for rendering opening and closing fenced div blocks as HTMl | |
| 17 | * div elements. | |
| 18 | */ | |
| 19 | class FencedDivRenderer implements NodeRenderer { | |
| 20 | @Override | |
| 21 | public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 22 | return Set.of( | |
| 23 | new NodeRenderingHandler<>( OpeningDivBlock.class, this::render ), | |
| 24 | new NodeRenderingHandler<>( ClosingDivBlock.class, this::render ) | |
| 25 | ); | |
| 26 | } | |
| 27 | ||
| 28 | /** | |
| 29 | * Renders the opening fenced div block as an HTML {@code <div>} element. | |
| 30 | */ | |
| 31 | void render( final OpeningDivBlock node, | |
| 32 | final NodeRendererContext context, | |
| 33 | final HtmlWriter html ) { | |
| 34 | node.export( html ); | |
| 35 | } | |
| 36 | ||
| 37 | /** | |
| 38 | * Renders the closing fenced div block as an HTML {@code </div>} element. | |
| 39 | */ | |
| 40 | void render( final ClosingDivBlock node, | |
| 41 | final NodeRendererContext context, | |
| 42 | final HtmlWriter html ) { | |
| 43 | node.export( html ); | |
| 44 | } | |
| 45 | ||
| 46 | static class Factory implements @NotNull NodeRendererFactory { | |
| 47 | @Override | |
| 48 | public @NotNull NodeRenderer apply( @NotNull final DataHolder options ) { | |
| 49 | return new FencedDivRenderer(); | |
| 50 | } | |
| 51 | } | |
| 52 | } | |
| 1 | 53 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.fences; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 5 | import com.vladsch.flexmark.util.html.Attribute; | |
| 6 | ||
| 7 | import java.util.ArrayList; | |
| 8 | import java.util.List; | |
| 9 | ||
| 10 | /** | |
| 11 | * Responsible for helping to generate an opening div element. | |
| 12 | */ | |
| 13 | class OpeningDivBlock extends DivBlock { | |
| 14 | private final List<Attribute> mAttributes = new ArrayList<>(); | |
| 15 | ||
| 16 | OpeningDivBlock( final List<Attribute> attributes ) { | |
| 17 | assert attributes != null; | |
| 18 | mAttributes.addAll( attributes ); | |
| 19 | } | |
| 20 | ||
| 21 | void export( final HtmlWriter html ) { | |
| 22 | mAttributes.forEach( html::attr ); | |
| 23 | html.withAttr().tag( HTML_DIV ); | |
| 24 | } | |
| 25 | } | |
| 1 | 26 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.r; | |
| 3 | ||
| 4 | import com.keenwrite.processors.Processor; | |
| 5 | import com.keenwrite.processors.ProcessorContext; | |
| 6 | import com.keenwrite.processors.markdown.BaseMarkdownProcessor; | |
| 7 | import com.keenwrite.processors.r.InlineRProcessor; | |
| 8 | import com.keenwrite.processors.r.RProcessor; | |
| 9 | import com.keenwrite.sigils.RSigilOperator; | |
| 10 | import com.vladsch.flexmark.ast.Paragraph; | |
| 11 | import com.vladsch.flexmark.ast.Text; | |
| 12 | import com.vladsch.flexmark.parser.InlineParserExtensionFactory; | |
| 13 | import com.vladsch.flexmark.parser.InlineParserFactory; | |
| 14 | import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor; | |
| 15 | import com.vladsch.flexmark.parser.internal.InlineParserImpl; | |
| 16 | import com.vladsch.flexmark.parser.internal.LinkRefProcessorData; | |
| 17 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 18 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 19 | ||
| 20 | import java.util.BitSet; | |
| 21 | import java.util.List; | |
| 22 | import java.util.Map; | |
| 23 | ||
| 24 | import static com.keenwrite.processors.IdentityProcessor.IDENTITY; | |
| 25 | import static com.keenwrite.processors.markdown.extensions.EmptyNode.EMPTY_NODE; | |
| 26 | import static com.vladsch.flexmark.parser.Parser.Builder; | |
| 27 | import static com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 28 | ||
| 29 | /** | |
| 30 | * Responsible for processing inline R statements (denoted using the | |
| 31 | * {@link RSigilOperator#PREFIX}) to prevent them from being converted to | |
| 32 | * HTML {@code <code>} elements and stop them from interfering with TeX | |
| 33 | * statements. Note that TeX statements are processed using a Markdown | |
| 34 | * extension, rather than an implementation of {@link Processor}. For this | |
| 35 | * reason, some pre-conversion is necessary. | |
| 36 | */ | |
| 37 | public final class RExtension implements ParserExtension { | |
| 38 | private final InlineParserFactory INLINE_FACTORY = InlineParser::new; | |
| 39 | private final RProcessor mProcessor; | |
| 40 | private final BaseMarkdownProcessor mMarkdownProcessor; | |
| 41 | ||
| 42 | private RExtension( | |
| 43 | final RProcessor processor, final ProcessorContext context ) { | |
| 44 | mProcessor = processor; | |
| 45 | mMarkdownProcessor = new BaseMarkdownProcessor( IDENTITY, context ); | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * Creates an extension capable of intercepting R code blocks and preventing | |
| 50 | * them from being converted into HTML {@code <code>} elements. | |
| 51 | */ | |
| 52 | public static RExtension create( | |
| 53 | final RProcessor processor, final ProcessorContext context ) { | |
| 54 | return new RExtension( processor, context ); | |
| 55 | } | |
| 56 | ||
| 57 | @Override | |
| 58 | public void extend( final Builder builder ) { | |
| 59 | builder.customInlineParserFactory( INLINE_FACTORY ); | |
| 60 | } | |
| 61 | ||
| 62 | @Override | |
| 63 | public void parserOptions( final MutableDataHolder options ) { | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Prevents rendering {@code `r} statements as inline HTML {@code <code>} | |
| 68 | * blocks, which allows the {@link InlineRProcessor} to post-process the | |
| 69 | * text prior to display in the preview pane. This intervention assists | |
| 70 | * with decoupling the caret from the Markdown content so that the two | |
| 71 | * can vary independently in the architecture while permitting synchronization | |
| 72 | * of the editor and preview pane. | |
| 73 | * <p> | |
| 74 | * The text is therefore processed twice: once by flexmark-java and once by | |
| 75 | * {@link InlineRProcessor}. | |
| 76 | * </p> | |
| 77 | */ | |
| 78 | private class InlineParser extends InlineParserImpl { | |
| 79 | private InlineParser( | |
| 80 | final DataHolder options, | |
| 81 | final BitSet specialCharacters, | |
| 82 | final BitSet delimiterCharacters, | |
| 83 | final Map<Character, DelimiterProcessor> delimiterProcessors, | |
| 84 | final LinkRefProcessorData referenceLinkProcessors, | |
| 85 | final List<InlineParserExtensionFactory> inlineParserExtensions ) { | |
| 86 | super( options, | |
| 87 | specialCharacters, | |
| 88 | delimiterCharacters, | |
| 89 | delimiterProcessors, | |
| 90 | referenceLinkProcessors, | |
| 91 | inlineParserExtensions ); | |
| 92 | mProcessor.init(); | |
| 93 | } | |
| 94 | ||
| 95 | /** | |
| 96 | * The superclass handles a number backtick parsing edge cases; this method | |
| 97 | * changes the behaviour to retain R code snippets, identified by | |
| 98 | * {@link RSigilOperator#PREFIX}, so that subsequent processing can | |
| 99 | * invoke R. If other languages are added, the {@link InlineParser} will | |
| 100 | * have to be rewritten to identify more than merely R. | |
| 101 | * | |
| 102 | * @return The return value from {@link super#parseBackticks()}. | |
| 103 | * @inheritDoc | |
| 104 | */ | |
| 105 | @Override | |
| 106 | protected final boolean parseBackticks() { | |
| 107 | final var foundTicks = super.parseBackticks(); | |
| 108 | ||
| 109 | if( foundTicks && mProcessor.isReady() ) { | |
| 110 | final var blockNode = getBlock(); | |
| 111 | final var codeNode = blockNode.getLastChild(); | |
| 112 | ||
| 113 | if( codeNode != null ) { | |
| 114 | final var code = codeNode.getChars().toString(); | |
| 115 | ||
| 116 | if( code.startsWith( RSigilOperator.PREFIX ) ) { | |
| 117 | codeNode.unlink(); | |
| 118 | final var rText = mProcessor.apply( code ); | |
| 119 | var node = mMarkdownProcessor.toNode( rText ); | |
| 120 | ||
| 121 | if( node.getFirstChild() instanceof Paragraph ) { | |
| 122 | node = new Text( rText ); | |
| 123 | } | |
| 124 | else { | |
| 125 | node = node.getFirstChild(); | |
| 126 | ||
| 127 | if( node != null ) { | |
| 128 | // Mark the node as being generated code, such as text returned | |
| 129 | // from an R function. | |
| 130 | node.appendChild( EMPTY_NODE ); | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | if( node != null ) { | |
| 135 | blockNode.appendChild( node ); | |
| 136 | } | |
| 137 | } | |
| 138 | } | |
| 139 | } | |
| 140 | ||
| 141 | return foundTicks; | |
| 142 | } | |
| 143 | } | |
| 144 | } | |
| 1 | 145 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.r; | |
| 3 | ||
| 4 | import com.keenwrite.processors.ExecutorProcessor; | |
| 5 | import com.keenwrite.processors.r.InlineRProcessor; | |
| 6 | import com.keenwrite.processors.markdown.MarkdownProcessor; | |
| 7 | import com.keenwrite.processors.markdown.extensions.tex.TeXExtension; | |
| 8 | import com.vladsch.flexmark.ast.Paragraph; | |
| 9 | import com.vladsch.flexmark.ast.Text; | |
| 10 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 11 | import com.vladsch.flexmark.parser.Parser; | |
| 12 | import com.vladsch.flexmark.util.ast.IParse; | |
| 13 | import com.vladsch.flexmark.util.ast.IRender; | |
| 14 | ||
| 15 | /** | |
| 16 | * Responsible for parsing the output from an R eval statement. This class | |
| 17 | * is used to avoid an circular dependency whereby the {@link InlineRProcessor} | |
| 18 | * must treat the output from an R function call as Markdown, which would | |
| 19 | * otherwise require a {@link MarkdownProcessor} instance; however, the | |
| 20 | * {@link MarkdownProcessor} class gives precedence to its extensions, which | |
| 21 | * means the {@link TeXExtension} will be executed <em>before</em> the | |
| 22 | * {@link InlineRProcessor}, thereby being exposed to backticks in a TeX | |
| 23 | * macro---a syntax error. To break the cycle, the {@link InlineRProcessor} | |
| 24 | * uses this class instead of {@link MarkdownProcessor}. | |
| 25 | */ | |
| 26 | public class ROutputProcessor extends ExecutorProcessor<String> { | |
| 27 | private final IParse mParser = Parser.builder().build(); | |
| 28 | private final IRender mRenderer = HtmlRenderer.builder().build(); | |
| 29 | ||
| 30 | @Override | |
| 31 | public String apply( final String markdown ) { | |
| 32 | var node = mParser.parse( markdown ).getFirstChild(); | |
| 33 | ||
| 34 | if( node == null ) { | |
| 35 | node = new Text(); | |
| 36 | } | |
| 37 | else if( node.isOrDescendantOfType( Paragraph.class ) ) { | |
| 38 | node = new Text( node.getChars() ); | |
| 39 | } | |
| 40 | ||
| 41 | // Trimming prevents displaced commas and unwanted newlines. | |
| 42 | return mRenderer.render( node ).trim(); | |
| 43 | } | |
| 44 | } | |
| 1 | 45 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.tex; | |
| 3 | ||
| 4 | import com.keenwrite.ExportFormat; | |
| 5 | import com.keenwrite.processors.Processor; | |
| 6 | import com.keenwrite.processors.ProcessorContext; | |
| 7 | import com.keenwrite.processors.markdown.extensions.HtmlRendererAdapter; | |
| 8 | import com.keenwrite.processors.markdown.extensions.tex.TexNodeRenderer.Factory; | |
| 9 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 10 | import com.vladsch.flexmark.parser.Parser; | |
| 11 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 12 | import org.jetbrains.annotations.NotNull; | |
| 13 | ||
| 14 | import static com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 15 | ||
| 16 | /** | |
| 17 | * Responsible for wrapping delimited TeX code in Markdown into an XML element | |
| 18 | * that the HTML renderer can handle. For example, {@code $E=mc^2$} becomes | |
| 19 | * {@code <tex>E=mc^2</tex>} when passed to HTML renderer. The HTML renderer | |
| 20 | * is responsible for converting the TeX code for display. This avoids inserting | |
| 21 | * SVG code into the Markdown document, which the parser would then have to | |
| 22 | * iterate---a <em>very</em> wasteful operation that impacts front-end | |
| 23 | * performance. | |
| 24 | */ | |
| 25 | public class TeXExtension extends HtmlRendererAdapter | |
| 26 | implements ParserExtension { | |
| 27 | ||
| 28 | /** | |
| 29 | * Responsible for pre-parsing the input. | |
| 30 | */ | |
| 31 | private final Processor<String> mProcessor; | |
| 32 | ||
| 33 | /** | |
| 34 | * Controls how the node renderer produces TeX code within HTML output. | |
| 35 | */ | |
| 36 | private final ExportFormat mExportFormat; | |
| 37 | ||
| 38 | private TeXExtension( | |
| 39 | final Processor<String> processor, final ProcessorContext context ) { | |
| 40 | mProcessor = processor; | |
| 41 | mExportFormat = context.getExportFormat(); | |
| 42 | } | |
| 43 | ||
| 44 | /** | |
| 45 | * Creates an extension capable of handling delimited TeX code in Markdown. | |
| 46 | * | |
| 47 | * @return The new {@link TeXExtension}, never {@code null}. | |
| 48 | */ | |
| 49 | public static TeXExtension create( | |
| 50 | final Processor<String> processor, final ProcessorContext context ) { | |
| 51 | return new TeXExtension( processor, context ); | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Adds the TeX extension for HTML document export types. | |
| 56 | * | |
| 57 | * @param builder The document builder. | |
| 58 | * @param rendererType Indicates the document type to be built. | |
| 59 | */ | |
| 60 | @Override | |
| 61 | public void extend( @NotNull final HtmlRenderer.Builder builder, | |
| 62 | @NotNull final String rendererType ) { | |
| 63 | if( "HTML".equalsIgnoreCase( rendererType ) ) { | |
| 64 | builder.nodeRendererFactory( new Factory( mExportFormat, mProcessor ) ); | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | public void extend( final Parser.Builder builder ) { | |
| 70 | builder.customDelimiterProcessor( new TeXInlineDelimiterProcessor() ); | |
| 71 | } | |
| 72 | ||
| 73 | @Override | |
| 74 | public void parserOptions( final MutableDataHolder options ) { | |
| 75 | } | |
| 76 | } | |
| 1 | 77 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.tex; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.parser.InlineParser; | |
| 5 | import com.vladsch.flexmark.parser.core.delimiter.Delimiter; | |
| 6 | import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor; | |
| 7 | import com.vladsch.flexmark.parser.delimiter.DelimiterRun; | |
| 8 | import com.vladsch.flexmark.util.ast.Node; | |
| 9 | ||
| 10 | public class TeXInlineDelimiterProcessor implements DelimiterProcessor { | |
| 11 | ||
| 12 | @Override | |
| 13 | public void process( final Delimiter opener, | |
| 14 | final Delimiter closer, | |
| 15 | final int delimitersUsed ) { | |
| 16 | final var node = new TexNode( opener, closer ); | |
| 17 | opener.moveNodesBetweenDelimitersTo( node, closer ); | |
| 18 | } | |
| 19 | ||
| 20 | @Override | |
| 21 | public char getOpeningCharacter() { | |
| 22 | return '$'; | |
| 23 | } | |
| 24 | ||
| 25 | @Override | |
| 26 | public char getClosingCharacter() { | |
| 27 | return '$'; | |
| 28 | } | |
| 29 | ||
| 30 | @Override | |
| 31 | public int getMinLength() { | |
| 32 | return 1; | |
| 33 | } | |
| 34 | ||
| 35 | /** | |
| 36 | * Allow for $ or $$. | |
| 37 | * | |
| 38 | * @param opener One or more opening delimiter characters. | |
| 39 | * @param closer One or more closing delimiter characters. | |
| 40 | * @return The number of delimiters to use to determine whether a valid | |
| 41 | * opening delimiter expression is found. | |
| 42 | */ | |
| 43 | @Override | |
| 44 | public int getDelimiterUse( | |
| 45 | final DelimiterRun opener, final DelimiterRun closer ) { | |
| 46 | return 1; | |
| 47 | } | |
| 48 | ||
| 49 | @Override | |
| 50 | public boolean canBeOpener( final String before, | |
| 51 | final String after, | |
| 52 | final boolean leftFlanking, | |
| 53 | final boolean rightFlanking, | |
| 54 | final boolean beforeIsPunctuation, | |
| 55 | final boolean afterIsPunctuation, | |
| 56 | final boolean beforeIsWhitespace, | |
| 57 | final boolean afterIsWhiteSpace ) { | |
| 58 | return leftFlanking; | |
| 59 | } | |
| 60 | ||
| 61 | @Override | |
| 62 | public boolean canBeCloser( final String before, | |
| 63 | final String after, | |
| 64 | final boolean leftFlanking, | |
| 65 | final boolean rightFlanking, | |
| 66 | final boolean beforeIsPunctuation, | |
| 67 | final boolean afterIsPunctuation, | |
| 68 | final boolean beforeIsWhitespace, | |
| 69 | final boolean afterIsWhiteSpace ) { | |
| 70 | return rightFlanking; | |
| 71 | } | |
| 72 | ||
| 73 | @Override | |
| 74 | public Node unmatchedDelimiterNode( | |
| 75 | final InlineParser inlineParser, final DelimiterRun delimiter ) { | |
| 76 | return null; | |
| 77 | } | |
| 78 | ||
| 79 | @Override | |
| 80 | public boolean skipNonOpenerCloser() { | |
| 81 | return false; | |
| 82 | } | |
| 83 | } | |
| 1 | 84 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.tex; | |
| 3 | ||
| 4 | import com.vladsch.flexmark.ast.DelimitedNodeImpl; | |
| 5 | import com.vladsch.flexmark.parser.core.delimiter.Delimiter; | |
| 6 | ||
| 7 | public class TexNode extends DelimitedNodeImpl { | |
| 8 | /** | |
| 9 | * TeX expression wrapped in a {@code <tex>} element. | |
| 10 | */ | |
| 11 | public static final String HTML_TEX = "tex"; | |
| 12 | ||
| 13 | public static final String TOKEN_OPEN = "$"; | |
| 14 | public static final String TOKEN_CLOSE = "$"; | |
| 15 | ||
| 16 | private final String mOpener; | |
| 17 | private final String mCloser; | |
| 18 | ||
| 19 | /** | |
| 20 | * Creates a new TeX node representation that can distinguish between '$' | |
| 21 | * and '$$' as opening/closing delimiters. The '$' is used for inline | |
| 22 | * TeX statements and '$$' is used for multi-line statements. | |
| 23 | * | |
| 24 | * @param opener The opening delimiter. | |
| 25 | * @param closer The closing delimiter. | |
| 26 | */ | |
| 27 | public TexNode( final Delimiter opener, final Delimiter closer ) { | |
| 28 | mOpener = getDelimiter( opener ); | |
| 29 | mCloser = getDelimiter( closer ); | |
| 30 | } | |
| 31 | ||
| 32 | /** | |
| 33 | * @return Either '$' or '$$. | |
| 34 | */ | |
| 35 | public String getOpeningDelimiter() { return mOpener; } | |
| 36 | ||
| 37 | /** | |
| 38 | * @return Either '$' or '$$. | |
| 39 | */ | |
| 40 | public String getClosingDelimiter() { return mCloser; } | |
| 41 | ||
| 42 | private String getDelimiter( final Delimiter delimiter ) { | |
| 43 | return delimiter.getInput().subSequence( | |
| 44 | delimiter.getStartIndex(), delimiter.getEndIndex() | |
| 45 | ).toString(); | |
| 46 | } | |
| 47 | } | |
| 1 | 48 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.tex; | |
| 3 | ||
| 4 | import com.keenwrite.ExportFormat; | |
| 5 | import com.keenwrite.preview.SvgRasterizer; | |
| 6 | import com.keenwrite.processors.Processor; | |
| 7 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 9 | import com.vladsch.flexmark.html.renderer.NodeRendererContext; | |
| 10 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 11 | import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; | |
| 12 | import com.vladsch.flexmark.util.ast.Node; | |
| 13 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 14 | import org.jetbrains.annotations.NotNull; | |
| 15 | import org.jetbrains.annotations.Nullable; | |
| 16 | ||
| 17 | import java.util.Map; | |
| 18 | import java.util.Set; | |
| 19 | ||
| 20 | import static com.keenwrite.ExportFormat.*; | |
| 21 | import static com.keenwrite.preview.MathRenderer.MATH_RENDERER; | |
| 22 | import static com.keenwrite.processors.markdown.extensions.tex.TexNode.*; | |
| 23 | ||
| 24 | public class TexNodeRenderer { | |
| 25 | private static final RendererFacade RENDERER = | |
| 26 | new TexElementNodeRenderer( false ); | |
| 27 | ||
| 28 | private static final Map<ExportFormat, RendererFacade> EXPORT_RENDERERS = | |
| 29 | Map.of( | |
| 30 | APPLICATION_PDF, new TexElementNodeRenderer( true ), | |
| 31 | HTML_TEX_SVG, new TexSvgNodeRenderer(), | |
| 32 | HTML_TEX_DELIMITED, new TexDelimNodeRenderer(), | |
| 33 | XHTML_TEX, new TexElementNodeRenderer( true ), | |
| 34 | MARKDOWN_PLAIN, new TexDelimNodeRenderer(), | |
| 35 | NONE, RENDERER | |
| 36 | ); | |
| 37 | ||
| 38 | public static class Factory implements NodeRendererFactory { | |
| 39 | private final RendererFacade mNodeRenderer; | |
| 40 | ||
| 41 | public Factory( | |
| 42 | final ExportFormat exportFormat, final Processor<String> processor ) { | |
| 43 | mNodeRenderer = EXPORT_RENDERERS.getOrDefault( exportFormat, RENDERER ); | |
| 44 | mNodeRenderer.setProcessor( processor ); | |
| 45 | } | |
| 46 | ||
| 47 | @NotNull | |
| 48 | @Override | |
| 49 | public NodeRenderer apply( @NotNull final DataHolder options ) { | |
| 50 | return mNodeRenderer; | |
| 51 | } | |
| 52 | } | |
| 53 | ||
| 54 | private static abstract class RendererFacade | |
| 55 | implements NodeRenderer { | |
| 56 | private Processor<String> mProcessor; | |
| 57 | ||
| 58 | @Override | |
| 59 | public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 60 | return Set.of( | |
| 61 | new NodeRenderingHandler<>( TexNode.class, this::render ) | |
| 62 | ); | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Subclasses implement this method to render the content of {@link TexNode} | |
| 67 | * instances as per their associated {@link ExportFormat}. | |
| 68 | * | |
| 69 | * @param node {@link Node} containing text content of a math formula. | |
| 70 | * @param context Configuration information (unused). | |
| 71 | * @param html Where to write the rendered output. | |
| 72 | */ | |
| 73 | abstract void render( final TexNode node, | |
| 74 | final NodeRendererContext context, | |
| 75 | final HtmlWriter html ); | |
| 76 | ||
| 77 | private void setProcessor( final Processor<String> processor ) { | |
| 78 | mProcessor = processor; | |
| 79 | } | |
| 80 | ||
| 81 | Processor<String> getProcessor() { | |
| 82 | return mProcessor; | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | /** | |
| 87 | * Responsible for rendering a TeX node as an HTML {@code <tex>} | |
| 88 | * element. This is the default behaviour. | |
| 89 | */ | |
| 90 | private static class TexElementNodeRenderer extends RendererFacade { | |
| 91 | private final boolean mIncludeDelimiter; | |
| 92 | ||
| 93 | private TexElementNodeRenderer( final boolean includeDelimiter ) { | |
| 94 | mIncludeDelimiter = includeDelimiter; | |
| 95 | } | |
| 96 | ||
| 97 | void render( final TexNode node, | |
| 98 | final NodeRendererContext context, | |
| 99 | final HtmlWriter html ) { | |
| 100 | final var text = getProcessor().apply( node.getText().toString() ); | |
| 101 | final var content = | |
| 102 | mIncludeDelimiter | |
| 103 | ? node.getOpeningDelimiter() + text + node.getClosingDelimiter() | |
| 104 | : text; | |
| 105 | html.tag( HTML_TEX ); | |
| 106 | html.raw( content ); | |
| 107 | html.closeTag( HTML_TEX ); | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Responsible for rendering a TeX node as an HTML {@code <svg>} | |
| 113 | * element. | |
| 114 | */ | |
| 115 | private static class TexSvgNodeRenderer extends RendererFacade { | |
| 116 | void render( final TexNode node, | |
| 117 | final NodeRendererContext context, | |
| 118 | final HtmlWriter html ) { | |
| 119 | final var tex = node.getText().toStringOrNull(); | |
| 120 | final var doc = MATH_RENDERER.render( | |
| 121 | tex == null ? "" : getProcessor().apply( tex ) ); | |
| 122 | final var svg = SvgRasterizer.toSvg( doc.getDocumentElement() ); | |
| 123 | html.raw( svg ); | |
| 124 | } | |
| 125 | } | |
| 126 | ||
| 127 | /** | |
| 128 | * Responsible for rendering a TeX node as text bracketed by $ tokens. | |
| 129 | */ | |
| 130 | private static class TexDelimNodeRenderer extends RendererFacade { | |
| 131 | void render( final TexNode node, | |
| 132 | final NodeRendererContext context, | |
| 133 | final HtmlWriter html ) { | |
| 134 | html.raw( TOKEN_OPEN ); | |
| 135 | html.raw( getProcessor().apply( node.getText().toString() ) ); | |
| 136 | html.raw( TOKEN_CLOSE ); | |
| 137 | } | |
| 138 | } | |
| 139 | } | |
| 1 | 140 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.r; | |
| 3 | ||
| 4 | import com.keenwrite.preferences.Workspace; | |
| 5 | import com.keenwrite.processors.DefinitionProcessor; | |
| 6 | import com.keenwrite.processors.Processor; | |
| 7 | import com.keenwrite.processors.ProcessorContext; | |
| 8 | import com.keenwrite.processors.markdown.extensions.r.ROutputProcessor; | |
| 9 | import com.keenwrite.util.BoundedCache; | |
| 10 | import javafx.beans.property.Property; | |
| 11 | ||
| 12 | import javax.script.ScriptEngine; | |
| 13 | import javax.script.ScriptEngineManager; | |
| 14 | import java.io.File; | |
| 15 | import java.nio.file.Path; | |
| 16 | import java.util.Map; | |
| 17 | import java.util.concurrent.atomic.AtomicBoolean; | |
| 18 | ||
| 19 | import static com.keenwrite.constants.Constants.STATUS_PARSE_ERROR; | |
| 20 | import static com.keenwrite.Messages.get; | |
| 21 | import static com.keenwrite.events.StatusEvent.clue; | |
| 22 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 23 | import static com.keenwrite.processors.text.TextReplacementFactory.replace; | |
| 24 | import static com.keenwrite.sigils.RSigilOperator.PREFIX; | |
| 25 | import static com.keenwrite.sigils.RSigilOperator.SUFFIX; | |
| 26 | import static java.lang.Math.max; | |
| 27 | import static java.lang.Math.min; | |
| 28 | import static java.lang.String.format; | |
| 29 | ||
| 30 | /** | |
| 31 | * Transforms a document containing R statements into Markdown. | |
| 32 | */ | |
| 33 | public final class InlineRProcessor extends DefinitionProcessor { | |
| 34 | private final Processor<String> mPostProcessor = new ROutputProcessor(); | |
| 35 | ||
| 36 | /** | |
| 37 | * Where to put document inline evaluated R expressions, constrained to | |
| 38 | * avoid running out of memory. | |
| 39 | */ | |
| 40 | private final Map<String, String> mEvalCache = | |
| 41 | new BoundedCache<>( 512 ); | |
| 42 | ||
| 43 | private static final ScriptEngine ENGINE = | |
| 44 | (new ScriptEngineManager()).getEngineByName( "Renjin" ); | |
| 45 | ||
| 46 | private static final int PREFIX_LENGTH = PREFIX.length(); | |
| 47 | ||
| 48 | private final AtomicBoolean mDirty = new AtomicBoolean( false ); | |
| 49 | ||
| 50 | private final Workspace mWorkspace; | |
| 51 | ||
| 52 | /** | |
| 53 | * Constructs a processor capable of evaluating R statements. | |
| 54 | * | |
| 55 | * @param successor Subsequent link in the processing chain. | |
| 56 | * @param context Contains resolved definitions map. | |
| 57 | */ | |
| 58 | public InlineRProcessor( | |
| 59 | final Processor<String> successor, | |
| 60 | final ProcessorContext context ) { | |
| 61 | super( successor, context ); | |
| 62 | ||
| 63 | mWorkspace = context.getWorkspace(); | |
| 64 | ||
| 65 | bootstrapScriptProperty().addListener( | |
| 66 | ( __, oldScript, newScript ) -> setDirty( true ) ); | |
| 67 | workingDirectoryProperty().addListener( | |
| 68 | ( __, oldScript, newScript ) -> setDirty( true ) ); | |
| 69 | ||
| 70 | // TODO: Watch the "R" property keys in the workspace, directly. | |
| 71 | ||
| 72 | // If the user saves the preferences, make sure that any R-related settings | |
| 73 | // changes are applied. | |
| 74 | // getWorkspace().addSaveEventHandler( ( handler ) -> { | |
| 75 | // if( isDirty() ) { | |
| 76 | // init(); | |
| 77 | // setDirty( false ); | |
| 78 | // } | |
| 79 | // } ); | |
| 80 | ||
| 81 | init(); | |
| 82 | } | |
| 83 | ||
| 84 | /** | |
| 85 | * Initialises the R code so that R can find imported libraries. Note that | |
| 86 | * any existing R functionality will not be overwritten if this method is | |
| 87 | * called multiple times. | |
| 88 | * | |
| 89 | * @return {@code true} if initialization completed and all variables were | |
| 90 | * replaced; {@code false} if any variables remain. | |
| 91 | */ | |
| 92 | public boolean init() { | |
| 93 | final var bootstrap = getBootstrapScript(); | |
| 94 | ||
| 95 | if( !bootstrap.isBlank() ) { | |
| 96 | final var wd = getWorkingDirectory(); | |
| 97 | final var dir = wd.toString().replace( '\\', '/' ); | |
| 98 | final var map = getDefinitions(); | |
| 99 | final var defBegan = mWorkspace.toString( KEY_DEF_DELIM_BEGAN ); | |
| 100 | final var defEnded = mWorkspace.toString( KEY_DEF_DELIM_ENDED ); | |
| 101 | ||
| 102 | map.put( defBegan + "application.r.working.directory" + defEnded, dir ); | |
| 103 | ||
| 104 | final var replaced = replace( bootstrap, map ); | |
| 105 | final var bIndex = replaced.indexOf( defBegan ); | |
| 106 | ||
| 107 | // If there's a delimiter in the replaced text it means not all variables | |
| 108 | // are bound, which is an error. | |
| 109 | if( bIndex >= 0 ) { | |
| 110 | var eIndex = replaced.indexOf( defEnded ); | |
| 111 | eIndex = (eIndex == -1) ? replaced.length() - 1 : max( bIndex, eIndex ); | |
| 112 | ||
| 113 | final var def = replaced.substring( | |
| 114 | bIndex + defBegan.length(), eIndex ); | |
| 115 | clue( "Main.status.error.bootstrap.eval", | |
| 116 | format( "%s%s%s", defBegan, def, defEnded ) ); | |
| 117 | ||
| 118 | return false; | |
| 119 | } | |
| 120 | else { | |
| 121 | eval( replaced ); | |
| 122 | } | |
| 123 | } | |
| 124 | ||
| 125 | return true; | |
| 126 | } | |
| 127 | ||
| 128 | /** | |
| 129 | * Empties the cache. | |
| 130 | */ | |
| 131 | public void clear() { | |
| 132 | mEvalCache.clear(); | |
| 133 | } | |
| 134 | ||
| 135 | /** | |
| 136 | * Sets the dirty flag to indicate that the bootstrap script or working | |
| 137 | * directory has been modified. Upon saving the preferences, if this flag | |
| 138 | * is true, then {@link #init()} will be called to reload the R environment. | |
| 139 | * | |
| 140 | * @param dirty Set to true to reload changes upon closing preferences. | |
| 141 | */ | |
| 142 | private void setDirty( final boolean dirty ) { | |
| 143 | mDirty.set( dirty ); | |
| 144 | } | |
| 145 | ||
| 146 | /** | |
| 147 | * Answers whether R-related settings have been modified. | |
| 148 | * | |
| 149 | * @return {@code true} when the settings have changed. | |
| 150 | */ | |
| 151 | private boolean isDirty() { | |
| 152 | return mDirty.get(); | |
| 153 | } | |
| 154 | ||
| 155 | /** | |
| 156 | * Evaluates all R statements in the source document and inserts the | |
| 157 | * calculated value into the generated document. | |
| 158 | * | |
| 159 | * @param text The document text that includes variables that should be | |
| 160 | * replaced with values when rendered as HTML. | |
| 161 | * @return The generated document with output from all R statements | |
| 162 | * substituted with value returned from their execution. | |
| 163 | */ | |
| 164 | @Override | |
| 165 | public String apply( final String text ) { | |
| 166 | final int length = text.length(); | |
| 167 | ||
| 168 | // The * 2 is a wild guess at the ratio of R statements to the length | |
| 169 | // of text produced by those statements. | |
| 170 | final StringBuilder sb = new StringBuilder( length * 2 ); | |
| 171 | ||
| 172 | int prevIndex = 0; | |
| 173 | int currIndex = text.indexOf( PREFIX ); | |
| 174 | ||
| 175 | while( currIndex >= 0 ) { | |
| 176 | // Copy everything up to, but not including, the opening token. | |
| 177 | sb.append( text, prevIndex, currIndex ); | |
| 178 | ||
| 179 | // Jump to the start of the R statement. | |
| 180 | prevIndex = currIndex + PREFIX_LENGTH; | |
| 181 | ||
| 182 | // Find the closing token, without indexing past the text boundary. | |
| 183 | currIndex = text.indexOf( SUFFIX, min( currIndex + 1, length ) ); | |
| 184 | ||
| 185 | // Only evaluate inline R statements that have end delimiters. | |
| 186 | if( currIndex > 1 ) { | |
| 187 | // Extract the inline R statement to be evaluated. | |
| 188 | final var r = text.substring( prevIndex, currIndex ); | |
| 189 | ||
| 190 | // Pass the R statement into the R engine for evaluation. | |
| 191 | try { | |
| 192 | // Append the string representation of the result into the text. | |
| 193 | sb.append( evalCached( r ) ); | |
| 194 | } catch( final Exception ex ) { | |
| 195 | // Inform the user that there was a problem. | |
| 196 | clue( STATUS_PARSE_ERROR, ex.getMessage(), currIndex ); | |
| 197 | ||
| 198 | // If the string couldn't be parsed using R, append the statement | |
| 199 | // that failed to parse, instead of its evaluated value. | |
| 200 | sb.append( PREFIX ).append( r ).append( SUFFIX ); | |
| 201 | } | |
| 202 | ||
| 203 | // Retain the R statement's ending position in the text. | |
| 204 | prevIndex = currIndex + 1; | |
| 205 | } | |
| 206 | ||
| 207 | // Find the start of the next inline R statement. | |
| 208 | currIndex = text.indexOf( PREFIX, min( currIndex + 1, length ) ); | |
| 209 | } | |
| 210 | ||
| 211 | // Copy from the previous index to the end of the string. | |
| 212 | return sb.append( text.substring( min( prevIndex, length ) ) ).toString(); | |
| 213 | } | |
| 214 | ||
| 215 | /** | |
| 216 | * Look up an R expression from the cache then return the resulting object. | |
| 217 | * If the R expression hasn't been cached, it'll first be evaluated. | |
| 218 | * | |
| 219 | * @param r The expression to evaluate. | |
| 220 | * @return The object resulting from the evaluation. | |
| 221 | */ | |
| 222 | private String evalCached( final String r ) { | |
| 223 | return mEvalCache.computeIfAbsent( r, __ -> evalHtml( r ) ); | |
| 224 | } | |
| 225 | ||
| 226 | /** | |
| 227 | * Converts the given string to HTML, trimming new lines, and inlining | |
| 228 | * the text if it is a paragraph. Otherwise, the resulting HTML is most likely | |
| 229 | * complex (e.g., a Markdown table) and should be rendered as its HTML | |
| 230 | * equivalent. | |
| 231 | * | |
| 232 | * @param r The R expression to evaluate then convert to HTML. | |
| 233 | * @return The result from the R expression as an HTML element. | |
| 234 | */ | |
| 235 | private String evalHtml( final String r ) { | |
| 236 | return mPostProcessor.apply( eval( r ) ); | |
| 237 | } | |
| 238 | ||
| 239 | /** | |
| 240 | * Evaluate an R expression and return the resulting object. | |
| 241 | * | |
| 242 | * @param r The expression to evaluate. | |
| 243 | * @return The object resulting from the evaluation. | |
| 244 | */ | |
| 245 | private String eval( final String r ) { | |
| 246 | try { | |
| 247 | return ENGINE.eval( r ).toString(); | |
| 248 | } catch( final Exception ex ) { | |
| 249 | final var expr = r.substring( 0, min( r.length(), 50 ) ); | |
| 250 | clue( get( "Main.status.error.r", expr, ex.getMessage() ), ex ); | |
| 251 | return ""; | |
| 252 | } | |
| 253 | } | |
| 254 | ||
| 255 | /** | |
| 256 | * Return the given path if not {@code null}, otherwise return the path to | |
| 257 | * the user's directory. | |
| 258 | * | |
| 259 | * @return A non-null path. | |
| 260 | */ | |
| 261 | private Path getWorkingDirectory() { | |
| 262 | return workingDirectoryProperty().getValue().toPath(); | |
| 263 | } | |
| 264 | ||
| 265 | private Property<File> workingDirectoryProperty() { | |
| 266 | return getWorkspace().fileProperty( KEY_R_DIR ); | |
| 267 | } | |
| 268 | ||
| 269 | /** | |
| 270 | * Loads the R init script from the application's persisted preferences. | |
| 271 | * | |
| 272 | * @return A non-null string, possibly empty. | |
| 273 | */ | |
| 274 | private String getBootstrapScript() { | |
| 275 | return bootstrapScriptProperty().getValue(); | |
| 276 | } | |
| 277 | ||
| 278 | private Property<String> bootstrapScriptProperty() { | |
| 279 | return getWorkspace().valuesProperty( KEY_R_SCRIPT ); | |
| 280 | } | |
| 281 | ||
| 282 | private Workspace getWorkspace() { | |
| 283 | return mWorkspace; | |
| 284 | } | |
| 285 | } | |
| 1 | 286 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.r; | |
| 3 | ||
| 4 | import com.keenwrite.processors.ExecutorProcessor; | |
| 5 | import com.keenwrite.processors.Processor; | |
| 6 | import com.keenwrite.processors.ProcessorContext; | |
| 7 | ||
| 8 | import java.util.function.Function; | |
| 9 | ||
| 10 | import static com.keenwrite.processors.IdentityProcessor.IDENTITY; | |
| 11 | ||
| 12 | /** | |
| 13 | * Responsible for processing R statements within a text block. | |
| 14 | */ | |
| 15 | public final class RProcessor | |
| 16 | extends ExecutorProcessor<String> implements Function<String, String> { | |
| 17 | private final Processor<String> mProcessor; | |
| 18 | private final InlineRProcessor mInlineRProcessor; | |
| 19 | ||
| 20 | private boolean mReady; | |
| 21 | ||
| 22 | public RProcessor( final ProcessorContext context ) { | |
| 23 | final var irp = new InlineRProcessor( IDENTITY, context ); | |
| 24 | final var rvp = new RVariableProcessor( irp, context ); | |
| 25 | mProcessor = new ExecutorProcessor<>( rvp ); | |
| 26 | mInlineRProcessor = irp; | |
| 27 | } | |
| 28 | ||
| 29 | public void init() { | |
| 30 | mReady = mInlineRProcessor.init(); | |
| 31 | } | |
| 32 | ||
| 33 | public String apply( final String text ) { | |
| 34 | return mProcessor.apply( text ); | |
| 35 | } | |
| 36 | ||
| 37 | public boolean isReady() { | |
| 38 | return mReady; | |
| 39 | } | |
| 40 | } | |
| 1 | 41 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.r; | |
| 3 | ||
| 4 | import com.keenwrite.preferences.Workspace; | |
| 5 | import com.keenwrite.processors.DefinitionProcessor; | |
| 6 | import com.keenwrite.processors.ProcessorContext; | |
| 7 | import com.keenwrite.sigils.RSigilOperator; | |
| 8 | import com.keenwrite.sigils.SigilOperator; | |
| 9 | import com.keenwrite.sigils.YamlSigilOperator; | |
| 10 | ||
| 11 | import java.util.HashMap; | |
| 12 | import java.util.Map; | |
| 13 | ||
| 14 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 15 | ||
| 16 | /** | |
| 17 | * Converts the keys of the resolved map from default form to R form, then | |
| 18 | * performs a substitution on the text. The default R variable syntax is | |
| 19 | * {@code v$tree$leaf}. | |
| 20 | */ | |
| 21 | public final class RVariableProcessor extends DefinitionProcessor { | |
| 22 | ||
| 23 | private final SigilOperator mSigilOperator; | |
| 24 | ||
| 25 | public RVariableProcessor( | |
| 26 | final InlineRProcessor irp, final ProcessorContext context ) { | |
| 27 | super( irp, context ); | |
| 28 | mSigilOperator = createSigilOperator( context.getWorkspace() ); | |
| 29 | } | |
| 30 | ||
| 31 | /** | |
| 32 | * Returns the R-based version of the interpolated variable definitions. | |
| 33 | * | |
| 34 | * @return Variable names transmogrified from the default syntax to R syntax. | |
| 35 | */ | |
| 36 | @Override | |
| 37 | protected Map<String, String> getDefinitions() { | |
| 38 | return entoken( super.getDefinitions() ); | |
| 39 | } | |
| 40 | ||
| 41 | /** | |
| 42 | * Converts the given map from regular variables to R variables. | |
| 43 | * | |
| 44 | * @param map Map of variable names to values. | |
| 45 | * @return Map of R variables. | |
| 46 | */ | |
| 47 | private Map<String, String> entoken( final Map<String, String> map ) { | |
| 48 | final var rMap = new HashMap<String, String>( map.size() ); | |
| 49 | ||
| 50 | for( final var entry : map.entrySet() ) { | |
| 51 | final var key = entry.getKey(); | |
| 52 | rMap.put( mSigilOperator.entoken( key ), escape( map.get( key ) ) ); | |
| 53 | } | |
| 54 | ||
| 55 | return rMap; | |
| 56 | } | |
| 57 | ||
| 58 | private String escape( final String value ) { | |
| 59 | return '\'' + escape( value, '\'', "\\'" ) + '\''; | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * TODO: Make generic method for replacing text. | |
| 64 | * | |
| 65 | * @param haystack Search this string for the needle, must not be null. | |
| 66 | * @param needle The character to find in the haystack. | |
| 67 | * @param thread Replace the needle with this text, if the needle is found. | |
| 68 | * @return The haystack with the all instances of needle replaced with thread. | |
| 69 | */ | |
| 70 | @SuppressWarnings( "SameParameterValue" ) | |
| 71 | private String escape( | |
| 72 | final String haystack, final char needle, final String thread ) { | |
| 73 | int end = haystack.indexOf( needle ); | |
| 74 | ||
| 75 | if( end < 0 ) { | |
| 76 | return haystack; | |
| 77 | } | |
| 78 | ||
| 79 | final int length = haystack.length(); | |
| 80 | int start = 0; | |
| 81 | ||
| 82 | // Replace up to 32 occurrences before the string reallocates its buffer. | |
| 83 | final var sb = new StringBuilder( length + 32 ); | |
| 84 | ||
| 85 | while( end >= 0 ) { | |
| 86 | sb.append( haystack, start, end ).append( thread ); | |
| 87 | start = end + 1; | |
| 88 | end = haystack.indexOf( needle, start ); | |
| 89 | } | |
| 90 | ||
| 91 | return sb.append( haystack.substring( start ) ).toString(); | |
| 92 | } | |
| 93 | ||
| 94 | private SigilOperator createSigilOperator( final Workspace workspace ) { | |
| 95 | final var tokens = workspace.toTokens( | |
| 96 | KEY_R_DELIM_BEGAN, KEY_R_DELIM_ENDED ); | |
| 97 | final var antecedent = createDefinitionOperator( workspace ); | |
| 98 | return new RSigilOperator( tokens, antecedent ); | |
| 99 | } | |
| 100 | ||
| 101 | private SigilOperator createDefinitionOperator( final Workspace workspace ) { | |
| 102 | final var tokens = workspace.toTokens( | |
| 103 | KEY_DEF_DELIM_BEGAN, KEY_DEF_DELIM_ENDED ); | |
| 104 | return new YamlSigilOperator( tokens ); | |
| 105 | } | |
| 106 | } | |
| 1 | 107 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.text; | |
| 3 | ||
| 4 | import java.util.Map; | |
| 5 | ||
| 6 | /** | |
| 7 | * Responsible for common behaviour across all text replacer implementations. | |
| 8 | */ | |
| 9 | public abstract class AbstractTextReplacer implements TextReplacer { | |
| 10 | ||
| 11 | /** | |
| 12 | * Default (empty) constructor. | |
| 13 | */ | |
| 14 | protected AbstractTextReplacer() { | |
| 15 | } | |
| 16 | ||
| 17 | protected String[] keys( final Map<String, String> map ) { | |
| 18 | return map.keySet().toArray( new String[ 0 ] ); | |
| 19 | } | |
| 20 | ||
| 21 | protected String[] values( final Map<String, String> map ) { | |
| 22 | return map.values().toArray( new String[ 0 ] ); | |
| 23 | } | |
| 24 | } | |
| 1 | 25 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.text; | |
| 3 | ||
| 4 | import java.util.Map; | |
| 5 | ||
| 6 | import static org.ahocorasick.trie.Trie.builder; | |
| 7 | ||
| 8 | /** | |
| 9 | * Replaces text using an Aho-Corasick algorithm. | |
| 10 | */ | |
| 11 | public class AhoCorasickReplacer extends AbstractTextReplacer { | |
| 12 | ||
| 13 | /** | |
| 14 | * Default (empty) constructor. | |
| 15 | */ | |
| 16 | protected AhoCorasickReplacer() { | |
| 17 | } | |
| 18 | ||
| 19 | @Override | |
| 20 | public String replace( final String text, final Map<String, String> map ) { | |
| 21 | // Create a buffer sufficiently large that re-allocations are minimized. | |
| 22 | final var sb = new StringBuilder( (int)(text.length() * 1.25) ); | |
| 23 | ||
| 24 | // Definition names cannot overlap. | |
| 25 | final var builder = builder().ignoreOverlaps(); | |
| 26 | builder.addKeywords( keys( map ) ); | |
| 27 | ||
| 28 | int index = 0; | |
| 29 | ||
| 30 | // Replace all instances with dereferenced variables. | |
| 31 | for( final var emit : builder.build().parseText( text ) ) { | |
| 32 | sb.append( text, index, emit.getStart() ); | |
| 33 | sb.append( map.get( emit.getKeyword() ) ); | |
| 34 | index = emit.getEnd() + 1; | |
| 35 | } | |
| 36 | ||
| 37 | // Add the remainder of the string (contains no more matches). | |
| 38 | sb.append( text.substring( index ) ); | |
| 39 | ||
| 40 | return sb.toString(); | |
| 41 | } | |
| 42 | } | |
| 1 | 43 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.text; | |
| 3 | ||
| 4 | import java.util.Map; | |
| 5 | ||
| 6 | import static org.apache.commons.lang3.StringUtils.replaceEach; | |
| 7 | ||
| 8 | /** | |
| 9 | * Replaces text using Apache's StringUtils.replaceEach method. | |
| 10 | */ | |
| 11 | public class StringUtilsReplacer extends AbstractTextReplacer { | |
| 12 | ||
| 13 | /** | |
| 14 | * Default (empty) constructor. | |
| 15 | */ | |
| 16 | protected StringUtilsReplacer() { | |
| 17 | } | |
| 18 | ||
| 19 | @Override | |
| 20 | public String replace( final String text, final Map<String, String> map ) { | |
| 21 | return replaceEach( text, keys( map ), values( map ) ); | |
| 22 | } | |
| 23 | } | |
| 1 | 24 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.text; | |
| 3 | ||
| 4 | import java.util.Map; | |
| 5 | ||
| 6 | /** | |
| 7 | * Used to generate a class capable of efficiently replacing variable | |
| 8 | * definitions with their values. | |
| 9 | */ | |
| 10 | public final class TextReplacementFactory { | |
| 11 | ||
| 12 | private static final TextReplacer APACHE = new StringUtilsReplacer(); | |
| 13 | private static final TextReplacer AHO_CORASICK = new AhoCorasickReplacer(); | |
| 14 | ||
| 15 | /** | |
| 16 | * Returns a text search/replacement instance that is reasonably optimal for | |
| 17 | * the given length of text. | |
| 18 | * | |
| 19 | * @param length The length of text that requires some search and replacing. | |
| 20 | * @return A class that can search and replace text with utmost expediency. | |
| 21 | */ | |
| 22 | public static TextReplacer getTextReplacer( final int length ) { | |
| 23 | // After about 1,500 characters, the StringUtils implementation is slower | |
| 24 | // than the Aho-Corsick algorithm implementation. | |
| 25 | return length < 1500 ? APACHE : AHO_CORASICK; | |
| 26 | } | |
| 27 | ||
| 28 | /** | |
| 29 | * Convenience method to instantiate a suitable text replacer algorithm and | |
| 30 | * perform a replacement using the given map. At this point, the values should | |
| 31 | * be already dereferenced and ready to be substituted verbatim; any | |
| 32 | * recursively defined values must have been interpolated previously. | |
| 33 | * | |
| 34 | * @param text The text containing zero or more variables to replace. | |
| 35 | * @param map The map of variables to their dereferenced values. | |
| 36 | * @return The text with all variables replaced. | |
| 37 | */ | |
| 38 | public static String replace( | |
| 39 | final String text, final Map<String, String> map ) { | |
| 40 | return getTextReplacer( text.length() ).replace( text, map ); | |
| 41 | } | |
| 42 | } | |
| 1 | 43 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.text; | |
| 3 | ||
| 4 | import java.util.Map; | |
| 5 | ||
| 6 | /** | |
| 7 | * Defines the ability to replace text given a set of keys and values. | |
| 8 | */ | |
| 9 | public interface TextReplacer { | |
| 10 | ||
| 11 | /** | |
| 12 | * Searches through the given text for any of the keys given in the map and | |
| 13 | * replaces the keys that appear in the text with the key's corresponding | |
| 14 | * value. | |
| 15 | * | |
| 16 | * @param text The text that contains zero or more keys. | |
| 17 | * @param map The set of keys mapped to replacement values. | |
| 18 | * @return The given text with all keys replaced with corresponding values. | |
| 19 | */ | |
| 20 | String replace( String text, Map<String, String> map ); | |
| 21 | } | |
| 1 | 22 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.search; | |
| 3 | ||
| 4 | import com.keenwrite.util.CyclicIterator; | |
| 5 | import javafx.beans.property.ObjectProperty; | |
| 6 | import javafx.beans.property.SimpleObjectProperty; | |
| 7 | import javafx.beans.value.ObservableValue; | |
| 8 | import javafx.scene.control.IndexRange; | |
| 9 | import org.ahocorasick.trie.Emit; | |
| 10 | import org.ahocorasick.trie.Trie; | |
| 11 | ||
| 12 | import java.util.ArrayList; | |
| 13 | import java.util.List; | |
| 14 | ||
| 15 | import static org.ahocorasick.trie.Trie.builder; | |
| 16 | ||
| 17 | /** | |
| 18 | * Responsible for finding words in a text document. This implementation uses | |
| 19 | * a {@link Trie} for efficiency. | |
| 20 | */ | |
| 21 | public final class SearchModel { | |
| 22 | private final ObjectProperty<IndexRange> mMatchOffset = | |
| 23 | new SimpleObjectProperty<>(); | |
| 24 | private final ObjectProperty<Integer> mMatchCount = | |
| 25 | new SimpleObjectProperty<>(); | |
| 26 | private final ObjectProperty<Integer> mMatchIndex = | |
| 27 | new SimpleObjectProperty<>(); | |
| 28 | ||
| 29 | private CyclicIterator<Emit> mMatches = new CyclicIterator<>( List.of() ); | |
| 30 | ||
| 31 | private String mNeedle = ""; | |
| 32 | ||
| 33 | /** | |
| 34 | * Creates a new {@link SearchModel} that finds all text string in a | |
| 35 | * document simultaneously. | |
| 36 | */ | |
| 37 | public SearchModel() { | |
| 38 | } | |
| 39 | ||
| 40 | public ObjectProperty<Integer> matchCountProperty() { | |
| 41 | return mMatchCount; | |
| 42 | } | |
| 43 | ||
| 44 | public ObjectProperty<Integer> matchIndexProperty() { | |
| 45 | return mMatchIndex; | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * Observers watch this property to be notified when a needle has been | |
| 50 | * found in the haystack. Use {@link IndexRange#getStart()} to get the | |
| 51 | * absolute offset into the text (zero-based). | |
| 52 | * | |
| 53 | * @return The {@link IndexRange} property to observe, representing the | |
| 54 | * most recently matched text offset into the document. | |
| 55 | */ | |
| 56 | public ObservableValue<IndexRange> matchOffsetProperty() { | |
| 57 | return mMatchOffset; | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Searches the document for text matching the given parameter value. This | |
| 62 | * is the main entry point for kicking off text searches. | |
| 63 | * | |
| 64 | * @param needle The text string to find in the document, no regex allowed. | |
| 65 | * @param haystack The document to search within for a text string. | |
| 66 | */ | |
| 67 | public void search( final String needle, final String haystack ) { | |
| 68 | assert needle != null; | |
| 69 | assert haystack != null; | |
| 70 | ||
| 71 | final var trie = builder() | |
| 72 | .ignoreCase() | |
| 73 | .ignoreOverlaps() | |
| 74 | .addKeyword( needle ) | |
| 75 | .build(); | |
| 76 | final var emits = trie.parseText( haystack ); | |
| 77 | ||
| 78 | mMatches = new CyclicIterator<>( new ArrayList<>( emits ) ); | |
| 79 | mMatchCount.set( emits.size() ); | |
| 80 | mNeedle = needle; | |
| 81 | advance(); | |
| 82 | } | |
| 83 | ||
| 84 | /** | |
| 85 | * Searches the document for the last known needle. | |
| 86 | * | |
| 87 | * @param haystack The new text to search. | |
| 88 | */ | |
| 89 | public void search( final String haystack ) { | |
| 90 | search( mNeedle, haystack ); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Moves the search iterator to the next match, wrapping as needed. | |
| 95 | */ | |
| 96 | public void advance() { | |
| 97 | if( mMatches.hasNext() ) { | |
| 98 | setCurrent( mMatches.next() ); | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | /** | |
| 103 | * Moves the search iterator to the previous match, wrapping as needed. | |
| 104 | */ | |
| 105 | public void retreat() { | |
| 106 | if( mMatches.hasPrevious() ) { | |
| 107 | setCurrent( mMatches.previous() ); | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | private void setCurrent( final Emit emit ) { | |
| 112 | mMatchOffset.set( new IndexRange( emit.getStart(), emit.getEnd() ) ); | |
| 113 | mMatchIndex.set( mMatches.getIndex() + 1 ); | |
| 114 | } | |
| 115 | } | |
| 1 | 116 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.service; | |
| 3 | ||
| 4 | /** | |
| 5 | * All services inherit from this one. | |
| 6 | */ | |
| 7 | public interface Service { | |
| 8 | } | |
| 1 | 9 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.service; | |
| 3 | ||
| 4 | import java.util.Iterator; | |
| 5 | import java.util.List; | |
| 6 | ||
| 7 | /** | |
| 8 | * Defines how settings and options can be retrieved. | |
| 9 | */ | |
| 10 | public interface Settings extends Service { | |
| 11 | ||
| 12 | /** | |
| 13 | * Returns a setting property or its default value. | |
| 14 | * | |
| 15 | * @param property The property key name to obtain its value. | |
| 16 | * @param defaultValue The default value to return iff the property cannot be | |
| 17 | * found. | |
| 18 | * @return The property value for the given property key. | |
| 19 | */ | |
| 20 | String getSetting( String property, String defaultValue ); | |
| 21 | ||
| 22 | /** | |
| 23 | * Returns a setting property or its default value. | |
| 24 | * | |
| 25 | * @param property The property key name to obtain its value. | |
| 26 | * @param defaultValue The default value to return iff the property cannot be | |
| 27 | * found. | |
| 28 | * @return The property value for the given property key. | |
| 29 | */ | |
| 30 | int getSetting( String property, int defaultValue ); | |
| 31 | ||
| 32 | /** | |
| 33 | * Returns a list of property names that begin with the given prefix. The | |
| 34 | * prefix is included in any matching results. This will return keys that | |
| 35 | * either match the prefix or start with the prefix followed by a dot ('.'). | |
| 36 | * For example, a prefix value of <code>the.property.name</code> will likely | |
| 37 | * return the expected results, but <code>the.property.name.</code> (note the | |
| 38 | * extraneous period) will probably not. | |
| 39 | * | |
| 40 | * @param prefix The prefix to compare against each property name. | |
| 41 | * @return The list of property names that have the given prefix. | |
| 42 | */ | |
| 43 | Iterator<String> getKeys( final String prefix ); | |
| 44 | ||
| 45 | /** | |
| 46 | * Convert the generic list of property objects into strings. | |
| 47 | * | |
| 48 | * @param property The property value to coerce. | |
| 49 | * @param defaults The defaults values to use should the property be unset. | |
| 50 | * @return The list of properties coerced from objects to strings. | |
| 51 | */ | |
| 52 | List<String> getStringSettingList( String property, List<String> defaults ); | |
| 53 | ||
| 54 | /** | |
| 55 | * Converts the generic list of property objects into strings. | |
| 56 | * | |
| 57 | * @param property The property value to coerce. | |
| 58 | * @return The list of properties coerced from objects to strings. | |
| 59 | */ | |
| 60 | List<String> getStringSettingList( String property ); | |
| 61 | } | |
| 1 | 62 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.service.events; | |
| 3 | ||
| 4 | /** | |
| 5 | * Represents a message that contains a title and content. | |
| 6 | */ | |
| 7 | public interface Notification { | |
| 8 | ||
| 9 | /** | |
| 10 | * Alert title. | |
| 11 | * | |
| 12 | * @return A non-null string to use as alert message title. | |
| 13 | */ | |
| 14 | String getTitle(); | |
| 15 | ||
| 16 | /** | |
| 17 | * Alert message content. | |
| 18 | * | |
| 19 | * @return A non-null string that contains information for the user. | |
| 20 | */ | |
| 21 | String getContent(); | |
| 22 | } | |
| 1 | 23 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.service.events; | |
| 3 | ||
| 4 | import javafx.scene.control.Alert; | |
| 5 | import javafx.stage.Window; | |
| 6 | ||
| 7 | import java.nio.file.Path; | |
| 8 | ||
| 9 | /** | |
| 10 | * Provides the application with a uniform way to notify the user of events. | |
| 11 | */ | |
| 12 | public interface Notifier { | |
| 13 | ||
| 14 | /** | |
| 15 | * Constructs an alert message text for a modal alert dialog. | |
| 16 | * | |
| 17 | * @param parent The window responsible for the child dialog. | |
| 18 | * @param path The path to a file that was not actionable. | |
| 19 | * @param titleKey The dialog box message title. | |
| 20 | * @param messageKey The dialog box message content (needs formatting). | |
| 21 | * @param ex The problem that requires user attention. | |
| 22 | */ | |
| 23 | void alert( | |
| 24 | Window parent, | |
| 25 | Path path, | |
| 26 | String titleKey, | |
| 27 | String messageKey, | |
| 28 | Exception ex ); | |
| 29 | ||
| 30 | /** | |
| 31 | * Constructs an alert message text for a modal alert dialog. | |
| 32 | * | |
| 33 | * @param parent The window responsible for the child dialog. | |
| 34 | * @param path The path to a file that was not actionable. | |
| 35 | * @param key Prefix for both title and message key. | |
| 36 | * @param ex The problem that requires user attention. | |
| 37 | */ | |
| 38 | default void alert( | |
| 39 | Window parent, | |
| 40 | Path path, | |
| 41 | String key, | |
| 42 | Exception ex ) { | |
| 43 | alert( parent, path, key + ".title", key + ".message", ex ); | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Contains all the information that the user needs to know about a problem. | |
| 48 | * | |
| 49 | * @param title The dialog box message title (i.e., the error context). | |
| 50 | * @param message The message content (formatted with the given args). | |
| 51 | * @param args The arguments to the message content that must be formatted. | |
| 52 | * @return The message suitable for building a modal alert dialog. | |
| 53 | */ | |
| 54 | Notification createNotification( | |
| 55 | String title, | |
| 56 | String message, | |
| 57 | Object... args ); | |
| 58 | ||
| 59 | /** | |
| 60 | * Creates an alert of alert type error with a message showing the cause of | |
| 61 | * the error. | |
| 62 | * | |
| 63 | * @param parent Dialog box owner (for modal purposes). | |
| 64 | * @param message The error message, title, and possibly more details. | |
| 65 | * @return A modal alert dialog box ready to display using showAndWait. | |
| 66 | */ | |
| 67 | Alert createError( Window parent, Notification message ); | |
| 68 | ||
| 69 | /** | |
| 70 | * Creates an alert of alert type confirmation with Yes/No/Cancel buttons. | |
| 71 | * | |
| 72 | * @param parent Dialog box owner (for modal purposes). | |
| 73 | * @param message The message, title, and possibly more details. | |
| 74 | * @return A modal alert dialog box ready to display using showAndWait. | |
| 75 | */ | |
| 76 | Alert createConfirmation( Window parent, Notification message ); | |
| 77 | } | |
| 1 | 78 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.service.events.impl; | |
| 3 | ||
| 4 | import javafx.scene.Node; | |
| 5 | import javafx.scene.control.ButtonBar; | |
| 6 | import javafx.scene.control.DialogPane; | |
| 7 | ||
| 8 | import static com.keenwrite.constants.Constants.sSettings; | |
| 9 | import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS; | |
| 10 | ||
| 11 | /** | |
| 12 | * Ensures a consistent button order for alert dialogs across platforms (because | |
| 13 | * the default button order on Linux defies all logic). | |
| 14 | */ | |
| 15 | public class ButtonOrderPane extends DialogPane { | |
| 16 | public ButtonOrderPane() { | |
| 17 | } | |
| 18 | ||
| 19 | @Override | |
| 20 | protected Node createButtonBar() { | |
| 21 | final var node = (ButtonBar) super.createButtonBar(); | |
| 22 | node.setButtonOrder( getButtonOrder() ); | |
| 23 | return node; | |
| 24 | } | |
| 25 | ||
| 26 | private String getButtonOrder() { | |
| 27 | return sSettings.getSetting( | |
| 28 | "dialog.alert.button.order.windows", BUTTON_ORDER_WINDOWS ); | |
| 29 | } | |
| 30 | } | |
| 1 | 31 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.service.events.impl; | |
| 3 | ||
| 4 | import com.keenwrite.service.events.Notification; | |
| 5 | ||
| 6 | import java.text.MessageFormat; | |
| 7 | ||
| 8 | /** | |
| 9 | * Responsible for alerting the user to prominent information. | |
| 10 | */ | |
| 11 | public class DefaultNotification implements Notification { | |
| 12 | ||
| 13 | private final String title; | |
| 14 | private final String content; | |
| 15 | ||
| 16 | /** | |
| 17 | * Constructs default message text for a notification. | |
| 18 | * | |
| 19 | * @param title The message title. | |
| 20 | * @param message The message content (needs formatting). | |
| 21 | * @param args The arguments to the message content that must be formatted. | |
| 22 | */ | |
| 23 | public DefaultNotification( | |
| 24 | final String title, | |
| 25 | final String message, | |
| 26 | final Object... args ) { | |
| 27 | this.title = title; | |
| 28 | this.content = MessageFormat.format( message, args ); | |
| 29 | } | |
| 30 | ||
| 31 | @Override | |
| 32 | public String getTitle() { | |
| 33 | return this.title; | |
| 34 | } | |
| 35 | ||
| 36 | @Override | |
| 37 | public String getContent() { | |
| 38 | return this.content; | |
| 39 | } | |
| 40 | ||
| 41 | } | |
| 1 | 42 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.service.events.impl; | |
| 3 | ||
| 4 | import com.keenwrite.service.events.Notification; | |
| 5 | import com.keenwrite.service.events.Notifier; | |
| 6 | import javafx.scene.control.Alert; | |
| 7 | import javafx.scene.control.Alert.AlertType; | |
| 8 | import javafx.stage.Window; | |
| 9 | ||
| 10 | import java.nio.file.Path; | |
| 11 | ||
| 12 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG_NODE; | |
| 13 | import static com.keenwrite.Messages.get; | |
| 14 | import static javafx.scene.control.Alert.AlertType.CONFIRMATION; | |
| 15 | import static javafx.scene.control.Alert.AlertType.ERROR; | |
| 16 | import static javafx.scene.control.ButtonType.*; | |
| 17 | ||
| 18 | /** | |
| 19 | * Provides the ability to notify the user of events that need attention, | |
| 20 | * such as prompting the user to confirm closing when there are unsaved changes. | |
| 21 | */ | |
| 22 | public final class DefaultNotifier implements Notifier { | |
| 23 | ||
| 24 | @Override | |
| 25 | public Notification createNotification( | |
| 26 | final String title, | |
| 27 | final String message, | |
| 28 | final Object... args ) { | |
| 29 | return new DefaultNotification( title, message, args ); | |
| 30 | } | |
| 31 | ||
| 32 | @Override | |
| 33 | public void alert( | |
| 34 | final Window parent, | |
| 35 | final Path path, | |
| 36 | final String titleKey, | |
| 37 | final String messageKey, | |
| 38 | final Exception ex ) { | |
| 39 | final var message = createNotification( | |
| 40 | get( titleKey ), get( messageKey ), path, ex.getMessage() | |
| 41 | ); | |
| 42 | ||
| 43 | createError( parent, message ).showAndWait(); | |
| 44 | } | |
| 45 | ||
| 46 | @Override | |
| 47 | public Alert createConfirmation( | |
| 48 | final Window parent, final Notification message ) { | |
| 49 | final var alert = createAlertDialog( parent, CONFIRMATION, message ); | |
| 50 | ||
| 51 | alert.getButtonTypes().setAll( YES, NO, CANCEL ); | |
| 52 | ||
| 53 | return alert; | |
| 54 | } | |
| 55 | ||
| 56 | @Override | |
| 57 | public Alert createError( final Window parent, final Notification message ) { | |
| 58 | return createAlertDialog( parent, ERROR, message ); | |
| 59 | } | |
| 60 | ||
| 61 | private Alert createAlertDialog( | |
| 62 | final Window parent, | |
| 63 | final AlertType alertType, | |
| 64 | final Notification message ) { | |
| 65 | final var alert = new Alert( alertType ); | |
| 66 | ||
| 67 | alert.setDialogPane( new ButtonOrderPane() ); | |
| 68 | alert.setTitle( message.getTitle() ); | |
| 69 | alert.setHeaderText( null ); | |
| 70 | alert.setContentText( message.getContent() ); | |
| 71 | alert.initOwner( parent ); | |
| 72 | alert.setGraphic( ICON_DIALOG_NODE ); | |
| 73 | ||
| 74 | return alert; | |
| 75 | } | |
| 76 | } | |
| 1 | 77 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.service.impl; | |
| 3 | ||
| 4 | import com.keenwrite.service.Settings; | |
| 5 | import org.apache.commons.configuration2.PropertiesConfiguration; | |
| 6 | import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler; | |
| 7 | import org.apache.commons.configuration2.convert.ListDelimiterHandler; | |
| 8 | ||
| 9 | import java.io.InputStreamReader; | |
| 10 | import java.net.URL; | |
| 11 | import java.nio.charset.Charset; | |
| 12 | import java.util.Iterator; | |
| 13 | import java.util.List; | |
| 14 | ||
| 15 | import static com.keenwrite.constants.Constants.PATH_PROPERTIES_SETTINGS; | |
| 16 | ||
| 17 | /** | |
| 18 | * Responsible for loading settings that help avoid hard-coded assumptions. | |
| 19 | */ | |
| 20 | public final class DefaultSettings implements Settings { | |
| 21 | ||
| 22 | private static final char VALUE_SEPARATOR = ','; | |
| 23 | ||
| 24 | private final PropertiesConfiguration mProperties = createProperties(); | |
| 25 | ||
| 26 | public DefaultSettings() { | |
| 27 | } | |
| 28 | ||
| 29 | /** | |
| 30 | * Returns the value of a string property. | |
| 31 | * | |
| 32 | * @param property The property key. | |
| 33 | * @param defaultValue The value to return if no property key has been set. | |
| 34 | * @return The property key value, or defaultValue when no key found. | |
| 35 | */ | |
| 36 | @Override | |
| 37 | public String getSetting( final String property, final String defaultValue ) { | |
| 38 | return getSettings().getString( property, defaultValue ); | |
| 39 | } | |
| 40 | ||
| 41 | /** | |
| 42 | * Returns the value of a string property. | |
| 43 | * | |
| 44 | * @param property The property key. | |
| 45 | * @param defaultValue The value to return if no property key has been set. | |
| 46 | * @return The property key value, or defaultValue when no key found. | |
| 47 | */ | |
| 48 | @Override | |
| 49 | public int getSetting( final String property, final int defaultValue ) { | |
| 50 | return getSettings().getInt( property, defaultValue ); | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Convert the generic list of property objects into strings. | |
| 55 | * | |
| 56 | * @param property The property value to coerce. | |
| 57 | * @param defaults The defaults values to use should the property be unset. | |
| 58 | * @return The list of properties coerced from objects to strings. | |
| 59 | */ | |
| 60 | @Override | |
| 61 | public List<String> getStringSettingList( | |
| 62 | final String property, final List<String> defaults ) { | |
| 63 | return getSettings().getList( String.class, property, defaults ); | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Convert a list of property objects into strings, with no default value. | |
| 68 | * | |
| 69 | * @param property The property value to coerce. | |
| 70 | * @return The list of properties coerced from objects to strings. | |
| 71 | */ | |
| 72 | @Override | |
| 73 | public List<String> getStringSettingList( final String property ) { | |
| 74 | return getStringSettingList( property, null ); | |
| 75 | } | |
| 76 | ||
| 77 | /** | |
| 78 | * Returns a list of property names that begin with the given prefix. | |
| 79 | * | |
| 80 | * @param prefix The prefix to compare against each property name. | |
| 81 | * @return The list of property names that have the given prefix. | |
| 82 | */ | |
| 83 | @Override | |
| 84 | public Iterator<String> getKeys( final String prefix ) { | |
| 85 | return getSettings().getKeys( prefix ); | |
| 86 | } | |
| 87 | ||
| 88 | private PropertiesConfiguration createProperties() { | |
| 89 | final var url = getPropertySource(); | |
| 90 | final var configuration = new PropertiesConfiguration(); | |
| 91 | ||
| 92 | if( url != null ) { | |
| 93 | try( final var reader = new InputStreamReader( | |
| 94 | url.openStream(), getDefaultEncoding() ) ) { | |
| 95 | configuration.setListDelimiterHandler( createListDelimiterHandler() ); | |
| 96 | configuration.read( reader ); | |
| 97 | } catch( final Exception ex ) { | |
| 98 | throw new RuntimeException( ex ); | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | return configuration; | |
| 103 | } | |
| 104 | ||
| 105 | protected Charset getDefaultEncoding() { | |
| 106 | return Charset.defaultCharset(); | |
| 107 | } | |
| 108 | ||
| 109 | protected ListDelimiterHandler createListDelimiterHandler() { | |
| 110 | return new DefaultListDelimiterHandler( VALUE_SEPARATOR ); | |
| 111 | } | |
| 112 | ||
| 113 | private URL getPropertySource() { | |
| 114 | return DefaultSettings.class.getResource( PATH_PROPERTIES_SETTINGS ); | |
| 115 | } | |
| 116 | ||
| 117 | private PropertiesConfiguration getSettings() { | |
| 118 | return mProperties; | |
| 119 | } | |
| 120 | } | |
| 1 | 121 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.sigils; | |
| 3 | ||
| 4 | import static com.keenwrite.sigils.YamlSigilOperator.KEY_SEPARATOR_DEF; | |
| 5 | ||
| 6 | /** | |
| 7 | * Brackets variable names between {@link #PREFIX} and {@link #SUFFIX} sigils. | |
| 8 | */ | |
| 9 | public final class RSigilOperator extends SigilOperator { | |
| 10 | private static final char KEY_SEPARATOR_R = '$'; | |
| 11 | ||
| 12 | public static final String PREFIX = "`r#"; | |
| 13 | public static final char SUFFIX = '`'; | |
| 14 | ||
| 15 | /** | |
| 16 | * Definition variables are inserted into the document before R variables, | |
| 17 | * so this is required to reformat the definition variable suitable for R. | |
| 18 | */ | |
| 19 | private final SigilOperator mAntecedent; | |
| 20 | ||
| 21 | /** | |
| 22 | * Constructs a new {@link RSigilOperator} capable of wrapping tokens around | |
| 23 | * variable names (keys). | |
| 24 | * | |
| 25 | * @param tokens The starting and ending tokens. | |
| 26 | * @param antecedent The operator to use to undo any previous entokenizing. | |
| 27 | */ | |
| 28 | public RSigilOperator( final Tokens tokens, final SigilOperator antecedent ) { | |
| 29 | super( tokens ); | |
| 30 | ||
| 31 | mAntecedent = antecedent; | |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * Returns the given string with backticks prepended and appended. The | |
| 36 | * | |
| 37 | * @param key The string to adorn with R token delimiters. | |
| 38 | * @return PREFIX + delimiterBegan + variableName + delimiterEnded + SUFFIX. | |
| 39 | */ | |
| 40 | @Override | |
| 41 | public String apply( final String key ) { | |
| 42 | assert key != null; | |
| 43 | return PREFIX + getBegan() + key + getEnded() + SUFFIX; | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Transforms a definition key (bracketed by token delimiters) into the | |
| 48 | * expected format for an R variable key name. | |
| 49 | * <p> | |
| 50 | * The algorithm to entoken a definition name is faster than | |
| 51 | * {@link String#replace(char, char)}. Faster still would be to cache the | |
| 52 | * values, but that would mean managing the cache when the user changes | |
| 53 | * the beginning and ending of the R delimiters. This code gives about a | |
| 54 | * 2% performance boost when scrolling using cursor keys. After the JIT | |
| 55 | * warms up, this super-minor bottleneck vanishes. | |
| 56 | * </p> | |
| 57 | * | |
| 58 | * @param key The variable name to transform, can be empty but not null. | |
| 59 | * @return The transformed variable name. | |
| 60 | */ | |
| 61 | public String entoken( final String key ) { | |
| 62 | final var detokened = new StringBuilder( key.length() ); | |
| 63 | detokened.append( "v$" ); | |
| 64 | detokened.append( mAntecedent.detoken( key ) ); | |
| 65 | ||
| 66 | // The 3 is for "v$X" where X cannot be a period. | |
| 67 | for( int i = detokened.length() - 1; i >= 3; i-- ) { | |
| 68 | if( detokened.charAt( i ) == KEY_SEPARATOR_DEF ) { | |
| 69 | detokened.setCharAt( i, KEY_SEPARATOR_R ); | |
| 70 | } | |
| 71 | } | |
| 72 | ||
| 73 | return detokened.toString(); | |
| 74 | } | |
| 75 | } | |
| 1 | 76 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.sigils; | |
| 3 | ||
| 4 | import java.util.function.UnaryOperator; | |
| 5 | ||
| 6 | /** | |
| 7 | * Responsible for updating definition keys to use a machine-readable format | |
| 8 | * corresponding to the type of file being edited. This changes a definition | |
| 9 | * key name based on some criteria determined by the factory that creates | |
| 10 | * implementations of this interface. | |
| 11 | */ | |
| 12 | public abstract class SigilOperator implements UnaryOperator<String> { | |
| 13 | private final Tokens mTokens; | |
| 14 | ||
| 15 | SigilOperator( final Tokens tokens ) { | |
| 16 | mTokens = tokens; | |
| 17 | } | |
| 18 | ||
| 19 | /** | |
| 20 | * Removes start and stop definition key delimiters from the given key. This | |
| 21 | * method does not check for delimiters, only that there are sufficient | |
| 22 | * characters to remove from either end of the given key. | |
| 23 | * | |
| 24 | * @param key The key adorned with start and stop tokens. | |
| 25 | * @return The given key with the delimiters removed. | |
| 26 | */ | |
| 27 | String detoken( final String key ) { | |
| 28 | return key; | |
| 29 | } | |
| 30 | ||
| 31 | String getBegan() { | |
| 32 | return mTokens.getBegan(); | |
| 33 | } | |
| 34 | ||
| 35 | String getEnded() { | |
| 36 | return mTokens.getEnded(); | |
| 37 | } | |
| 38 | ||
| 39 | /** | |
| 40 | * Wraps the given key in the began and ended tokens. This may perform any | |
| 41 | * preprocessing necessary to ensure the transformation happens. | |
| 42 | * | |
| 43 | * @param key The variable name to transform. | |
| 44 | * @return The given key with tokens to delimit it (from the edited text). | |
| 45 | */ | |
| 46 | public abstract String entoken( final String key ); | |
| 47 | } | |
| 1 | 48 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.sigils; | |
| 3 | ||
| 4 | import javafx.beans.property.StringProperty; | |
| 5 | ||
| 6 | import java.util.AbstractMap.SimpleImmutableEntry; | |
| 7 | ||
| 8 | /** | |
| 9 | * Convenience class for pairing a start and an end sigil together. | |
| 10 | */ | |
| 11 | public final class Tokens | |
| 12 | extends SimpleImmutableEntry<StringProperty, StringProperty> { | |
| 13 | ||
| 14 | /** | |
| 15 | * Associates a new key-value pair. | |
| 16 | * | |
| 17 | * @param began The starting sigil. | |
| 18 | * @param ended The ending sigil. | |
| 19 | */ | |
| 20 | public Tokens( final StringProperty began, final StringProperty ended ) { | |
| 21 | super( began, ended ); | |
| 22 | } | |
| 23 | ||
| 24 | /** | |
| 25 | * @return The opening sigil token. | |
| 26 | */ | |
| 27 | public String getBegan() { | |
| 28 | return getKey().get(); | |
| 29 | } | |
| 30 | ||
| 31 | /** | |
| 32 | * @return The closing sigil token, or the empty string if none set. | |
| 33 | */ | |
| 34 | public String getEnded() { | |
| 35 | return getValue().get(); | |
| 36 | } | |
| 37 | } | |
| 1 | 38 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.sigils; | |
| 3 | ||
| 4 | /** | |
| 5 | * Brackets definition keys with token delimiters. | |
| 6 | */ | |
| 7 | public final class YamlSigilOperator extends SigilOperator { | |
| 8 | public static final char KEY_SEPARATOR_DEF = '.'; | |
| 9 | ||
| 10 | public YamlSigilOperator( final Tokens tokens ) { | |
| 11 | super( tokens ); | |
| 12 | } | |
| 13 | ||
| 14 | /** | |
| 15 | * Returns the given {@link String} verbatim because variables in YAML | |
| 16 | * documents and plain Markdown documents already have the appropriate | |
| 17 | * tokenizable syntax wrapped around the text. | |
| 18 | * | |
| 19 | * @param key Returned verbatim. | |
| 20 | */ | |
| 21 | @Override | |
| 22 | public String apply( final String key ) { | |
| 23 | return key; | |
| 24 | } | |
| 25 | ||
| 26 | /** | |
| 27 | * Adds delimiters to the given key. | |
| 28 | * | |
| 29 | * @param key The key to adorn with start and stop definition tokens. | |
| 30 | * @return The given key bracketed by definition token symbols. | |
| 31 | */ | |
| 32 | public String entoken( final String key ) { | |
| 33 | assert key != null; | |
| 34 | return getBegan() + key + getEnded(); | |
| 35 | } | |
| 36 | ||
| 37 | /** | |
| 38 | * Removes start and stop definition key delimiters from the given key. | |
| 39 | * | |
| 40 | * @param key The key that may have start and stop tokens. | |
| 41 | * @return The given key with the delimiters removed. | |
| 42 | */ | |
| 43 | public String detoken( final String key ) { | |
| 44 | final var began = getBegan(); | |
| 45 | final var ended = getEnded(); | |
| 46 | final int bLength = began.length(); | |
| 47 | final int eLength = ended.length(); | |
| 48 | final var bIndex = key.indexOf( began ); | |
| 49 | final var eIndex = key.indexOf( ended, bIndex ); | |
| 50 | final var kLength = key.length(); | |
| 51 | ||
| 52 | return key.substring( | |
| 53 | bIndex == -1 ? 0 : bLength, eIndex == -1 ? kLength : kLength - eLength ); | |
| 54 | } | |
| 55 | } | |
| 1 | 56 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.spelling.api; | |
| 3 | ||
| 4 | import java.util.function.BiConsumer; | |
| 5 | ||
| 6 | /** | |
| 7 | * Represents an operation that accepts two input arguments and returns no | |
| 8 | * result. Unlike most other functional interfaces, this class is expected to | |
| 9 | * operate via side-effects. | |
| 10 | * <p> | |
| 11 | * This is used instead of a {@link BiConsumer} to avoid autoboxing. | |
| 12 | * </p> | |
| 13 | */ | |
| 14 | @FunctionalInterface | |
| 15 | public interface SpellCheckListener { | |
| 16 | ||
| 17 | /** | |
| 18 | * Performs an operation on the given arguments. | |
| 19 | * | |
| 20 | * @param text The text associated with a beginning and ending offset. | |
| 21 | * @param beganOffset A starting offset, used as an index into a string. | |
| 22 | * @param endedOffset An ending offset, which should equal text.length() + | |
| 23 | * beganOffset. | |
| 24 | */ | |
| 25 | void accept( String text, int beganOffset, int endedOffset ); | |
| 26 | } | |
| 1 | 27 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.spelling.api; | |
| 3 | ||
| 4 | import java.util.List; | |
| 5 | ||
| 6 | /** | |
| 7 | * Defines the responsibilities for a spell checking API. The intention is | |
| 8 | * to allow different spell checking implementations to be used by the | |
| 9 | * application, such as SymSpell and LinSpell. | |
| 10 | */ | |
| 11 | public interface SpellChecker { | |
| 12 | ||
| 13 | /** | |
| 14 | * Answers whether the given lexeme, in whole, is found in the lexicon. The | |
| 15 | * lexicon lookup is performed case-insensitively. This method should be | |
| 16 | * used instead of {@link #suggestions(String, int)} for performance reasons. | |
| 17 | * | |
| 18 | * @param lexeme The word to check for correctness. | |
| 19 | * @return {@code true} if the lexeme is in the lexicon. | |
| 20 | */ | |
| 21 | boolean inLexicon( String lexeme ); | |
| 22 | ||
| 23 | /** | |
| 24 | * Gets a list of spelling corrections for the given lexeme. | |
| 25 | * | |
| 26 | * @param lexeme A word to check for correctness that's not in the lexicon. | |
| 27 | * @param count The maximum number of alternatives to return. | |
| 28 | * @return A list of words in the lexicon that are similar to the given | |
| 29 | * lexeme. | |
| 30 | */ | |
| 31 | List<String> suggestions( String lexeme, int count ); | |
| 32 | ||
| 33 | /** | |
| 34 | * Iterates over the given text, emitting starting and ending offsets into | |
| 35 | * the text for every word that is missing from the lexicon. | |
| 36 | * | |
| 37 | * @param text The text to check for words missing from the lexicon. | |
| 38 | * @param consumer Every missing word emits a message with the starting | |
| 39 | * and ending offset into the text where said word is found. | |
| 40 | */ | |
| 41 | void proofread( String text, SpellCheckListener consumer ); | |
| 42 | } | |
| 1 | 43 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | ||
| 3 | /** | |
| 4 | * This package contains interfaces for spell checking implementations. | |
| 5 | */ | |
| 6 | package com.keenwrite.spelling.api; | |
| 1 | 7 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.spelling.impl; | |
| 3 | ||
| 4 | import com.keenwrite.spelling.api.SpellCheckListener; | |
| 5 | import com.keenwrite.spelling.api.SpellChecker; | |
| 6 | ||
| 7 | import java.util.List; | |
| 8 | ||
| 9 | /** | |
| 10 | * Responsible for spell checking in the event that a real spell checking | |
| 11 | * implementation cannot be created (for any reason). Does not perform any | |
| 12 | * spell checking and indicates that any given lexeme is in the lexicon. | |
| 13 | */ | |
| 14 | public final class PermissiveSpeller implements SpellChecker { | |
| 15 | /** | |
| 16 | * Returns {@code true}, ignoring the given word. | |
| 17 | * | |
| 18 | * @param ignored Unused. | |
| 19 | * @return {@code true} | |
| 20 | */ | |
| 21 | @Override | |
| 22 | public boolean inLexicon( final String ignored ) { | |
| 23 | return true; | |
| 24 | } | |
| 25 | ||
| 26 | /** | |
| 27 | * Returns an array with the given lexeme. | |
| 28 | * | |
| 29 | * @param lexeme The word to return. | |
| 30 | * @param ignored Unused. | |
| 31 | * @return A suggestion list containing the given lexeme. | |
| 32 | */ | |
| 33 | @Override | |
| 34 | public List<String> suggestions( final String lexeme, final int ignored ) { | |
| 35 | return List.of( lexeme ); | |
| 36 | } | |
| 37 | ||
| 38 | /** | |
| 39 | * Performs no action. | |
| 40 | * | |
| 41 | * @param text Unused. | |
| 42 | * @param ignored Uncalled. | |
| 43 | */ | |
| 44 | @Override | |
| 45 | public void proofread( | |
| 46 | final String text, final SpellCheckListener ignored ) { | |
| 47 | } | |
| 48 | } | |
| 1 | 49 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.spelling.impl; | |
| 3 | ||
| 4 | import com.keenwrite.exceptions.MissingFileException; | |
| 5 | import com.keenwrite.spelling.api.SpellCheckListener; | |
| 6 | import com.keenwrite.spelling.api.SpellChecker; | |
| 7 | import io.gitlab.rxp90.jsymspell.SuggestItem; | |
| 8 | import io.gitlab.rxp90.jsymspell.SymSpell; | |
| 9 | import io.gitlab.rxp90.jsymspell.SymSpellBuilder; | |
| 10 | ||
| 11 | import java.io.BufferedReader; | |
| 12 | import java.io.InputStreamReader; | |
| 13 | import java.text.BreakIterator; | |
| 14 | import java.util.ArrayList; | |
| 15 | import java.util.Collection; | |
| 16 | import java.util.List; | |
| 17 | import java.util.stream.Collectors; | |
| 18 | ||
| 19 | import static com.keenwrite.constants.Constants.LEXICONS_DIRECTORY; | |
| 20 | import static com.keenwrite.events.StatusEvent.clue; | |
| 21 | import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity; | |
| 22 | import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity.ALL; | |
| 23 | import static io.gitlab.rxp90.jsymspell.SymSpell.Verbosity.CLOSEST; | |
| 24 | import static java.lang.Character.isLetter; | |
| 25 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 26 | ||
| 27 | /** | |
| 28 | * Responsible for spell checking using {@link SymSpell}. | |
| 29 | */ | |
| 30 | public class SymSpellSpeller implements SpellChecker { | |
| 31 | private final BreakIterator mBreakIterator = BreakIterator.getWordInstance(); | |
| 32 | private final SymSpell mSymSpell; | |
| 33 | ||
| 34 | /** | |
| 35 | * Creates a new spellchecker for a lexicon of words found in the specified | |
| 36 | * file. | |
| 37 | * | |
| 38 | * @param filename Lexicon language file (e.g., "en.txt"). | |
| 39 | * @return An instance of {@link SpellChecker} that can check if a word | |
| 40 | * is correct and suggest alternatives, or {@link PermissiveSpeller} if the | |
| 41 | * lexicon cannot be loaded. | |
| 42 | */ | |
| 43 | public static SpellChecker forLexicon( final String filename ) { | |
| 44 | try { | |
| 45 | final var lexicon = readLexicon( filename ); | |
| 46 | return SymSpellSpeller.forLexicon( lexicon ); | |
| 47 | } catch( final Exception ex ) { | |
| 48 | clue( ex ); | |
| 49 | return new PermissiveSpeller(); | |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | private static SpellChecker forLexicon( | |
| 54 | final Collection<String> lexiconWords ) { | |
| 55 | assert lexiconWords != null && !lexiconWords.isEmpty(); | |
| 56 | ||
| 57 | final var builder = new SymSpellBuilder() | |
| 58 | .setLexiconWords( lexiconWords ); | |
| 59 | ||
| 60 | return new SymSpellSpeller( builder.build() ); | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * Prevent direct instantiation so that only the {@link SpellChecker} | |
| 65 | * interface | |
| 66 | * is available. | |
| 67 | * | |
| 68 | * @param symSpell The implementation-specific spell checker. | |
| 69 | */ | |
| 70 | private SymSpellSpeller( final SymSpell symSpell ) { | |
| 71 | mSymSpell = symSpell; | |
| 72 | } | |
| 73 | ||
| 74 | @Override | |
| 75 | public boolean inLexicon( final String lexeme ) { | |
| 76 | return lookup( lexeme, CLOSEST ).size() == 1; | |
| 77 | } | |
| 78 | ||
| 79 | @Override | |
| 80 | public List<String> suggestions( final String lexeme, int count ) { | |
| 81 | final List<String> result = new ArrayList<>( count ); | |
| 82 | ||
| 83 | for( final var item : lookup( lexeme, ALL ) ) { | |
| 84 | if( count-- > 0 ) { | |
| 85 | result.add( item.getSuggestion() ); | |
| 86 | } | |
| 87 | else { | |
| 88 | break; | |
| 89 | } | |
| 90 | } | |
| 91 | ||
| 92 | return result; | |
| 93 | } | |
| 94 | ||
| 95 | @Override | |
| 96 | public void proofread( | |
| 97 | final String text, final SpellCheckListener consumer ) { | |
| 98 | assert text != null; | |
| 99 | assert consumer != null; | |
| 100 | ||
| 101 | mBreakIterator.setText( text ); | |
| 102 | ||
| 103 | int boundaryIndex = mBreakIterator.first(); | |
| 104 | int previousIndex = 0; | |
| 105 | ||
| 106 | while( boundaryIndex != BreakIterator.DONE ) { | |
| 107 | final var lex = | |
| 108 | text.substring( previousIndex, boundaryIndex ).toLowerCase(); | |
| 109 | ||
| 110 | // Get the lexeme for the possessive. | |
| 111 | final var pos = lex.endsWith( "'s" ) || lex.endsWith( "’s" ); | |
| 112 | final var lexeme = pos ? lex.substring( 0, lex.length() - 2 ) : lex; | |
| 113 | ||
| 114 | if( isWord( lexeme ) && !inLexicon( lexeme ) ) { | |
| 115 | consumer.accept( lex, previousIndex, boundaryIndex ); | |
| 116 | } | |
| 117 | ||
| 118 | previousIndex = boundaryIndex; | |
| 119 | boundaryIndex = mBreakIterator.next(); | |
| 120 | } | |
| 121 | } | |
| 122 | ||
| 123 | @SuppressWarnings( "SameParameterValue" ) | |
| 124 | private static Collection<String> readLexicon( final String filename ) | |
| 125 | throws Exception { | |
| 126 | final var path = '/' + LEXICONS_DIRECTORY + '/' + filename; | |
| 127 | ||
| 128 | try( final var resource = | |
| 129 | SymSpellSpeller.class.getResourceAsStream( path ) ) { | |
| 130 | if( resource == null ) { | |
| 131 | throw new MissingFileException( path ); | |
| 132 | } | |
| 133 | ||
| 134 | try( final var isr = new InputStreamReader( resource, UTF_8 ); | |
| 135 | final var reader = new BufferedReader( isr ) ) { | |
| 136 | return reader.lines().collect( Collectors.toList() ); | |
| 137 | } | |
| 138 | } | |
| 139 | } | |
| 140 | ||
| 141 | /** | |
| 142 | * Answers whether the given string is likely a word by checking the first | |
| 143 | * character. | |
| 144 | * | |
| 145 | * @param word The word to check. | |
| 146 | * @return {@code true} if the word begins with a letter. | |
| 147 | */ | |
| 148 | private boolean isWord( final String word ) { | |
| 149 | return !word.isEmpty() && isLetter( word.charAt( 0 ) ); | |
| 150 | } | |
| 151 | ||
| 152 | /** | |
| 153 | * Returns a list of {@link SuggestItem} instances that provide alternative | |
| 154 | * spellings for the given lexeme. | |
| 155 | * | |
| 156 | * @param lexeme A word to look up in the lexicon. | |
| 157 | * @param v Influences the number of results returned. | |
| 158 | * @return Alternative lexemes. | |
| 159 | */ | |
| 160 | private List<SuggestItem> lookup( final String lexeme, final Verbosity v ) { | |
| 161 | return mSymSpell.lookup( lexeme, v ); | |
| 162 | } | |
| 163 | } | |
| 1 | 164 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.spelling.impl; | |
| 3 | ||
| 4 | import com.keenwrite.spelling.api.SpellCheckListener; | |
| 5 | import com.keenwrite.spelling.api.SpellChecker; | |
| 6 | import com.vladsch.flexmark.parser.Parser; | |
| 7 | import com.vladsch.flexmark.util.ast.NodeVisitor; | |
| 8 | import com.vladsch.flexmark.util.ast.VisitHandler; | |
| 9 | import org.fxmisc.richtext.StyleClassedTextArea; | |
| 10 | import org.fxmisc.richtext.model.StyleSpansBuilder; | |
| 11 | ||
| 12 | import java.util.Collection; | |
| 13 | import java.util.concurrent.atomic.AtomicInteger; | |
| 14 | ||
| 15 | import static com.keenwrite.spelling.impl.SymSpellSpeller.forLexicon; | |
| 16 | import static java.util.Collections.emptyList; | |
| 17 | import static java.util.Collections.singleton; | |
| 18 | import static org.fxmisc.richtext.model.TwoDimensional.Bias.Forward; | |
| 19 | ||
| 20 | /** | |
| 21 | * Responsible for checking the spelling of a document being edited. | |
| 22 | */ | |
| 23 | public final class TextEditorSpeller { | |
| 24 | /** | |
| 25 | * Only load the dictionary into memory once, because it's huge. | |
| 26 | */ | |
| 27 | private static final SpellChecker sSpellChecker = forLexicon( "en.txt" ); | |
| 28 | ||
| 29 | private final Parser mParser; | |
| 30 | ||
| 31 | public TextEditorSpeller() { | |
| 32 | mParser = Parser.builder().build(); | |
| 33 | } | |
| 34 | ||
| 35 | /** | |
| 36 | * Delegates to {@link #spellcheck(StyleClassedTextArea, String, int)}. | |
| 37 | * call to spell check the entire document. | |
| 38 | */ | |
| 39 | public void checkDocument( final StyleClassedTextArea editor ) { | |
| 40 | spellcheck( editor, editor.getText(), -1 ); | |
| 41 | } | |
| 42 | ||
| 43 | /** | |
| 44 | * Listen for changes to the any particular paragraph and perform a quick | |
| 45 | * spell check upon it. The style classes in the editor will be changed to | |
| 46 | * mark any spelling mistakes in the paragraph. The user may then interact | |
| 47 | * with any misspelled word (i.e., any piece of text that is marked) to | |
| 48 | * revise the spelling. | |
| 49 | * | |
| 50 | * @param editor The text area containing paragraphs to spellcheck. | |
| 51 | */ | |
| 52 | public void checkParagraphs( final StyleClassedTextArea editor ) { | |
| 53 | // Use the plain text changes so that notifications of style changes | |
| 54 | // are suppressed. Checking against the identity ensures that only | |
| 55 | // new text additions or deletions trigger proofreading. | |
| 56 | editor.plainTextChanges() | |
| 57 | .filter( p -> !p.isIdentity() ).subscribe( change -> { | |
| 58 | ||
| 59 | // Check current paragraph; the whole document was checked upon opening. | |
| 60 | final var offset = change.getPosition(); | |
| 61 | final var position = editor.offsetToPosition( offset, Forward ); | |
| 62 | final var paraId = position.getMajor(); | |
| 63 | final var paragraph = editor.getParagraph( paraId ); | |
| 64 | final var text = paragraph.getText(); | |
| 65 | ||
| 66 | // Prevent doubling-up styles. | |
| 67 | editor.clearStyle( paraId ); | |
| 68 | ||
| 69 | spellcheck( editor, text, paraId ); | |
| 70 | } ); | |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Spellchecks a subset of the entire document. | |
| 75 | * | |
| 76 | * @param text Look up words for this text in the lexicon. | |
| 77 | * @param paraId Set to -1 to apply resulting style spans to the entire | |
| 78 | * text. | |
| 79 | */ | |
| 80 | private void spellcheck( | |
| 81 | final StyleClassedTextArea editor, final String text, final int paraId ) { | |
| 82 | final var builder = new StyleSpansBuilder<Collection<String>>(); | |
| 83 | final var runningIndex = new AtomicInteger( 0 ); | |
| 84 | ||
| 85 | // The text nodes must be relayed through a contextual "visitor" that | |
| 86 | // can return text in chunks with correlative offsets into the string. | |
| 87 | // This allows Markdown, R Markdown, XML, and R XML documents to return | |
| 88 | // sets of words to check. | |
| 89 | ||
| 90 | final var node = mParser.parse( text ); | |
| 91 | final var visitor = new TextVisitor( ( visited, bIndex, eIndex ) -> { | |
| 92 | // Treat hyphenated compound words as individual words. | |
| 93 | final var check = visited.replace( '-', ' ' ); | |
| 94 | ||
| 95 | sSpellChecker.proofread( check, ( misspelled, prevIndex, currIndex ) -> { | |
| 96 | prevIndex += bIndex; | |
| 97 | currIndex += bIndex; | |
| 98 | ||
| 99 | // Clear styling between lexiconically absent words. | |
| 100 | builder.add( emptyList(), prevIndex - runningIndex.get() ); | |
| 101 | builder.add( singleton( "spelling" ), currIndex - prevIndex ); | |
| 102 | runningIndex.set( currIndex ); | |
| 103 | } ); | |
| 104 | } ); | |
| 105 | ||
| 106 | visitor.visit( node ); | |
| 107 | ||
| 108 | // If the running index was set, at least one word triggered the listener. | |
| 109 | if( runningIndex.get() > 0 ) { | |
| 110 | // Clear styling after the last lexiconically absent word. | |
| 111 | builder.add( emptyList(), text.length() - runningIndex.get() ); | |
| 112 | ||
| 113 | final var spans = builder.create(); | |
| 114 | ||
| 115 | if( paraId >= 0 ) { | |
| 116 | editor.setStyleSpans( paraId, 0, spans ); | |
| 117 | } | |
| 118 | else { | |
| 119 | editor.setStyleSpans( 0, spans ); | |
| 120 | } | |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 | /** | |
| 125 | * TODO: #59 -- Replace with generic interface; provide Markdown/XML | |
| 126 | * implementations. | |
| 127 | */ | |
| 128 | private static final class TextVisitor { | |
| 129 | private final NodeVisitor mVisitor = new NodeVisitor( new VisitHandler<>( | |
| 130 | com.vladsch.flexmark.ast.Text.class, this::visit ) | |
| 131 | ); | |
| 132 | ||
| 133 | private final SpellCheckListener mConsumer; | |
| 134 | ||
| 135 | public TextVisitor( final SpellCheckListener consumer ) { | |
| 136 | mConsumer = consumer; | |
| 137 | } | |
| 138 | ||
| 139 | private void visit( final com.vladsch.flexmark.util.ast.Node node ) { | |
| 140 | if( node instanceof com.vladsch.flexmark.ast.Text ) { | |
| 141 | mConsumer.accept( node.getChars().toString(), | |
| 142 | node.getStartOffset(), | |
| 143 | node.getEndOffset() ); | |
| 144 | } | |
| 145 | ||
| 146 | mVisitor.visitChildren( node ); | |
| 147 | } | |
| 148 | } | |
| 149 | } | |
| 1 | 150 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | ||
| 3 | /** | |
| 4 | * This package contains classes for spell checking implementations. | |
| 5 | */ | |
| 6 | package com.keenwrite.spelling.impl; | |
| 1 | 7 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.typesetting; | |
| 3 | ||
| 4 | import com.keenwrite.io.SysFile; | |
| 5 | import com.keenwrite.preferences.Workspace; | |
| 6 | import com.keenwrite.util.BoundedCache; | |
| 7 | ||
| 8 | import java.io.*; | |
| 9 | import java.nio.file.NoSuchFileException; | |
| 10 | import java.nio.file.Path; | |
| 11 | import java.util.ArrayList; | |
| 12 | import java.util.List; | |
| 13 | import java.util.Map; | |
| 14 | import java.util.Scanner; | |
| 15 | import java.util.concurrent.Callable; | |
| 16 | import java.util.regex.Pattern; | |
| 17 | ||
| 18 | import static com.keenwrite.constants.Constants.DEFAULT_DIRECTORY; | |
| 19 | import static com.keenwrite.events.StatusEvent.clue; | |
| 20 | import static com.keenwrite.preferences.WorkspaceKeys.*; | |
| 21 | import static java.lang.ProcessBuilder.Redirect.DISCARD; | |
| 22 | import static java.lang.String.format; | |
| 23 | import static java.lang.System.currentTimeMillis; | |
| 24 | import static java.lang.System.getProperty; | |
| 25 | import static java.nio.file.Files.*; | |
| 26 | import static java.util.Arrays.asList; | |
| 27 | import static java.util.concurrent.TimeUnit.*; | |
| 28 | import static org.apache.commons.io.FilenameUtils.removeExtension; | |
| 29 | ||
| 30 | /** | |
| 31 | * Responsible for invoking an executable to typeset text. This will | |
| 32 | * construct suitable command-line arguments to invoke the typesetting engine. | |
| 33 | */ | |
| 34 | public class Typesetter { | |
| 35 | private static final SysFile TYPESETTER = new SysFile( "mtxrun" ); | |
| 36 | ||
| 37 | private final Workspace mWorkspace; | |
| 38 | ||
| 39 | /** | |
| 40 | * Creates a new {@link Typesetter} instance capable of configuring the | |
| 41 | * typesetter used to generate a typeset document. | |
| 42 | */ | |
| 43 | public Typesetter( final Workspace workspace ) { | |
| 44 | mWorkspace = workspace; | |
| 45 | } | |
| 46 | ||
| 47 | public static boolean canRun() { | |
| 48 | return TYPESETTER.canRun(); | |
| 49 | } | |
| 50 | ||
| 51 | /** | |
| 52 | * This will typeset the document using a new process. The return value only | |
| 53 | * indicates whether the typesetter exists, not whether the typesetting was | |
| 54 | * successful. | |
| 55 | * | |
| 56 | * @param in The input document to typeset. | |
| 57 | * @param out Path to the finished typeset document. | |
| 58 | * @throws IOException If the process could not be started. | |
| 59 | * @throws InterruptedException If the process was killed. | |
| 60 | * @throws TypesetterNotFoundException When no typesetter is along the PATH. | |
| 61 | */ | |
| 62 | public void typeset( final Path in, final Path out ) | |
| 63 | throws IOException, InterruptedException, TypesetterNotFoundException { | |
| 64 | if( TYPESETTER.canRun() ) { | |
| 65 | clue( "Main.status.typeset.began", out ); | |
| 66 | final var task = new TypesetTask( in, out ); | |
| 67 | final var time = currentTimeMillis(); | |
| 68 | final var success = task.typeset(); | |
| 69 | ||
| 70 | clue( "Main.status.typeset.ended." + (success ? "success" : "failure"), | |
| 71 | out, since( time ) | |
| 72 | ); | |
| 73 | } | |
| 74 | else { | |
| 75 | throw new TypesetterNotFoundException( TYPESETTER.toString() ); | |
| 76 | } | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Calculates the time that has elapsed from the current time to the | |
| 81 | * given moment in time. | |
| 82 | * | |
| 83 | * @param start The starting time, which should be before the current time. | |
| 84 | * @return A human-readable formatted time. | |
| 85 | * @see #asElapsed(long) | |
| 86 | */ | |
| 87 | private static String since( final long start ) { | |
| 88 | return asElapsed( currentTimeMillis() - start ); | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Converts an elapsed time to a human-readable format (hours, minutes, | |
| 93 | * seconds, and milliseconds). | |
| 94 | * | |
| 95 | * @param elapsed An elapsed time, in milliseconds. | |
| 96 | * @return Human-readable elapsed time. | |
| 97 | */ | |
| 98 | private static String asElapsed( final long elapsed ) { | |
| 99 | final var hours = MILLISECONDS.toHours( elapsed ); | |
| 100 | final var eHours = elapsed - HOURS.toMillis( hours ); | |
| 101 | final var minutes = MILLISECONDS.toMinutes( eHours ); | |
| 102 | final var eMinutes = eHours - MINUTES.toMillis( minutes ); | |
| 103 | final var seconds = MILLISECONDS.toSeconds( eMinutes ); | |
| 104 | final var eSeconds = eMinutes - SECONDS.toMillis( seconds ); | |
| 105 | final var milliseconds = MILLISECONDS.toMillis( eSeconds ); | |
| 106 | ||
| 107 | return format( "%02d:%02d:%02d.%03d", | |
| 108 | hours, minutes, seconds, milliseconds ); | |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Launches a task to typeset a document. | |
| 113 | */ | |
| 114 | private class TypesetTask implements Callable<Boolean> { | |
| 115 | private final List<String> mArgs = new ArrayList<>(); | |
| 116 | private final Path mInput; | |
| 117 | private final Path mOutput; | |
| 118 | ||
| 119 | /** | |
| 120 | * Working directory must be set because ConTeXt cannot write the | |
| 121 | * result to an arbitrary location. | |
| 122 | */ | |
| 123 | private final Path mDirectory; | |
| 124 | ||
| 125 | private TypesetTask( final Path input, final Path output ) { | |
| 126 | assert input != null; | |
| 127 | assert output != null; | |
| 128 | ||
| 129 | final var parentDir = output.getParent(); | |
| 130 | mInput = input; | |
| 131 | mOutput = output; | |
| 132 | mDirectory = parentDir == null ? DEFAULT_DIRECTORY : parentDir; | |
| 133 | } | |
| 134 | ||
| 135 | /** | |
| 136 | * Initializes ConTeXt, which means creating the cache directory if it | |
| 137 | * doesn't already exist. The theme entry point must be named 'main.tex'. | |
| 138 | * | |
| 139 | * @return {@code true} if the cache directory exists. | |
| 140 | */ | |
| 141 | private boolean reinitialize() { | |
| 142 | final var filename = mOutput.getFileName(); | |
| 143 | final var themes = getThemesPath(); | |
| 144 | final var theme = getThemesSelection(); | |
| 145 | final var cacheExists = !isEmpty( getCacheDir().toPath() ); | |
| 146 | ||
| 147 | // Ensure invoking multiple times will load the correct arguments. | |
| 148 | mArgs.clear(); | |
| 149 | mArgs.add( TYPESETTER.getName() ); | |
| 150 | ||
| 151 | if( cacheExists ) { | |
| 152 | mArgs.add( "--autogenerate" ); | |
| 153 | mArgs.add( "--script" ); | |
| 154 | mArgs.add( "mtx-context" ); | |
| 155 | mArgs.add( "--batchmode" ); | |
| 156 | mArgs.add( "--nonstopmode" ); | |
| 157 | mArgs.add( "--purgeall" ); | |
| 158 | mArgs.add( "--path='" + Path.of( themes.toString(), theme ) + "'" ); | |
| 159 | mArgs.add( "--environment='main'" ); | |
| 160 | mArgs.add( "--result='" + filename + "'" ); | |
| 161 | mArgs.add( mInput.toString() ); | |
| 162 | ||
| 163 | final var sb = new StringBuilder( 128 ); | |
| 164 | mArgs.forEach( arg -> sb.append( arg ).append( " " ) ); | |
| 165 | clue( sb.toString() ); | |
| 166 | } | |
| 167 | else { | |
| 168 | mArgs.add( "--generate" ); | |
| 169 | } | |
| 170 | ||
| 171 | return cacheExists; | |
| 172 | } | |
| 173 | ||
| 174 | /** | |
| 175 | * Setting {@code TEXMFCACHE} when run on a fresh system fails on first | |
| 176 | * run. If the cache directory doesn't exist, attempt to create it, then | |
| 177 | * call ConTeXt to generate the PDF. This is brittle because if the | |
| 178 | * directory is empty, or not populated with cached data, a false positive | |
| 179 | * will be returned, resulting in no PDF being created. | |
| 180 | * | |
| 181 | * @return {@code true} if the document was typeset successfully. | |
| 182 | * @throws IOException If the process could not be started. | |
| 183 | * @throws InterruptedException If the process was killed. | |
| 184 | */ | |
| 185 | private boolean typeset() throws IOException, InterruptedException { | |
| 186 | return reinitialize() ? call() : call() && reinitialize() && call(); | |
| 187 | } | |
| 188 | ||
| 189 | @Override | |
| 190 | public Boolean call() throws IOException, InterruptedException { | |
| 191 | final var stdout = new BoundedCache<String, String>( 150 ); | |
| 192 | final var builder = new ProcessBuilder( mArgs ); | |
| 193 | builder.directory( mDirectory.toFile() ); | |
| 194 | builder.environment().put( "TEXMFCACHE", getCacheDir().toString() ); | |
| 195 | ||
| 196 | // Without redirecting (or draining) stderr, the command may not | |
| 197 | // terminate successfully. | |
| 198 | builder.redirectError( DISCARD ); | |
| 199 | ||
| 200 | final var process = builder.start(); | |
| 201 | ||
| 202 | // Reading from stdout allows slurping page numbers while generating. | |
| 203 | final var listener = new PaginationListener( | |
| 204 | process.getInputStream(), stdout ); | |
| 205 | listener.start(); | |
| 206 | ||
| 207 | process.waitFor(); | |
| 208 | final var exit = process.exitValue(); | |
| 209 | process.destroy(); | |
| 210 | ||
| 211 | // If there was an error, the typesetter will leave behind log, pdf, and | |
| 212 | // error files. | |
| 213 | if( exit > 0 ) { | |
| 214 | final var xmlName = mInput.getFileName().toString(); | |
| 215 | final var srcName = mOutput.getFileName().toString(); | |
| 216 | final var logName = newExtension( xmlName, ".log" ); | |
| 217 | final var errName = newExtension( xmlName, "-error.log" ); | |
| 218 | final var pdfName = newExtension( xmlName, ".pdf" ); | |
| 219 | final var tuaName = newExtension( xmlName, ".tua" ); | |
| 220 | final var badName = newExtension( srcName, ".log" ); | |
| 221 | ||
| 222 | log( badName ); | |
| 223 | log( logName ); | |
| 224 | log( errName ); | |
| 225 | log( stdout.keySet().stream().toList() ); | |
| 226 | ||
| 227 | // Users may opt to keep these files around for debugging purposes. | |
| 228 | if( autoclean() ) { | |
| 229 | deleteIfExists( logName ); | |
| 230 | deleteIfExists( errName ); | |
| 231 | deleteIfExists( pdfName ); | |
| 232 | deleteIfExists( badName ); | |
| 233 | deleteIfExists( tuaName ); | |
| 234 | } | |
| 235 | } | |
| 236 | ||
| 237 | // Exit value for a successful invocation of the typesetter. This value | |
| 238 | // value is returned when creating the cache on the first run as well as | |
| 239 | // creating PDFs on subsequent runs (after the cache has been created). | |
| 240 | // Users don't care about exit codes, only whether the PDF was generated. | |
| 241 | return exit == 0; | |
| 242 | } | |
| 243 | ||
| 244 | private Path newExtension( final String baseName, final String ext ) { | |
| 245 | return mOutput.resolveSibling( removeExtension( baseName ) + ext ); | |
| 246 | } | |
| 247 | ||
| 248 | /** | |
| 249 | * Fires a status message for each line in the given file. The file format | |
| 250 | * is somewhat machine-readable, but no effort beyond line splitting is | |
| 251 | * made to parse the text. | |
| 252 | * | |
| 253 | * @param path Path to the file containing error messages. | |
| 254 | */ | |
| 255 | private void log( final Path path ) throws IOException { | |
| 256 | if( exists( path ) ) { | |
| 257 | log( readAllLines( path ) ); | |
| 258 | } | |
| 259 | } | |
| 260 | ||
| 261 | private void log( final List<String> lines ) { | |
| 262 | final var splits = new ArrayList<String>( lines.size() * 2 ); | |
| 263 | ||
| 264 | for( final var line : lines ) { | |
| 265 | splits.addAll( asList( line.split( "\\\\n" ) ) ); | |
| 266 | } | |
| 267 | ||
| 268 | clue( splits ); | |
| 269 | } | |
| 270 | ||
| 271 | /** | |
| 272 | * Returns the location of the cache directory. | |
| 273 | * | |
| 274 | * @return A fully qualified path to the location to store temporary | |
| 275 | * files between typesetting runs. | |
| 276 | */ | |
| 277 | private java.io.File getCacheDir() { | |
| 278 | final var temp = getProperty( "java.io.tmpdir" ); | |
| 279 | final var cache = Path.of( temp, "luatex-cache" ); | |
| 280 | return cache.toFile(); | |
| 281 | } | |
| 282 | ||
| 283 | /** | |
| 284 | * Answers whether the given directory is empty. The typesetting software | |
| 285 | * creates a non-empty directory by default. The return value from this | |
| 286 | * method is a proxy to answering whether the typesetter has been run for | |
| 287 | * the first time or not. | |
| 288 | * | |
| 289 | * @param path The directory to check for emptiness. | |
| 290 | * @return {@code true} if the directory is empty. | |
| 291 | */ | |
| 292 | private boolean isEmpty( final Path path ) { | |
| 293 | try( final var stream = newDirectoryStream( path ) ) { | |
| 294 | return !stream.iterator().hasNext(); | |
| 295 | } catch( final NoSuchFileException | FileNotFoundException ex ) { | |
| 296 | // A missing directory means it doesn't exist, ergo is empty. | |
| 297 | return true; | |
| 298 | } catch( final IOException ex ) { | |
| 299 | throw new RuntimeException( ex ); | |
| 300 | } | |
| 301 | } | |
| 302 | } | |
| 303 | ||
| 304 | /** | |
| 305 | * Responsible for parsing the output from the typesetting engine and | |
| 306 | * updating the status bar to provide assurance that typesetting is | |
| 307 | * executing. | |
| 308 | * | |
| 309 | * <p> | |
| 310 | * Example lines written to standard output: | |
| 311 | * </p> | |
| 312 | * <pre>{@code | |
| 313 | * pages > flushing realpage 15, userpage 15, subpage 15 | |
| 314 | * pages > flushing realpage 16, userpage 16, subpage 16 | |
| 315 | * pages > flushing realpage 1, userpage 1, subpage 1 | |
| 316 | * pages > flushing realpage 2, userpage 2, subpage 2 | |
| 317 | * }</pre> | |
| 318 | * <p> | |
| 319 | * The lines are parsed; the first number is displayed in a status bar | |
| 320 | * message. | |
| 321 | * </p> | |
| 322 | */ | |
| 323 | private static class PaginationListener extends Thread { | |
| 324 | private static final Pattern DIGITS = Pattern.compile( "[^\\d]+" ); | |
| 325 | ||
| 326 | private final InputStream mInputStream; | |
| 327 | ||
| 328 | private final Map<String, String> mCache; | |
| 329 | ||
| 330 | public PaginationListener( | |
| 331 | final InputStream in, final Map<String, String> cache ) { | |
| 332 | mInputStream = in; | |
| 333 | mCache = cache; | |
| 334 | } | |
| 335 | ||
| 336 | @Override | |
| 337 | public void run() { | |
| 338 | try( final var reader = createReader() ) { | |
| 339 | int pageCount = 1; | |
| 340 | int passCount = 1; | |
| 341 | int pageTotal = 0; | |
| 342 | String line; | |
| 343 | ||
| 344 | while( (line = reader.readLine()) != null ) { | |
| 345 | mCache.put( line, "" ); | |
| 346 | ||
| 347 | if( line.startsWith( "pages" ) ) { | |
| 348 | // The bottleneck will be the typesetting engine writing to stdout, | |
| 349 | // not the parsing of stdout. | |
| 350 | final var scanner = new Scanner( line ).useDelimiter( DIGITS ); | |
| 351 | final var digits = scanner.next(); | |
| 352 | final var page = Integer.parseInt( digits ); | |
| 353 | ||
| 354 | // If the page number is less than the previous page count, it | |
| 355 | // means that the typesetting engine has started another pass. | |
| 356 | if( page < pageCount ) { | |
| 357 | passCount++; | |
| 358 | pageTotal = pageCount; | |
| 359 | } | |
| 360 | ||
| 361 | pageCount = page; | |
| 362 | ||
| 363 | // Let the user know that something is happening in the background. | |
| 364 | clue( "Main.status.typeset.page", | |
| 365 | pageCount, pageTotal < 1 ? "?" : pageTotal, passCount | |
| 366 | ); | |
| 367 | } | |
| 368 | } | |
| 369 | } catch( final IOException ex ) { | |
| 370 | throw new RuntimeException( ex ); | |
| 371 | } | |
| 372 | } | |
| 373 | ||
| 374 | private BufferedReader createReader() { | |
| 375 | return new BufferedReader( new InputStreamReader( mInputStream ) ); | |
| 376 | } | |
| 377 | } | |
| 378 | ||
| 379 | private File getThemesPath() { | |
| 380 | return mWorkspace.toFile( KEY_TYPESET_CONTEXT_THEMES_PATH ); | |
| 381 | } | |
| 382 | ||
| 383 | private String getThemesSelection() { | |
| 384 | return mWorkspace.toString( KEY_TYPESET_CONTEXT_THEME_SELECTION ); | |
| 385 | } | |
| 386 | ||
| 387 | /** | |
| 388 | * Answers whether logs and other files should be deleted upon error. The | |
| 389 | * log files are useful for debugging. | |
| 390 | * | |
| 391 | * @return {@code true} to delete generated files. | |
| 392 | */ | |
| 393 | public boolean autoclean() { | |
| 394 | return mWorkspace.toBoolean( KEY_TYPESET_CONTEXT_CLEAN ); | |
| 395 | } | |
| 396 | } | |
| 1 | 397 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.typesetting; | |
| 3 | ||
| 4 | /** | |
| 5 | * Responsible for creating an alternate execution path when a typesetter | |
| 6 | * cannot be found. | |
| 7 | */ | |
| 8 | public class TypesetterNotFoundException extends RuntimeException { | |
| 9 | /** | |
| 10 | * Constructs a new exception that indicates the typesetting engine cannot | |
| 11 | * be found anywhere along the PATH. | |
| 12 | * | |
| 13 | * @param name Typesetter executable file name. | |
| 14 | */ | |
| 15 | public TypesetterNotFoundException( final String name ) { | |
| 16 | super( name ); | |
| 17 | } | |
| 18 | } | |
| 1 | 19 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.actions; | |
| 3 | ||
| 4 | import com.keenwrite.Messages; | |
| 5 | import com.keenwrite.util.GenericBuilder; | |
| 6 | import javafx.event.ActionEvent; | |
| 7 | import javafx.event.EventHandler; | |
| 8 | import javafx.scene.control.Button; | |
| 9 | import javafx.scene.control.Menu; | |
| 10 | import javafx.scene.control.MenuItem; | |
| 11 | import javafx.scene.control.Tooltip; | |
| 12 | import javafx.scene.input.KeyCombination; | |
| 13 | ||
| 14 | import java.util.ArrayList; | |
| 15 | import java.util.List; | |
| 16 | ||
| 17 | import static com.keenwrite.constants.Constants.ACTION_PREFIX; | |
| 18 | import static com.keenwrite.ui.fonts.IconFactory.createGraphic; | |
| 19 | import static javafx.scene.input.KeyCombination.valueOf; | |
| 20 | ||
| 21 | /** | |
| 22 | * Defines actions the user can take through GUI interactions | |
| 23 | */ | |
| 24 | public final class Action implements MenuAction { | |
| 25 | private final String mText; | |
| 26 | private final KeyCombination mAccelerator; | |
| 27 | private final String mIcon; | |
| 28 | private final EventHandler<ActionEvent> mHandler; | |
| 29 | private final List<MenuAction> mSubActions = new ArrayList<>(); | |
| 30 | ||
| 31 | public Action( | |
| 32 | final String text, | |
| 33 | final String accelerator, | |
| 34 | final String icon, | |
| 35 | final EventHandler<ActionEvent> handler ) { | |
| 36 | assert text != null; | |
| 37 | assert handler != null; | |
| 38 | ||
| 39 | mText = text; | |
| 40 | mAccelerator = accelerator == null ? null : valueOf( accelerator ); | |
| 41 | mIcon = icon; | |
| 42 | mHandler = handler; | |
| 43 | } | |
| 44 | ||
| 45 | @Override | |
| 46 | public MenuItem createMenuItem() { | |
| 47 | // This will either become a menu or a menu item, depending on whether | |
| 48 | // sub-actions are defined. | |
| 49 | final MenuItem menuItem; | |
| 50 | ||
| 51 | if( mSubActions.isEmpty() ) { | |
| 52 | // Regular menu item has no sub-menus. | |
| 53 | menuItem = new MenuItem( mText ); | |
| 54 | } | |
| 55 | else { | |
| 56 | // Sub-actions are translated into sub-menu items beneath this action. | |
| 57 | final var submenu = new Menu( mText ); | |
| 58 | ||
| 59 | for( final var action : mSubActions ) { | |
| 60 | // Recursive call that creates a sub-menu hierarchy. | |
| 61 | submenu.getItems().add( action.createMenuItem() ); | |
| 62 | } | |
| 63 | ||
| 64 | menuItem = submenu; | |
| 65 | } | |
| 66 | ||
| 67 | if( mAccelerator != null ) { | |
| 68 | menuItem.setAccelerator( mAccelerator ); | |
| 69 | } | |
| 70 | ||
| 71 | if( mIcon != null ) { | |
| 72 | menuItem.setGraphic( createGraphic( mIcon ) ); | |
| 73 | } | |
| 74 | ||
| 75 | menuItem.setOnAction( mHandler ); | |
| 76 | ||
| 77 | return menuItem; | |
| 78 | } | |
| 79 | ||
| 80 | @Override | |
| 81 | public Button createToolBarNode() { | |
| 82 | final var button = createIconButton(); | |
| 83 | var tooltip = mText; | |
| 84 | ||
| 85 | if( tooltip.endsWith( "..." ) ) { | |
| 86 | tooltip = tooltip.substring( 0, tooltip.length() - 3 ); | |
| 87 | } | |
| 88 | ||
| 89 | // Do not display mnemonic accelerator character in tooltip text. | |
| 90 | // The accelerator key will still be available, this is display-only. | |
| 91 | tooltip = tooltip.replace( "_", "" ); | |
| 92 | ||
| 93 | if( mAccelerator != null ) { | |
| 94 | tooltip += " (" + mAccelerator.getDisplayText() + ')'; | |
| 95 | } | |
| 96 | ||
| 97 | button.setTooltip( new Tooltip( tooltip ) ); | |
| 98 | button.setFocusTraversable( false ); | |
| 99 | button.setOnAction( mHandler ); | |
| 100 | ||
| 101 | return button; | |
| 102 | } | |
| 103 | ||
| 104 | private Button createIconButton() { | |
| 105 | return new Button( null, createGraphic( mIcon ) ); | |
| 106 | } | |
| 107 | ||
| 108 | /** | |
| 109 | * Adds subordinate actions to the menu. This is used to establish sub-menu | |
| 110 | * relationships. The default behaviour does not wire up any registration; | |
| 111 | * subclasses are responsible for handling how actions relate to one another. | |
| 112 | * | |
| 113 | * @param action Actions that only exist with respect to this action. | |
| 114 | */ | |
| 115 | public MenuAction addSubActions( final MenuAction... action ) { | |
| 116 | mSubActions.addAll( List.of( action ) ); | |
| 117 | return this; | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 121 | * TODO: Reuse the {@link GenericBuilder}. | |
| 122 | * | |
| 123 | * @return The {@link Builder} for an instance of {@link Action}. | |
| 124 | */ | |
| 125 | public static Builder builder() { | |
| 126 | return new Builder(); | |
| 127 | } | |
| 128 | ||
| 129 | /** | |
| 130 | * Provides a fluent interface around constructing actions so that duplication | |
| 131 | * can be avoided. | |
| 132 | */ | |
| 133 | public static class Builder { | |
| 134 | private String mText; | |
| 135 | private String mAccelerator; | |
| 136 | private String mIcon; | |
| 137 | private EventHandler<ActionEvent> mHandler; | |
| 138 | ||
| 139 | /** | |
| 140 | * Sets the text, icon, and accelerator for a given action identifier. | |
| 141 | * See the messages properties file for details. | |
| 142 | * | |
| 143 | * @param id The identifier to look up in the properties file. | |
| 144 | * @return An instance of {@link Builder} that can be built into an | |
| 145 | * instance of {@link Action}. | |
| 146 | */ | |
| 147 | public Builder setId( final String id ) { | |
| 148 | final var prefix = ACTION_PREFIX + id + "."; | |
| 149 | final var text = prefix + "text"; | |
| 150 | final var icon = prefix + "icon"; | |
| 151 | final var accelerator = prefix + "accelerator"; | |
| 152 | final var builder = setText( text ).setIcon( icon ); | |
| 153 | ||
| 154 | return Messages.containsKey( accelerator ) | |
| 155 | ? builder.setAccelerator( Messages.get( accelerator ) ) | |
| 156 | : builder; | |
| 157 | } | |
| 158 | ||
| 159 | /** | |
| 160 | * Sets the action text based on a resource bundle key. | |
| 161 | * | |
| 162 | * @param key The key to look up in the {@link Messages}. | |
| 163 | * @return The corresponding value, or the key name if none found. | |
| 164 | */ | |
| 165 | private Builder setText( final String key ) { | |
| 166 | mText = Messages.get( key, key ); | |
| 167 | return this; | |
| 168 | } | |
| 169 | ||
| 170 | private Builder setAccelerator( final String accelerator ) { | |
| 171 | mAccelerator = accelerator; | |
| 172 | return this; | |
| 173 | } | |
| 174 | ||
| 175 | private Builder setIcon( final String iconKey ) { | |
| 176 | assert iconKey != null; | |
| 177 | ||
| 178 | // If there's no icon associated with the icon key name, don't attempt | |
| 179 | // to create a graphic for the icon, because it won't exist. | |
| 180 | final var iconName = Messages.get( iconKey ); | |
| 181 | mIcon = iconKey.equals( iconName ) ? "" : iconName; | |
| 182 | ||
| 183 | return this; | |
| 184 | } | |
| 185 | ||
| 186 | public Builder setHandler( final EventHandler<ActionEvent> handler ) { | |
| 187 | mHandler = handler; | |
| 188 | return this; | |
| 189 | } | |
| 190 | ||
| 191 | public Action build() { | |
| 192 | return new Action( mText, mAccelerator, mIcon, mHandler ); | |
| 193 | } | |
| 194 | } | |
| 195 | } | |
| 1 | 196 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.actions; | |
| 3 | ||
| 4 | import com.keenwrite.ExportFormat; | |
| 5 | import com.keenwrite.MainPane; | |
| 6 | import com.keenwrite.MainScene; | |
| 7 | import com.keenwrite.editors.TextDefinition; | |
| 8 | import com.keenwrite.editors.TextEditor; | |
| 9 | import com.keenwrite.editors.markdown.HyperlinkModel; | |
| 10 | import com.keenwrite.editors.markdown.LinkVisitor; | |
| 11 | import com.keenwrite.events.ExportFailedEvent; | |
| 12 | import com.keenwrite.preferences.PreferencesController; | |
| 13 | import com.keenwrite.preferences.Workspace; | |
| 14 | import com.keenwrite.processors.markdown.MarkdownProcessor; | |
| 15 | import com.keenwrite.search.SearchModel; | |
| 16 | import com.keenwrite.typesetting.Typesetter; | |
| 17 | import com.keenwrite.ui.controls.SearchBar; | |
| 18 | import com.keenwrite.ui.dialogs.ImageDialog; | |
| 19 | import com.keenwrite.ui.dialogs.LinkDialog; | |
| 20 | import com.keenwrite.ui.dialogs.ThemePicker; | |
| 21 | import com.keenwrite.ui.explorer.FilePicker; | |
| 22 | import com.keenwrite.ui.explorer.FilePickerFactory; | |
| 23 | import com.keenwrite.ui.logging.LogView; | |
| 24 | import com.keenwrite.util.AlphanumComparator; | |
| 25 | import com.vladsch.flexmark.ast.Link; | |
| 26 | import javafx.concurrent.Task; | |
| 27 | import javafx.scene.control.Alert; | |
| 28 | import javafx.scene.control.Dialog; | |
| 29 | import javafx.stage.Window; | |
| 30 | import javafx.stage.WindowEvent; | |
| 31 | ||
| 32 | import java.io.File; | |
| 33 | import java.io.IOException; | |
| 34 | import java.nio.file.Path; | |
| 35 | import java.util.ArrayList; | |
| 36 | import java.util.List; | |
| 37 | import java.util.Optional; | |
| 38 | import java.util.concurrent.ExecutorService; | |
| 39 | ||
| 40 | import static com.keenwrite.Bootstrap.*; | |
| 41 | import static com.keenwrite.ExportFormat.*; | |
| 42 | import static com.keenwrite.Messages.get; | |
| 43 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG_NODE; | |
| 44 | import static com.keenwrite.events.StatusEvent.clue; | |
| 45 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_TYPESET_CONTEXT_THEMES_PATH; | |
| 46 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_TYPESET_CONTEXT_THEME_SELECTION; | |
| 47 | import static com.keenwrite.processors.ProcessorFactory.createProcessors; | |
| 48 | import static com.keenwrite.ui.explorer.FilePickerFactory.Options; | |
| 49 | import static com.keenwrite.ui.explorer.FilePickerFactory.Options.*; | |
| 50 | import static com.keenwrite.util.FileWalker.walk; | |
| 51 | import static java.nio.file.Files.readString; | |
| 52 | import static java.nio.file.Files.writeString; | |
| 53 | import static java.util.concurrent.Executors.newFixedThreadPool; | |
| 54 | import static javafx.application.Platform.runLater; | |
| 55 | import static javafx.event.Event.fireEvent; | |
| 56 | import static javafx.scene.control.Alert.AlertType.INFORMATION; | |
| 57 | import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST; | |
| 58 | import static org.apache.commons.io.FilenameUtils.getExtension; | |
| 59 | ||
| 60 | /** | |
| 61 | * Responsible for abstracting how functionality is mapped to the application. | |
| 62 | * This allows users to customize accelerator keys and will provide pluggable | |
| 63 | * functionality so that different text markup languages can change documents | |
| 64 | * using their respective syntax. | |
| 65 | */ | |
| 66 | public final class ApplicationActions { | |
| 67 | private static final ExecutorService sExecutor = newFixedThreadPool( 1 ); | |
| 68 | ||
| 69 | private static final String STYLE_SEARCH = "search"; | |
| 70 | ||
| 71 | /** | |
| 72 | * Sci-fi genres, which are can be longer than other genres, typically fall | |
| 73 | * below 150,000 words at 6 chars per word. This reduces re-allocations of | |
| 74 | * memory when concatenating files together when exporting novels. | |
| 75 | */ | |
| 76 | private static final int DOCUMENT_LENGTH = 150_000 * 6; | |
| 77 | ||
| 78 | /** | |
| 79 | * When an action is executed, this is one of the recipients. | |
| 80 | */ | |
| 81 | private final MainPane mMainPane; | |
| 82 | ||
| 83 | private final MainScene mMainScene; | |
| 84 | ||
| 85 | private final LogView mLogView; | |
| 86 | ||
| 87 | /** | |
| 88 | * Tracks finding text in the active document. | |
| 89 | */ | |
| 90 | private final SearchModel mSearchModel; | |
| 91 | ||
| 92 | public ApplicationActions( final MainScene scene, final MainPane pane ) { | |
| 93 | mMainScene = scene; | |
| 94 | mMainPane = pane; | |
| 95 | mLogView = new LogView(); | |
| 96 | mSearchModel = new SearchModel(); | |
| 97 | mSearchModel.matchOffsetProperty().addListener( ( c, o, n ) -> { | |
| 98 | final var editor = getActiveTextEditor(); | |
| 99 | ||
| 100 | // Clear highlighted areas before highlighting a new region. | |
| 101 | if( o != null ) { | |
| 102 | editor.unstylize( STYLE_SEARCH ); | |
| 103 | } | |
| 104 | ||
| 105 | if( n != null ) { | |
| 106 | editor.moveTo( n.getStart() ); | |
| 107 | editor.stylize( n, STYLE_SEARCH ); | |
| 108 | } | |
| 109 | } ); | |
| 110 | ||
| 111 | // When the active text editor changes, update the haystack. | |
| 112 | mMainPane.activeTextEditorProperty().addListener( | |
| 113 | ( c, o, n ) -> mSearchModel.search( getActiveTextEditor().getText() ) | |
| 114 | ); | |
| 115 | } | |
| 116 | ||
| 117 | public void file_new() { | |
| 118 | getMainPane().newTextEditor(); | |
| 119 | } | |
| 120 | ||
| 121 | public void file_open() { | |
| 122 | pickFiles( FILE_OPEN_MULTIPLE ).ifPresent( l -> getMainPane().open( l ) ); | |
| 123 | } | |
| 124 | ||
| 125 | public void file_close() { | |
| 126 | getMainPane().close(); | |
| 127 | } | |
| 128 | ||
| 129 | public void file_close_all() { | |
| 130 | getMainPane().closeAll(); | |
| 131 | } | |
| 132 | ||
| 133 | public void file_save() { | |
| 134 | getMainPane().save(); | |
| 135 | } | |
| 136 | ||
| 137 | public void file_save_as() { | |
| 138 | pickFiles( FILE_SAVE_AS ).ifPresent( l -> getMainPane().saveAs( l ) ); | |
| 139 | } | |
| 140 | ||
| 141 | public void file_save_all() { | |
| 142 | getMainPane().saveAll(); | |
| 143 | } | |
| 144 | ||
| 145 | /** | |
| 146 | * Converts the actively edited file in the given file format. | |
| 147 | * | |
| 148 | * @param format The destination file format. | |
| 149 | */ | |
| 150 | private void file_export( final ExportFormat format ) { | |
| 151 | file_export( format, false ); | |
| 152 | } | |
| 153 | ||
| 154 | /** | |
| 155 | * Converts one or more files into the given file format. If {@code dir} | |
| 156 | * is set to true, this will first append all files in the same directory | |
| 157 | * as the actively edited file. | |
| 158 | * | |
| 159 | * @param format The destination file format. | |
| 160 | * @param dir Export all files in the actively edited file's directory. | |
| 161 | */ | |
| 162 | private void file_export( final ExportFormat format, final boolean dir ) { | |
| 163 | final var main = getMainPane(); | |
| 164 | final var editor = main.getActiveTextEditor(); | |
| 165 | final var filename = format.toExportFilename( editor.getPath() ); | |
| 166 | final var selection = pickFiles( filename, FILE_EXPORT ); | |
| 167 | ||
| 168 | selection.ifPresent( ( files ) -> { | |
| 169 | final var file = files.get( 0 ); | |
| 170 | final var path = file.toPath(); | |
| 171 | final var document = dir ? append( editor ) : editor.getText(); | |
| 172 | final var context = main.createProcessorContext( path, format ); | |
| 173 | ||
| 174 | final var task = new Task<Path>() { | |
| 175 | @Override | |
| 176 | protected Path call() throws Exception { | |
| 177 | final var chain = createProcessors( context ); | |
| 178 | final var export = chain.apply( document ); | |
| 179 | ||
| 180 | // Processors can export binary files. In such cases, processors | |
| 181 | // return null to prevent further processing. | |
| 182 | return export == null ? null : writeString( path, export ); | |
| 183 | } | |
| 184 | }; | |
| 185 | ||
| 186 | task.setOnSucceeded( | |
| 187 | e -> { | |
| 188 | final var result = task.getValue(); | |
| 189 | ||
| 190 | // Binary formats must notify users of success independently. | |
| 191 | if( result != null ) { | |
| 192 | clue( "Main.status.export.success", result ); | |
| 193 | } | |
| 194 | } | |
| 195 | ); | |
| 196 | ||
| 197 | task.setOnFailed( e -> { | |
| 198 | final var ex = task.getException(); | |
| 199 | clue( ex ); | |
| 200 | ||
| 201 | if( ex instanceof TypeNotPresentException ) { | |
| 202 | fireExportFailedEvent(); | |
| 203 | } | |
| 204 | } ); | |
| 205 | ||
| 206 | sExecutor.execute( task ); | |
| 207 | } ); | |
| 208 | } | |
| 209 | ||
| 210 | /** | |
| 211 | * @param dir {@code true} means to export all files in the active file | |
| 212 | * editor's directory; {@code false} means to export only the | |
| 213 | * actively edited file. | |
| 214 | */ | |
| 215 | private void file_export_pdf( final boolean dir ) { | |
| 216 | final var workspace = getWorkspace(); | |
| 217 | final var themes = workspace.toFile( KEY_TYPESET_CONTEXT_THEMES_PATH ); | |
| 218 | final var theme = workspace.stringProperty( | |
| 219 | KEY_TYPESET_CONTEXT_THEME_SELECTION ); | |
| 220 | ||
| 221 | if( Typesetter.canRun() ) { | |
| 222 | // If the typesetter is installed, allow the user to select a theme. If | |
| 223 | // the themes aren't installed, a status message will appear. | |
| 224 | if( ThemePicker.choose( themes, theme ) ) { | |
| 225 | file_export( APPLICATION_PDF, dir ); | |
| 226 | } | |
| 227 | } | |
| 228 | else { | |
| 229 | fireExportFailedEvent(); | |
| 230 | } | |
| 231 | } | |
| 232 | ||
| 233 | public void file_export_pdf() { | |
| 234 | file_export_pdf( false ); | |
| 235 | } | |
| 236 | ||
| 237 | public void file_export_pdf_dir() { | |
| 238 | file_export_pdf( true ); | |
| 239 | } | |
| 240 | ||
| 241 | public void file_export_html_svg() { | |
| 242 | file_export( HTML_TEX_SVG ); | |
| 243 | } | |
| 244 | ||
| 245 | public void file_export_html_tex() { | |
| 246 | file_export( HTML_TEX_DELIMITED ); | |
| 247 | } | |
| 248 | ||
| 249 | public void file_export_xhtml_tex() { | |
| 250 | file_export( XHTML_TEX ); | |
| 251 | } | |
| 252 | ||
| 253 | public void file_export_markdown() { | |
| 254 | file_export( MARKDOWN_PLAIN ); | |
| 255 | } | |
| 256 | ||
| 257 | private void fireExportFailedEvent() { | |
| 258 | runLater( ExportFailedEvent::fireExportFailedEvent ); | |
| 259 | } | |
| 260 | ||
| 261 | public void file_exit() { | |
| 262 | final var window = getWindow(); | |
| 263 | fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) ); | |
| 264 | } | |
| 265 | ||
| 266 | public void edit_undo() { | |
| 267 | getActiveTextEditor().undo(); | |
| 268 | } | |
| 269 | ||
| 270 | public void edit_redo() { | |
| 271 | getActiveTextEditor().redo(); | |
| 272 | } | |
| 273 | ||
| 274 | public void edit_cut() { | |
| 275 | getActiveTextEditor().cut(); | |
| 276 | } | |
| 277 | ||
| 278 | public void edit_copy() { | |
| 279 | getActiveTextEditor().copy(); | |
| 280 | } | |
| 281 | ||
| 282 | public void edit_paste() { | |
| 283 | getActiveTextEditor().paste(); | |
| 284 | } | |
| 285 | ||
| 286 | public void edit_select_all() { | |
| 287 | getActiveTextEditor().selectAll(); | |
| 288 | } | |
| 289 | ||
| 290 | public void edit_find() { | |
| 291 | final var nodes = getMainScene().getStatusBar().getLeftItems(); | |
| 292 | ||
| 293 | if( nodes.isEmpty() ) { | |
| 294 | final var searchBar = new SearchBar(); | |
| 295 | ||
| 296 | searchBar.matchIndexProperty().bind( mSearchModel.matchIndexProperty() ); | |
| 297 | searchBar.matchCountProperty().bind( mSearchModel.matchCountProperty() ); | |
| 298 | ||
| 299 | searchBar.setOnCancelAction( ( event ) -> { | |
| 300 | final var editor = getActiveTextEditor(); | |
| 301 | nodes.remove( searchBar ); | |
| 302 | editor.unstylize( STYLE_SEARCH ); | |
| 303 | editor.getNode().requestFocus(); | |
| 304 | } ); | |
| 305 | ||
| 306 | searchBar.addInputListener( ( c, o, n ) -> { | |
| 307 | if( n != null && !n.isEmpty() ) { | |
| 308 | mSearchModel.search( n, getActiveTextEditor().getText() ); | |
| 309 | } | |
| 310 | } ); | |
| 311 | ||
| 312 | searchBar.setOnNextAction( ( event ) -> edit_find_next() ); | |
| 313 | searchBar.setOnPrevAction( ( event ) -> edit_find_prev() ); | |
| 314 | ||
| 315 | nodes.add( searchBar ); | |
| 316 | searchBar.requestFocus(); | |
| 317 | } | |
| 318 | else { | |
| 319 | nodes.clear(); | |
| 320 | } | |
| 321 | } | |
| 322 | ||
| 323 | public void edit_find_next() { | |
| 324 | mSearchModel.advance(); | |
| 325 | } | |
| 326 | ||
| 327 | public void edit_find_prev() { | |
| 328 | mSearchModel.retreat(); | |
| 329 | } | |
| 330 | ||
| 331 | public void edit_preferences() { | |
| 332 | try { | |
| 333 | new PreferencesController( getWorkspace() ).show(); | |
| 334 | } catch( final Exception ex ) { | |
| 335 | clue( ex ); | |
| 336 | } | |
| 337 | } | |
| 338 | ||
| 339 | public void format_bold() { | |
| 340 | getActiveTextEditor().bold(); | |
| 341 | } | |
| 342 | ||
| 343 | public void format_italic() { | |
| 344 | getActiveTextEditor().italic(); | |
| 345 | } | |
| 346 | ||
| 347 | public void format_monospace() { | |
| 348 | getActiveTextEditor().monospace(); | |
| 349 | } | |
| 350 | ||
| 351 | public void format_superscript() { | |
| 352 | getActiveTextEditor().superscript(); | |
| 353 | } | |
| 354 | ||
| 355 | public void format_subscript() { | |
| 356 | getActiveTextEditor().subscript(); | |
| 357 | } | |
| 358 | ||
| 359 | public void format_strikethrough() { | |
| 360 | getActiveTextEditor().strikethrough(); | |
| 361 | } | |
| 362 | ||
| 363 | public void insert_blockquote() { | |
| 364 | getActiveTextEditor().blockquote(); | |
| 365 | } | |
| 366 | ||
| 367 | public void insert_code() { | |
| 368 | getActiveTextEditor().code(); | |
| 369 | } | |
| 370 | ||
| 371 | public void insert_fenced_code_block() { | |
| 372 | getActiveTextEditor().fencedCodeBlock(); | |
| 373 | } | |
| 374 | ||
| 375 | public void insert_link() { | |
| 376 | insertObject( createLinkDialog() ); | |
| 377 | } | |
| 378 | ||
| 379 | public void insert_image() { | |
| 380 | insertObject( createImageDialog() ); | |
| 381 | } | |
| 382 | ||
| 383 | private void insertObject( final Dialog<String> dialog ) { | |
| 384 | final var textArea = getActiveTextEditor().getTextArea(); | |
| 385 | dialog.showAndWait().ifPresent( textArea::replaceSelection ); | |
| 386 | } | |
| 387 | ||
| 388 | private Dialog<String> createLinkDialog() { | |
| 389 | return new LinkDialog( getWindow(), createHyperlinkModel() ); | |
| 390 | } | |
| 391 | ||
| 392 | private Dialog<String> createImageDialog() { | |
| 393 | final var path = getActiveTextEditor().getPath(); | |
| 394 | final var parentDir = path.getParent(); | |
| 395 | return new ImageDialog( getWindow(), parentDir ); | |
| 396 | } | |
| 397 | ||
| 398 | /** | |
| 399 | * Returns one of: selected text, word under cursor, or parsed hyperlink from | |
| 400 | * the Markdown AST. | |
| 401 | * | |
| 402 | * @return An instance containing the link URL and display text. | |
| 403 | */ | |
| 404 | private HyperlinkModel createHyperlinkModel() { | |
| 405 | final var context = getMainPane().createProcessorContext(); | |
| 406 | final var editor = getActiveTextEditor(); | |
| 407 | final var textArea = editor.getTextArea(); | |
| 408 | final var selectedText = textArea.getSelectedText(); | |
| 409 | ||
| 410 | // Convert current paragraph to Markdown nodes. | |
| 411 | final var mp = MarkdownProcessor.create( context ); | |
| 412 | final var p = textArea.getCurrentParagraph(); | |
| 413 | final var paragraph = textArea.getText( p ); | |
| 414 | final var node = mp.toNode( paragraph ); | |
| 415 | final var visitor = new LinkVisitor( textArea.getCaretColumn() ); | |
| 416 | final var link = visitor.process( node ); | |
| 417 | ||
| 418 | if( link != null ) { | |
| 419 | textArea.selectRange( p, link.getStartOffset(), p, link.getEndOffset() ); | |
| 420 | } | |
| 421 | ||
| 422 | return createHyperlinkModel( link, selectedText ); | |
| 423 | } | |
| 424 | ||
| 425 | private HyperlinkModel createHyperlinkModel( | |
| 426 | final Link link, final String selection ) { | |
| 427 | ||
| 428 | return link == null | |
| 429 | ? new HyperlinkModel( selection, "https://localhost" ) | |
| 430 | : new HyperlinkModel( link ); | |
| 431 | } | |
| 432 | ||
| 433 | public void insert_heading_1() { | |
| 434 | insert_heading( 1 ); | |
| 435 | } | |
| 436 | ||
| 437 | public void insert_heading_2() { | |
| 438 | insert_heading( 2 ); | |
| 439 | } | |
| 440 | ||
| 441 | public void insert_heading_3() { | |
| 442 | insert_heading( 3 ); | |
| 443 | } | |
| 444 | ||
| 445 | private void insert_heading( final int level ) { | |
| 446 | getActiveTextEditor().heading( level ); | |
| 447 | } | |
| 448 | ||
| 449 | public void insert_unordered_list() { | |
| 450 | getActiveTextEditor().unorderedList(); | |
| 451 | } | |
| 452 | ||
| 453 | public void insert_ordered_list() { | |
| 454 | getActiveTextEditor().orderedList(); | |
| 455 | } | |
| 456 | ||
| 457 | public void insert_horizontal_rule() { | |
| 458 | getActiveTextEditor().horizontalRule(); | |
| 459 | } | |
| 460 | ||
| 461 | public void definition_create() { | |
| 462 | getActiveTextDefinition().createDefinition(); | |
| 463 | } | |
| 464 | ||
| 465 | public void definition_rename() { | |
| 466 | getActiveTextDefinition().renameDefinition(); | |
| 467 | } | |
| 468 | ||
| 469 | public void definition_delete() { | |
| 470 | getActiveTextDefinition().deleteDefinitions(); | |
| 471 | } | |
| 472 | ||
| 473 | public void definition_autoinsert() { | |
| 474 | getMainPane().autoinsert(); | |
| 475 | } | |
| 476 | ||
| 477 | public void view_refresh() { | |
| 478 | getMainPane().viewRefresh(); | |
| 479 | } | |
| 480 | ||
| 481 | public void view_preview() { | |
| 482 | getMainPane().viewPreview(); | |
| 483 | } | |
| 484 | ||
| 485 | public void view_outline() { | |
| 486 | getMainPane().viewOutline(); | |
| 487 | } | |
| 488 | ||
| 489 | public void view_files() { getMainPane().viewFiles(); } | |
| 490 | ||
| 491 | public void view_statistics() { | |
| 492 | getMainPane().viewStatistics(); | |
| 493 | } | |
| 494 | ||
| 495 | public void view_menubar() { | |
| 496 | getMainScene().toggleMenuBar(); | |
| 497 | } | |
| 498 | ||
| 499 | public void view_toolbar() { | |
| 500 | getMainScene().toggleToolBar(); | |
| 501 | } | |
| 502 | ||
| 503 | public void view_statusbar() { | |
| 504 | getMainScene().toggleStatusBar(); | |
| 505 | } | |
| 506 | ||
| 507 | public void view_log() { | |
| 508 | mLogView.view(); | |
| 509 | } | |
| 510 | ||
| 511 | public void help_about() { | |
| 512 | final var alert = new Alert( INFORMATION ); | |
| 513 | final var prefix = "Dialog.about."; | |
| 514 | alert.setTitle( get( prefix + "title", APP_TITLE ) ); | |
| 515 | alert.setHeaderText( get( prefix + "header", APP_TITLE ) ); | |
| 516 | alert.setContentText( get( prefix + "content", APP_YEAR, APP_VERSION ) ); | |
| 517 | alert.setGraphic( ICON_DIALOG_NODE ); | |
| 518 | alert.initOwner( getWindow() ); | |
| 519 | alert.showAndWait(); | |
| 520 | } | |
| 521 | ||
| 522 | /** | |
| 523 | * Concatenates all the files in the same directory as the given file into | |
| 524 | * a string. The extension is determined by the given file name pattern; the | |
| 525 | * order files are concatenated is based on their numeric sort order (this | |
| 526 | * avoids lexicographic sorting). | |
| 527 | * <p> | |
| 528 | * If the parent path to the file being edited in the text editor cannot | |
| 529 | * be found then this will return the editor's text, without iterating through | |
| 530 | * the parent directory. (Should never happen, but who knows?) | |
| 531 | * </p> | |
| 532 | * <p> | |
| 533 | * New lines are automatically appended to separate each file. | |
| 534 | * </p> | |
| 535 | * | |
| 536 | * @param editor The text editor containing | |
| 537 | * @return All files in the same directory as the file being edited | |
| 538 | * concatenated into a single string. | |
| 539 | */ | |
| 540 | private String append( final TextEditor editor ) { | |
| 541 | final var pattern = editor.getPath(); | |
| 542 | final var parent = pattern.getParent(); | |
| 543 | ||
| 544 | // Short-circuit because nothing else can be done. | |
| 545 | if( parent == null ) { | |
| 546 | clue( "Main.status.export.concat.parent", pattern ); | |
| 547 | return editor.getText(); | |
| 548 | } | |
| 549 | ||
| 550 | final var filename = pattern.getFileName().toString(); | |
| 551 | final var extension = getExtension( filename ); | |
| 552 | ||
| 553 | if( extension == null || extension.isBlank() ) { | |
| 554 | clue( "Main.status.export.concat.extension", filename ); | |
| 555 | return editor.getText(); | |
| 556 | } | |
| 557 | ||
| 558 | try { | |
| 559 | final var glob = "**/*." + extension; | |
| 560 | final ArrayList<Path> files = new ArrayList<>(); | |
| 561 | walk( parent, glob, files::add ); | |
| 562 | files.sort( new AlphanumComparator<>() ); | |
| 563 | ||
| 564 | final var text = new StringBuilder( DOCUMENT_LENGTH ); | |
| 565 | ||
| 566 | files.forEach( ( file ) -> { | |
| 567 | try { | |
| 568 | clue( "Main.status.export.concat", file ); | |
| 569 | text.append( readString( file ) ); | |
| 570 | } catch( final IOException ex ) { | |
| 571 | clue( "Main.status.export.concat.io", file ); | |
| 572 | } | |
| 573 | } ); | |
| 574 | ||
| 575 | return text.toString(); | |
| 576 | } catch( final Throwable t ) { | |
| 577 | clue( t ); | |
| 578 | return editor.getText(); | |
| 579 | } | |
| 580 | } | |
| 581 | ||
| 582 | private Optional<List<File>> pickFiles( final Options... options ) { | |
| 583 | return createPicker( options ).choose(); | |
| 584 | } | |
| 585 | ||
| 586 | private Optional<List<File>> pickFiles( | |
| 587 | final File filename, final Options... options ) { | |
| 588 | final var picker = createPicker( options ); | |
| 589 | picker.setInitialFilename( filename ); | |
| 590 | return picker.choose(); | |
| 591 | } | |
| 592 | ||
| 593 | private FilePicker createPicker( final Options... options ) { | |
| 594 | final var factory = new FilePickerFactory( getWorkspace() ); | |
| 595 | return factory.createModal( getWindow(), options ); | |
| 596 | } | |
| 597 | ||
| 598 | private TextEditor getActiveTextEditor() { | |
| 599 | return getMainPane().getActiveTextEditor(); | |
| 600 | } | |
| 601 | ||
| 602 | private TextDefinition getActiveTextDefinition() { | |
| 603 | return getMainPane().getActiveTextDefinition(); | |
| 604 | } | |
| 605 | ||
| 606 | private MainScene getMainScene() { | |
| 607 | return mMainScene; | |
| 608 | } | |
| 609 | ||
| 610 | private MainPane getMainPane() { | |
| 611 | return mMainPane; | |
| 612 | } | |
| 613 | ||
| 614 | private Workspace getWorkspace() { | |
| 615 | return mMainPane.getWorkspace(); | |
| 616 | } | |
| 617 | ||
| 618 | private Window getWindow() { | |
| 619 | return getMainPane().getWindow(); | |
| 620 | } | |
| 621 | } | |
| 1 | 622 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.actions; | |
| 3 | ||
| 4 | import com.keenwrite.ui.controls.EventedStatusBar; | |
| 5 | import javafx.event.ActionEvent; | |
| 6 | import javafx.event.EventHandler; | |
| 7 | import javafx.scene.Node; | |
| 8 | import javafx.scene.control.Menu; | |
| 9 | import javafx.scene.control.MenuBar; | |
| 10 | import javafx.scene.control.MenuItem; | |
| 11 | import javafx.scene.control.ToolBar; | |
| 12 | import org.controlsfx.control.StatusBar; | |
| 13 | ||
| 14 | import java.util.HashMap; | |
| 15 | import java.util.Map; | |
| 16 | ||
| 17 | import static com.keenwrite.Messages.get; | |
| 18 | ||
| 19 | /** | |
| 20 | * Responsible for wiring all application actions to menus, toolbar buttons, | |
| 21 | * and keyboard shortcuts. | |
| 22 | */ | |
| 23 | public final class ApplicationBars { | |
| 24 | ||
| 25 | private static final Map<String, Action> sMap = new HashMap<>( 64 ); | |
| 26 | ||
| 27 | /** | |
| 28 | * Empty constructor. | |
| 29 | */ | |
| 30 | public ApplicationBars() { | |
| 31 | } | |
| 32 | ||
| 33 | /** | |
| 34 | * Creates the main application affordances. | |
| 35 | * | |
| 36 | * @param actions The {@link ApplicationActions} that map user interface | |
| 37 | * selections to executable code. | |
| 38 | * @return An instance of {@link MenuBar} that contains the menu. | |
| 39 | */ | |
| 40 | public static MenuBar createMenuBar( final ApplicationActions actions ) { | |
| 41 | final var SEPARATOR_ACTION = new SeparatorAction(); | |
| 42 | ||
| 43 | //@formatter:off | |
| 44 | return new MenuBar( | |
| 45 | createMenu( | |
| 46 | get( "Main.menu.file" ), | |
| 47 | addAction( "file.new", e -> actions.file_new() ), | |
| 48 | addAction( "file.open", e -> actions.file_open() ), | |
| 49 | SEPARATOR_ACTION, | |
| 50 | addAction( "file.close", e -> actions.file_close() ), | |
| 51 | addAction( "file.close_all", e -> actions.file_close_all() ), | |
| 52 | SEPARATOR_ACTION, | |
| 53 | addAction( "file.save", e -> actions.file_save() ), | |
| 54 | addAction( "file.save_as", e -> actions.file_save_as() ), | |
| 55 | addAction( "file.save_all", e -> actions.file_save_all() ), | |
| 56 | SEPARATOR_ACTION, | |
| 57 | addAction( "file.export", e -> {} ) | |
| 58 | .addSubActions( | |
| 59 | addAction( "file.export.pdf", e -> actions.file_export_pdf() ), | |
| 60 | addAction( "file.export.pdf.dir", e -> actions.file_export_pdf_dir() ), | |
| 61 | addAction( "file.export.html_svg", e -> actions.file_export_html_svg() ), | |
| 62 | addAction( "file.export.html_tex", e -> actions.file_export_html_tex() ), | |
| 63 | addAction( "file.export.xhtml_tex", e -> actions.file_export_xhtml_tex() ), | |
| 64 | addAction( "file.export.markdown", e -> actions.file_export_markdown() ) | |
| 65 | ), | |
| 66 | SEPARATOR_ACTION, | |
| 67 | addAction( "file.exit", e -> actions.file_exit() ) | |
| 68 | ), | |
| 69 | createMenu( | |
| 70 | get( "Main.menu.edit" ), | |
| 71 | SEPARATOR_ACTION, | |
| 72 | addAction( "edit.undo", e -> actions.edit_undo() ), | |
| 73 | addAction( "edit.redo", e -> actions.edit_redo() ), | |
| 74 | SEPARATOR_ACTION, | |
| 75 | addAction( "edit.cut", e -> actions.edit_cut() ), | |
| 76 | addAction( "edit.copy", e -> actions.edit_copy() ), | |
| 77 | addAction( "edit.paste", e -> actions.edit_paste() ), | |
| 78 | addAction( "edit.select_all", e -> actions.edit_select_all() ), | |
| 79 | SEPARATOR_ACTION, | |
| 80 | addAction( "edit.find", e -> actions.edit_find() ), | |
| 81 | addAction( "edit.find_next", e -> actions.edit_find_next() ), | |
| 82 | addAction( "edit.find_prev", e -> actions.edit_find_prev() ), | |
| 83 | SEPARATOR_ACTION, | |
| 84 | addAction( "edit.preferences", e -> actions.edit_preferences() ) | |
| 85 | ), | |
| 86 | createMenu( | |
| 87 | get( "Main.menu.format" ), | |
| 88 | addAction( "format.bold", e -> actions.format_bold() ), | |
| 89 | addAction( "format.italic", e -> actions.format_italic() ), | |
| 90 | addAction( "format.monospace", e -> actions.format_monospace() ), | |
| 91 | addAction( "format.superscript", e -> actions.format_superscript() ), | |
| 92 | addAction( "format.subscript", e -> actions.format_subscript() ), | |
| 93 | addAction( "format.strikethrough", e -> actions.format_strikethrough() ) | |
| 94 | ), | |
| 95 | createMenu( | |
| 96 | get( "Main.menu.insert" ), | |
| 97 | addAction( "insert.blockquote", e -> actions.insert_blockquote() ), | |
| 98 | addAction( "insert.code", e -> actions.insert_code() ), | |
| 99 | addAction( "insert.fenced_code_block", e -> actions.insert_fenced_code_block() ), | |
| 100 | SEPARATOR_ACTION, | |
| 101 | addAction( "insert.link", e -> actions.insert_link() ), | |
| 102 | addAction( "insert.image", e -> actions.insert_image() ), | |
| 103 | SEPARATOR_ACTION, | |
| 104 | addAction( "insert.heading_1", e -> actions.insert_heading_1() ), | |
| 105 | addAction( "insert.heading_2", e -> actions.insert_heading_2() ), | |
| 106 | addAction( "insert.heading_3", e -> actions.insert_heading_3() ), | |
| 107 | SEPARATOR_ACTION, | |
| 108 | addAction( "insert.unordered_list", e -> actions.insert_unordered_list() ), | |
| 109 | addAction( "insert.ordered_list", e -> actions.insert_ordered_list() ), | |
| 110 | addAction( "insert.horizontal_rule", e -> actions.insert_horizontal_rule() ) | |
| 111 | ), | |
| 112 | createMenu( | |
| 113 | get( "Main.menu.definition" ), | |
| 114 | addAction( "definition.insert", e -> actions.definition_autoinsert() ), | |
| 115 | SEPARATOR_ACTION, | |
| 116 | addAction( "definition.create", e -> actions.definition_create() ), | |
| 117 | addAction( "definition.rename", e -> actions.definition_rename() ), | |
| 118 | addAction( "definition.delete", e -> actions.definition_delete() ) | |
| 119 | ), | |
| 120 | createMenu( | |
| 121 | get( "Main.menu.view" ), | |
| 122 | addAction( "view.refresh", e -> actions.view_refresh() ), | |
| 123 | SEPARATOR_ACTION, | |
| 124 | addAction( "view.preview", e -> actions.view_preview() ), | |
| 125 | addAction( "view.outline", e -> actions.view_outline() ), | |
| 126 | addAction( "view.statistics", e-> actions.view_statistics() ), | |
| 127 | addAction( "view.files", e-> actions.view_files() ), | |
| 128 | SEPARATOR_ACTION, | |
| 129 | addAction( "view.menubar", e -> actions.view_menubar() ), | |
| 130 | addAction( "view.toolbar", e -> actions.view_toolbar() ), | |
| 131 | addAction( "view.statusbar", e -> actions.view_statusbar() ), | |
| 132 | SEPARATOR_ACTION, | |
| 133 | addAction( "view.log", e -> actions.view_log() ) | |
| 134 | ), | |
| 135 | createMenu( | |
| 136 | get( "Main.menu.help" ), | |
| 137 | addAction( "help.about", e -> actions.help_about() ) | |
| 138 | ) ); | |
| 139 | //@formatter:on | |
| 140 | } | |
| 141 | ||
| 142 | public static Node createToolBar() { | |
| 143 | final var SEPARATOR_ACTION = new SeparatorAction(); | |
| 144 | ||
| 145 | return createToolBar( | |
| 146 | getAction( "file.new" ), | |
| 147 | getAction( "file.open" ), | |
| 148 | getAction( "file.save" ), | |
| 149 | SEPARATOR_ACTION, | |
| 150 | getAction( "file.export.pdf" ), | |
| 151 | SEPARATOR_ACTION, | |
| 152 | getAction( "edit.undo" ), | |
| 153 | getAction( "edit.redo" ), | |
| 154 | getAction( "edit.cut" ), | |
| 155 | getAction( "edit.copy" ), | |
| 156 | getAction( "edit.paste" ), | |
| 157 | SEPARATOR_ACTION, | |
| 158 | getAction( "format.bold" ), | |
| 159 | getAction( "format.italic" ), | |
| 160 | getAction( "format.superscript" ), | |
| 161 | getAction( "format.subscript" ), | |
| 162 | getAction( "insert.blockquote" ), | |
| 163 | getAction( "insert.code" ), | |
| 164 | getAction( "insert.fenced_code_block" ), | |
| 165 | SEPARATOR_ACTION, | |
| 166 | getAction( "insert.link" ), | |
| 167 | getAction( "insert.image" ), | |
| 168 | SEPARATOR_ACTION, | |
| 169 | getAction( "insert.heading_1" ), | |
| 170 | SEPARATOR_ACTION, | |
| 171 | getAction( "insert.unordered_list" ), | |
| 172 | getAction( "insert.ordered_list" ) | |
| 173 | ); | |
| 174 | } | |
| 175 | ||
| 176 | public static StatusBar createStatusBar() { | |
| 177 | return new EventedStatusBar(); | |
| 178 | } | |
| 179 | ||
| 180 | /** | |
| 181 | * Adds a new action to the list of actions. | |
| 182 | * | |
| 183 | * @param key The name of the action to register in {@link #sMap}. | |
| 184 | * @param handler Performs the action upon request. | |
| 185 | * @return The newly registered action. | |
| 186 | */ | |
| 187 | private static Action addAction( | |
| 188 | final String key, final EventHandler<ActionEvent> handler ) { | |
| 189 | assert key != null; | |
| 190 | assert handler != null; | |
| 191 | ||
| 192 | final var action = Action | |
| 193 | .builder() | |
| 194 | .setId( key ) | |
| 195 | .setHandler( handler ) | |
| 196 | .build(); | |
| 197 | ||
| 198 | sMap.put( key, action ); | |
| 199 | ||
| 200 | return action; | |
| 201 | } | |
| 202 | ||
| 203 | private static Action getAction( final String key ) { | |
| 204 | return sMap.get( key ); | |
| 205 | } | |
| 206 | ||
| 207 | public static Menu createMenu( | |
| 208 | final String text, final MenuAction... actions ) { | |
| 209 | return new Menu( text, null, createMenuItems( actions ) ); | |
| 210 | } | |
| 211 | ||
| 212 | public static MenuItem[] createMenuItems( final MenuAction... actions ) { | |
| 213 | final var menuItems = new MenuItem[ actions.length ]; | |
| 214 | ||
| 215 | for( var i = 0; i < actions.length; i++ ) { | |
| 216 | menuItems[ i ] = actions[ i ].createMenuItem(); | |
| 217 | } | |
| 218 | ||
| 219 | return menuItems; | |
| 220 | } | |
| 221 | ||
| 222 | private static ToolBar createToolBar( final MenuAction... actions ) { | |
| 223 | return new ToolBar( createToolBarButtons( actions ) ); | |
| 224 | } | |
| 225 | ||
| 226 | private static Node[] createToolBarButtons( final MenuAction... actions ) { | |
| 227 | final var len = actions.length; | |
| 228 | final var nodes = new Node[ len ]; | |
| 229 | ||
| 230 | for( var i = 0; i < len; i++ ) { | |
| 231 | nodes[ i ] = actions[ i ].createToolBarNode(); | |
| 232 | } | |
| 233 | ||
| 234 | return nodes; | |
| 235 | } | |
| 236 | } | |
| 1 | 237 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.actions; | |
| 3 | ||
| 4 | import javafx.scene.Node; | |
| 5 | import javafx.scene.control.Button; | |
| 6 | import javafx.scene.control.MenuItem; | |
| 7 | import javafx.scene.control.Separator; | |
| 8 | ||
| 9 | /** | |
| 10 | * Implementations are responsible for creating menu items and toolbar buttons. | |
| 11 | */ | |
| 12 | public interface MenuAction { | |
| 13 | /** | |
| 14 | * Creates a menu item based on the {@link Action} parameters. | |
| 15 | * | |
| 16 | * @return A new {@link MenuItem} instance. | |
| 17 | */ | |
| 18 | MenuItem createMenuItem(); | |
| 19 | ||
| 20 | /** | |
| 21 | * Creates an instance of {@link Button} or {@link Separator} based on the | |
| 22 | * {@link Action} parameters. | |
| 23 | * | |
| 24 | * @return A new {@link Button} or {@link Separator} instance. | |
| 25 | */ | |
| 26 | Node createToolBarNode(); | |
| 27 | } | |
| 1 | 28 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.actions; | |
| 3 | ||
| 4 | import javafx.scene.Node; | |
| 5 | import javafx.scene.control.*; | |
| 6 | ||
| 7 | /** | |
| 8 | * Represents a {@link MenuBar} or {@link ToolBar} action that has no | |
| 9 | * operation, acting as a placeholder for line separators. | |
| 10 | */ | |
| 11 | public final class SeparatorAction implements MenuAction { | |
| 12 | @Override | |
| 13 | public MenuItem createMenuItem() { | |
| 14 | return new SeparatorMenuItem(); | |
| 15 | } | |
| 16 | ||
| 17 | @Override | |
| 18 | public Node createToolBarNode() { | |
| 19 | return new Separator(); | |
| 20 | } | |
| 21 | } | |
| 1 | 22 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | ||
| 3 | /** | |
| 4 | * This package contains classes that define commands as executable actions. | |
| 5 | */ | |
| 6 | package com.keenwrite.ui.actions; | |
| 1 | 7 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.adapters; | |
| 3 | ||
| 4 | import org.xhtmlrenderer.event.DocumentListener; | |
| 5 | ||
| 6 | import static com.keenwrite.events.StatusEvent.clue; | |
| 7 | ||
| 8 | /** | |
| 9 | * Allows subclasses to implement only specific events of interest. | |
| 10 | */ | |
| 11 | public class DocumentAdapter implements DocumentListener { | |
| 12 | @Override | |
| 13 | public void documentStarted() { | |
| 14 | } | |
| 15 | ||
| 16 | @Override | |
| 17 | public void documentLoaded() { | |
| 18 | } | |
| 19 | ||
| 20 | @Override | |
| 21 | public void onLayoutException( final Throwable t ) { | |
| 22 | clue( t ); | |
| 23 | } | |
| 24 | ||
| 25 | @Override | |
| 26 | public void onRenderException( final Throwable t ) { | |
| 27 | clue( t ); | |
| 28 | } | |
| 29 | } | |
| 1 | 30 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.adapters; | |
| 3 | ||
| 4 | import org.w3c.dom.Element; | |
| 5 | import org.xhtmlrenderer.extend.ReplacedElementFactory; | |
| 6 | import org.xhtmlrenderer.simple.extend.FormSubmissionListener; | |
| 7 | ||
| 8 | /** | |
| 9 | * Allows subclasses to implement only specific events of interest. | |
| 10 | */ | |
| 11 | public abstract class ReplacedElementAdapter implements ReplacedElementFactory { | |
| 12 | @Override | |
| 13 | public void reset() { | |
| 14 | } | |
| 15 | ||
| 16 | @Override | |
| 17 | public void remove( final Element e ) { | |
| 18 | } | |
| 19 | ||
| 20 | @Override | |
| 21 | public void setFormSubmissionListener( | |
| 22 | final FormSubmissionListener listener ) { | |
| 23 | } | |
| 24 | } | |
| 1 | 25 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.controls; | |
| 3 | ||
| 4 | import com.keenwrite.Messages; | |
| 5 | import javafx.event.ActionEvent; | |
| 6 | import javafx.scene.control.Button; | |
| 7 | import javafx.stage.DirectoryChooser; | |
| 8 | ||
| 9 | import java.io.File; | |
| 10 | import java.util.function.Consumer; | |
| 11 | ||
| 12 | import static com.keenwrite.ui.fonts.IconFactory.createGraphic; | |
| 13 | import static org.controlsfx.glyphfont.FontAwesome.Glyph.FILE_ALT; | |
| 14 | ||
| 15 | /** | |
| 16 | * Responsible for browsing directories. | |
| 17 | */ | |
| 18 | public class BrowseButton extends Button { | |
| 19 | /** | |
| 20 | * Initial directory. | |
| 21 | */ | |
| 22 | private final File mDirectory; | |
| 23 | ||
| 24 | /** | |
| 25 | * Called when the user accepts a directory. | |
| 26 | */ | |
| 27 | private final Consumer<File> mConsumer; | |
| 28 | ||
| 29 | public BrowseButton( final File directory, final Consumer<File> consumer ) { | |
| 30 | assert directory != null; | |
| 31 | assert consumer != null; | |
| 32 | ||
| 33 | mDirectory = directory; | |
| 34 | mConsumer = consumer; | |
| 35 | ||
| 36 | setGraphic( createGraphic( FILE_ALT ) ); | |
| 37 | setOnAction( this::browse ); | |
| 38 | } | |
| 39 | ||
| 40 | public void browse( final ActionEvent ignored ) { | |
| 41 | final var chooser = new DirectoryChooser(); | |
| 42 | chooser.setTitle( Messages.get( "BrowseDirectoryButton.chooser.title" ) ); | |
| 43 | chooser.setInitialDirectory( mDirectory ); | |
| 44 | ||
| 45 | final var result = chooser.showDialog( getScene().getWindow() ); | |
| 46 | ||
| 47 | if( result != null ) { | |
| 48 | mConsumer.accept( result ); | |
| 49 | } | |
| 50 | } | |
| 51 | } | |
| 1 | 52 |
| 1 | /* | |
| 2 | * Copyright 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | package com.keenwrite.ui.controls; | |
| 29 | ||
| 30 | import com.keenwrite.Messages; | |
| 31 | import javafx.beans.property.ObjectProperty; | |
| 32 | import javafx.beans.property.SimpleObjectProperty; | |
| 33 | import javafx.event.ActionEvent; | |
| 34 | import javafx.scene.control.Button; | |
| 35 | import javafx.scene.control.Tooltip; | |
| 36 | import javafx.scene.input.KeyCode; | |
| 37 | import javafx.scene.input.KeyEvent; | |
| 38 | import javafx.stage.FileChooser; | |
| 39 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 40 | ||
| 41 | import java.io.File; | |
| 42 | import java.nio.file.Path; | |
| 43 | import java.util.ArrayList; | |
| 44 | import java.util.List; | |
| 45 | ||
| 46 | import static com.keenwrite.ui.fonts.IconFactory.createGraphic; | |
| 47 | import static org.controlsfx.glyphfont.FontAwesome.Glyph.FILE_ALT; | |
| 48 | ||
| 49 | /** | |
| 50 | * Button that opens a file chooser to select a local file for a URL. | |
| 51 | */ | |
| 52 | public class BrowseFileButton extends Button { | |
| 53 | ||
| 54 | private final List<ExtensionFilter> mExtensionFilters = new ArrayList<>(); | |
| 55 | private final ObjectProperty<Path> mBasePath = new SimpleObjectProperty<>(); | |
| 56 | private final ObjectProperty<String> mUrl = new SimpleObjectProperty<>(); | |
| 57 | ||
| 58 | public BrowseFileButton() { | |
| 59 | setGraphic( createGraphic( FILE_ALT ) ); | |
| 60 | setTooltip( new Tooltip( Messages.get( "BrowseFileButton.tooltip" ) ) ); | |
| 61 | setOnAction( this::browse ); | |
| 62 | ||
| 63 | disableProperty().bind( mBasePath.isNull() ); | |
| 64 | ||
| 65 | // workaround for a JavaFX bug: | |
| 66 | // avoid closing the dialog that contains this control when the user | |
| 67 | // closes the FileChooser or DirectoryChooser using the ESC key | |
| 68 | addEventHandler( KeyEvent.KEY_RELEASED, e -> { | |
| 69 | if( e.getCode() == KeyCode.ESCAPE ) { | |
| 70 | e.consume(); | |
| 71 | } | |
| 72 | } ); | |
| 73 | } | |
| 74 | ||
| 75 | public void addExtensionFilter( ExtensionFilter extensionFilter ) { | |
| 76 | mExtensionFilters.add( extensionFilter ); | |
| 77 | } | |
| 78 | ||
| 79 | public ObjectProperty<String> urlProperty() { | |
| 80 | return mUrl; | |
| 81 | } | |
| 82 | ||
| 83 | private void browse( ActionEvent e ) { | |
| 84 | var fileChooser = new FileChooser(); | |
| 85 | fileChooser.setTitle( Messages.get( "BrowseFileButton.chooser.title" ) ); | |
| 86 | fileChooser.getExtensionFilters().addAll( mExtensionFilters ); | |
| 87 | fileChooser.getExtensionFilters() | |
| 88 | .add( new ExtensionFilter( Messages.get( | |
| 89 | "BrowseFileButton.chooser.allFilesFilter" ), "*.*" ) ); | |
| 90 | fileChooser.setInitialDirectory( getInitialDirectory() ); | |
| 91 | var result = fileChooser.showOpenDialog( getScene().getWindow() ); | |
| 92 | if( result != null ) { | |
| 93 | updateUrl( result ); | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | private File getInitialDirectory() { | |
| 98 | //TODO build initial directory based on current value of 'url' property | |
| 99 | return getBasePath().toFile(); | |
| 100 | } | |
| 101 | ||
| 102 | private void updateUrl( File file ) { | |
| 103 | String newUrl; | |
| 104 | try { | |
| 105 | newUrl = getBasePath().relativize( file.toPath() ).toString(); | |
| 106 | } catch( final Exception ex ) { | |
| 107 | newUrl = file.toString(); | |
| 108 | } | |
| 109 | mUrl.set( newUrl.replace( '\\', '/' ) ); | |
| 110 | } | |
| 111 | ||
| 112 | public void setBasePath( Path basePath ) { | |
| 113 | this.mBasePath.set( basePath ); | |
| 114 | } | |
| 115 | ||
| 116 | private Path getBasePath() { | |
| 117 | return mBasePath.get(); | |
| 118 | } | |
| 119 | } | |
| 1 | 120 |
| 1 | /* | |
| 2 | * Copyright 2020 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | package com.keenwrite.ui.controls; | |
| 29 | ||
| 30 | import javafx.beans.property.SimpleStringProperty; | |
| 31 | import javafx.beans.property.StringProperty; | |
| 32 | import javafx.scene.control.TextField; | |
| 33 | import javafx.util.StringConverter; | |
| 34 | ||
| 35 | /** | |
| 36 | * Responsible for escaping/unescaping characters for Markdown. | |
| 37 | */ | |
| 38 | public class EscapeTextField extends TextField { | |
| 39 | ||
| 40 | public EscapeTextField() { | |
| 41 | escapedText.bindBidirectional( | |
| 42 | textProperty(), | |
| 43 | new StringConverter<>() { | |
| 44 | @Override | |
| 45 | public String toString( String object ) { | |
| 46 | return escape( object ); | |
| 47 | } | |
| 48 | ||
| 49 | @Override | |
| 50 | public String fromString( String string ) { | |
| 51 | return unescape( string ); | |
| 52 | } | |
| 53 | } | |
| 54 | ); | |
| 55 | escapeCharacters.addListener( | |
| 56 | e -> escapedText.set( escape( textProperty().get() ) ) | |
| 57 | ); | |
| 58 | } | |
| 59 | ||
| 60 | // 'escapedText' property | |
| 61 | private final StringProperty escapedText = new SimpleStringProperty(); | |
| 62 | ||
| 63 | public StringProperty escapedTextProperty() { | |
| 64 | return escapedText; | |
| 65 | } | |
| 66 | ||
| 67 | // 'escapeCharacters' property | |
| 68 | private final StringProperty escapeCharacters = new SimpleStringProperty(); | |
| 69 | ||
| 70 | public String getEscapeCharacters() { | |
| 71 | return escapeCharacters.get(); | |
| 72 | } | |
| 73 | ||
| 74 | public void setEscapeCharacters( String escapeCharacters ) { | |
| 75 | this.escapeCharacters.set( escapeCharacters ); | |
| 76 | } | |
| 77 | ||
| 78 | private String escape( final String s ) { | |
| 79 | final String escapeChars = getEscapeCharacters(); | |
| 80 | ||
| 81 | return isEmpty( escapeChars ) ? s : | |
| 82 | s.replaceAll( "([" + escapeChars.replaceAll( | |
| 83 | "(.)", | |
| 84 | "\\\\$1" ) + "])", "\\\\$1" ); | |
| 85 | } | |
| 86 | ||
| 87 | private String unescape( final String s ) { | |
| 88 | final String escapeChars = getEscapeCharacters(); | |
| 89 | ||
| 90 | return isEmpty( escapeChars ) ? s : | |
| 91 | s.replaceAll( "\\\\([" + escapeChars | |
| 92 | .replaceAll( "(.)", "\\\\$1" ) + "])", "$1" ); | |
| 93 | } | |
| 94 | ||
| 95 | private static boolean isEmpty( final String s ) { | |
| 96 | return s == null || s.isEmpty(); | |
| 97 | } | |
| 98 | } | |
| 1 | 99 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.controls; | |
| 3 | ||
| 4 | import com.keenwrite.events.StatusEvent; | |
| 5 | import org.controlsfx.control.StatusBar; | |
| 6 | import org.greenrobot.eventbus.Subscribe; | |
| 7 | ||
| 8 | import static com.keenwrite.events.Bus.register; | |
| 9 | import static javafx.application.Platform.isFxApplicationThread; | |
| 10 | import static javafx.application.Platform.runLater; | |
| 11 | ||
| 12 | /** | |
| 13 | * Responsible for handling application status events. | |
| 14 | */ | |
| 15 | public class EventedStatusBar extends StatusBar { | |
| 16 | public EventedStatusBar() { | |
| 17 | register( this ); | |
| 18 | } | |
| 19 | ||
| 20 | /** | |
| 21 | * Called when an application problem is encountered. Updates the status | |
| 22 | * bar to show the first line of the given message. This method is | |
| 23 | * idempotent (if the message text is already set to the text from the | |
| 24 | * given message, no update is performed). | |
| 25 | * | |
| 26 | * @param event The event containing information about the problem. | |
| 27 | */ | |
| 28 | @Subscribe | |
| 29 | public void handle( final StatusEvent event ) { | |
| 30 | final var m = event.toString(); | |
| 31 | ||
| 32 | // Don't burden the repaint thread if there's no status bar change. | |
| 33 | if( !getText().equals( m ) ) { | |
| 34 | final var i = m.indexOf( '\n' ); | |
| 35 | ||
| 36 | final Runnable update = | |
| 37 | () -> setText( m.substring( 0, i > 0 ? i : m.length() ) ); | |
| 38 | ||
| 39 | if( isFxApplicationThread() ) { | |
| 40 | update.run(); | |
| 41 | } | |
| 42 | else { | |
| 43 | runLater( update ); | |
| 44 | } | |
| 45 | } | |
| 46 | } | |
| 47 | } | |
| 1 | 48 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.controls; | |
| 3 | ||
| 4 | import javafx.beans.property.IntegerProperty; | |
| 5 | import javafx.beans.property.SimpleIntegerProperty; | |
| 6 | import javafx.beans.value.ChangeListener; | |
| 7 | import javafx.event.ActionEvent; | |
| 8 | import javafx.event.EventHandler; | |
| 9 | import javafx.geometry.Pos; | |
| 10 | import javafx.scene.Node; | |
| 11 | import javafx.scene.control.*; | |
| 12 | import javafx.scene.layout.HBox; | |
| 13 | import javafx.scene.layout.Region; | |
| 14 | import javafx.scene.layout.VBox; | |
| 15 | import org.controlsfx.control.textfield.CustomTextField; | |
| 16 | ||
| 17 | import static com.keenwrite.Messages.get; | |
| 18 | import static com.keenwrite.ui.fonts.IconFactory.createGraphic; | |
| 19 | import static java.lang.StrictMath.max; | |
| 20 | import static java.lang.String.format; | |
| 21 | import static javafx.geometry.Orientation.VERTICAL; | |
| 22 | import static javafx.scene.layout.Priority.ALWAYS; | |
| 23 | ||
| 24 | /** | |
| 25 | * Responsible for presenting user interface options for searching through | |
| 26 | * the document. | |
| 27 | */ | |
| 28 | public final class SearchBar extends HBox { | |
| 29 | ||
| 30 | private static final String MESSAGE_KEY = "Main.search.%s.%s"; | |
| 31 | ||
| 32 | private final Button mButtonStop = createButtonStop(); | |
| 33 | private final Button mButtonNext = createButton( "next" ); | |
| 34 | private final Button mButtonPrev = createButton( "prev" ); | |
| 35 | private final TextField mFind = createTextField(); | |
| 36 | private final Label mMatches = new Label(); | |
| 37 | private final IntegerProperty mMatchIndex = new SimpleIntegerProperty(); | |
| 38 | private final IntegerProperty mMatchCount = new SimpleIntegerProperty(); | |
| 39 | ||
| 40 | public SearchBar() { | |
| 41 | setAlignment( Pos.CENTER ); | |
| 42 | addAll( | |
| 43 | mButtonStop, | |
| 44 | createSpacer( 10 ), | |
| 45 | mFind, | |
| 46 | createSpacer( 10 ), | |
| 47 | mButtonNext, | |
| 48 | createSpacer( 10 ), | |
| 49 | mButtonPrev, | |
| 50 | createSpacer( 10 ), | |
| 51 | mMatches, | |
| 52 | createSpacer( 10 ), | |
| 53 | createSeparatorVertical(), | |
| 54 | createSpacer( 5 ) | |
| 55 | ); | |
| 56 | ||
| 57 | mMatchIndex.addListener( ( c, o, n ) -> updateMatchText() ); | |
| 58 | mMatchCount.addListener( ( c, o, n ) -> updateMatchText() ); | |
| 59 | updateMatchText(); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Gives focus to the text field. | |
| 64 | */ | |
| 65 | @Override | |
| 66 | public void requestFocus() { | |
| 67 | mFind.requestFocus(); | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Adds a listener that triggers when the input text field changes. | |
| 72 | * | |
| 73 | * @param listener The listener to notify of change events. | |
| 74 | */ | |
| 75 | public void addInputListener( final ChangeListener<String> listener ) { | |
| 76 | mFind.textProperty().addListener( listener ); | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Sets the {@link EventHandler} to call when the user interface triggers | |
| 81 | * finding the next matching search string. This will wrap from the end | |
| 82 | * to the beginning. | |
| 83 | * | |
| 84 | * @param handler The handler requested to perform the find next action. | |
| 85 | */ | |
| 86 | public void setOnNextAction( final EventHandler<ActionEvent> handler ) { | |
| 87 | mButtonNext.setOnAction( handler ); | |
| 88 | mFind.setOnAction( handler ); | |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Sets the {@link EventHandler} to call when the user interface triggers | |
| 93 | * finding the previous matching search string. This will wrap from the | |
| 94 | * beginning to the end. | |
| 95 | * | |
| 96 | * @param handler The handler requested to perform the find next action. | |
| 97 | */ | |
| 98 | public void setOnPrevAction( final EventHandler<ActionEvent> handler ) { | |
| 99 | mButtonPrev.setOnAction( handler ); | |
| 100 | } | |
| 101 | ||
| 102 | /** | |
| 103 | * Sets the {@link EventHandler} to call when searching has been terminated. | |
| 104 | * | |
| 105 | * @param handler The {@link EventHandler} that will perform an action | |
| 106 | * when the searching has stopped (e.g., remove from this | |
| 107 | * widget from status bar). | |
| 108 | */ | |
| 109 | public void setOnCancelAction( final EventHandler<ActionEvent> handler ) { | |
| 110 | mButtonStop.setOnAction( handler ); | |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * When this property value changes, the match text is updated accordingly. | |
| 115 | * If the value is less than zero, the text will show zero. | |
| 116 | * | |
| 117 | * @return The index of the latest search string match. | |
| 118 | */ | |
| 119 | public IntegerProperty matchIndexProperty() { | |
| 120 | return mMatchIndex; | |
| 121 | } | |
| 122 | ||
| 123 | /** | |
| 124 | * When this property value changes, the match text is updated accordingly. | |
| 125 | * If the value is less than zero, the text will show zero. | |
| 126 | * | |
| 127 | * @return The total number of items that match the search string. | |
| 128 | */ | |
| 129 | public IntegerProperty matchCountProperty() { | |
| 130 | return mMatchCount; | |
| 131 | } | |
| 132 | ||
| 133 | /** | |
| 134 | * Updates the match count. | |
| 135 | */ | |
| 136 | private void updateMatchText() { | |
| 137 | final var index = max( 0, mMatchIndex.get() ); | |
| 138 | final var count = max( 0, mMatchCount.get() ); | |
| 139 | final var suffix = count == 0 ? "none" : "some"; | |
| 140 | final var key = getMessageValue( "match", suffix ); | |
| 141 | ||
| 142 | mMatches.setText( get( key, index, count ) ); | |
| 143 | } | |
| 144 | ||
| 145 | private Button createButton( final String id ) { | |
| 146 | final var button = new Button(); | |
| 147 | final var tooltipText = getMessageValue( id, "tooltip" ); | |
| 148 | ||
| 149 | button.setMnemonicParsing( false ); | |
| 150 | button.setGraphic( getIcon( id ) ); | |
| 151 | button.setTooltip( new Tooltip( tooltipText ) ); | |
| 152 | ||
| 153 | return button; | |
| 154 | } | |
| 155 | ||
| 156 | private Button createButtonStop() { | |
| 157 | final var button = createButton( "stop" ); | |
| 158 | button.setCancelButton( true ); | |
| 159 | return button; | |
| 160 | } | |
| 161 | ||
| 162 | private TextField createTextField() { | |
| 163 | final var textField = new CustomTextField(); | |
| 164 | textField.setLeft( getIcon( "find" ) ); | |
| 165 | return textField; | |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * Creates a vertical bar, used to divide the search results from the | |
| 170 | * application status message. | |
| 171 | * | |
| 172 | * @return A vertical separator. | |
| 173 | */ | |
| 174 | private Node createSeparatorVertical() { | |
| 175 | return new Separator( VERTICAL ); | |
| 176 | } | |
| 177 | ||
| 178 | /** | |
| 179 | * Breathing room between the search box and the application status message. | |
| 180 | * This could also be accomplished by using CSS. | |
| 181 | * | |
| 182 | * @param width The spacer's width. | |
| 183 | * @return A new {@link Node} having about 10px of space. | |
| 184 | */ | |
| 185 | private Node createSpacer( final int width ) { | |
| 186 | final var spacer = new Region(); | |
| 187 | spacer.setPrefWidth( width ); | |
| 188 | VBox.setVgrow( spacer, ALWAYS ); | |
| 189 | return spacer; | |
| 190 | } | |
| 191 | ||
| 192 | private Node getIcon( final String id ) { | |
| 193 | return createGraphic( getMessageValue( id, "icon" ) ); | |
| 194 | } | |
| 195 | ||
| 196 | private String getMessageValue( final String id, final String suffix ) { | |
| 197 | return get( format( MESSAGE_KEY, id, suffix ) ); | |
| 198 | } | |
| 199 | ||
| 200 | private void addAll( final Node... nodes ) { | |
| 201 | getChildren().addAll( nodes ); | |
| 202 | } | |
| 203 | } | |
| 1 | 204 |
| 1 | /* Copyright 2017-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.dialogs; | |
| 3 | ||
| 4 | import com.keenwrite.service.events.impl.ButtonOrderPane; | |
| 5 | import javafx.scene.control.Dialog; | |
| 6 | import javafx.stage.Stage; | |
| 7 | import javafx.stage.Window; | |
| 8 | ||
| 9 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG; | |
| 10 | import static com.keenwrite.Messages.get; | |
| 11 | import static javafx.scene.control.ButtonType.CANCEL; | |
| 12 | import static javafx.scene.control.ButtonType.OK; | |
| 13 | ||
| 14 | /** | |
| 15 | * Superclass that abstracts common behaviours for all dialogs. | |
| 16 | * | |
| 17 | * @param <T> The type of dialog to create (usually String). | |
| 18 | */ | |
| 19 | public abstract class AbstractDialog<T> extends Dialog<T> { | |
| 20 | ||
| 21 | /** | |
| 22 | * Ensures that all dialogs can be closed. | |
| 23 | * | |
| 24 | * @param owner The parent window of this dialog. | |
| 25 | * @param title The messages title to display in the title bar. | |
| 26 | */ | |
| 27 | @SuppressWarnings( "OverridableMethodCallInConstructor" ) | |
| 28 | public AbstractDialog( final Window owner, final String title ) { | |
| 29 | setTitle( get( title ) ); | |
| 30 | setResizable( true ); | |
| 31 | ||
| 32 | initOwner( owner ); | |
| 33 | initCloseAction(); | |
| 34 | initDialogPane(); | |
| 35 | initDialogButtons(); | |
| 36 | initComponents(); | |
| 37 | initIcon( (Stage) owner ); | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * Initialize the component layout. | |
| 42 | */ | |
| 43 | protected abstract void initComponents(); | |
| 44 | ||
| 45 | /** | |
| 46 | * Set the dialog to use a button order pane with an OK and a CANCEL button. | |
| 47 | */ | |
| 48 | protected void initDialogPane() { | |
| 49 | setDialogPane( new ButtonOrderPane() ); | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Set an OK and CANCEL button on the dialog. | |
| 54 | */ | |
| 55 | protected void initDialogButtons() { | |
| 56 | getDialogPane().getButtonTypes().addAll( OK, CANCEL ); | |
| 57 | } | |
| 58 | ||
| 59 | /** | |
| 60 | * Attaches a close request to the dialog's [X] button so that the user | |
| 61 | * can always close the window, even if there's an error. | |
| 62 | */ | |
| 63 | protected final void initCloseAction() { | |
| 64 | final var window = getDialogPane().getScene().getWindow(); | |
| 65 | window.setOnCloseRequest( event -> window.hide() ); | |
| 66 | } | |
| 67 | ||
| 68 | private void initIcon( final Stage owner ) { | |
| 69 | owner.getIcons().add( ICON_DIALOG ); | |
| 70 | } | |
| 71 | } | |
| 1 | 72 |
| 1 | /* | |
| 2 | * Copyright 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | package com.keenwrite.ui.dialogs; | |
| 28 | ||
| 29 | import static com.keenwrite.Messages.get; | |
| 30 | import com.keenwrite.ui.controls.BrowseFileButton; | |
| 31 | import com.keenwrite.ui.controls.EscapeTextField; | |
| 32 | import java.nio.file.Path; | |
| 33 | import javafx.application.Platform; | |
| 34 | import javafx.beans.binding.Bindings; | |
| 35 | import javafx.beans.property.SimpleStringProperty; | |
| 36 | import javafx.beans.property.StringProperty; | |
| 37 | import javafx.scene.control.ButtonBar.ButtonData; | |
| 38 | import static javafx.scene.control.ButtonType.OK; | |
| 39 | import javafx.scene.control.DialogPane; | |
| 40 | import javafx.scene.control.Label; | |
| 41 | import javafx.stage.FileChooser.ExtensionFilter; | |
| 42 | import javafx.stage.Window; | |
| 43 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 44 | ||
| 45 | /** | |
| 46 | * Dialog to enter a Markdown image. | |
| 47 | */ | |
| 48 | public class ImageDialog extends AbstractDialog<String> { | |
| 49 | ||
| 50 | private final StringProperty image = new SimpleStringProperty(); | |
| 51 | ||
| 52 | public ImageDialog( final Window owner, final Path basePath ) { | |
| 53 | super(owner, "Dialog.image.title" ); | |
| 54 | ||
| 55 | final DialogPane dialogPane = getDialogPane(); | |
| 56 | dialogPane.setContent( pane ); | |
| 57 | ||
| 58 | linkBrowseFileButton.setBasePath( basePath ); | |
| 59 | linkBrowseFileButton.addExtensionFilter( new ExtensionFilter( get( "Dialog.image.chooser.imagesFilter" ), "*.png", "*.gif", "*.jpg" ) ); | |
| 60 | linkBrowseFileButton.urlProperty().bindBidirectional( urlField.escapedTextProperty() ); | |
| 61 | ||
| 62 | dialogPane.lookupButton( OK ).disableProperty().bind( | |
| 63 | urlField.escapedTextProperty().isEmpty() | |
| 64 | .or( textField.escapedTextProperty().isEmpty() ) ); | |
| 65 | ||
| 66 | image.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | |
| 67 | .then( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | |
| 68 | .otherwise( Bindings.format( "", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) ); | |
| 69 | previewField.textProperty().bind( image ); | |
| 70 | ||
| 71 | setResultConverter( dialogButton -> { | |
| 72 | ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | |
| 73 | return (data == ButtonData.OK_DONE) ? image.get() : null; | |
| 74 | } ); | |
| 75 | ||
| 76 | Platform.runLater( () -> { | |
| 77 | urlField.requestFocus(); | |
| 78 | ||
| 79 | if( urlField.getText().startsWith( "http://" ) ) { | |
| 80 | urlField.selectRange( "http://".length(), urlField.getLength() ); | |
| 81 | } | |
| 82 | } ); | |
| 83 | } | |
| 84 | ||
| 85 | @Override | |
| 86 | protected void initComponents() { | |
| 87 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 88 | pane = new MigPane(); | |
| 89 | Label urlLabel = new Label(); | |
| 90 | urlField = new EscapeTextField(); | |
| 91 | linkBrowseFileButton = new BrowseFileButton(); | |
| 92 | Label textLabel = new Label(); | |
| 93 | textField = new EscapeTextField(); | |
| 94 | Label titleLabel = new Label(); | |
| 95 | titleField = new EscapeTextField(); | |
| 96 | Label previewLabel = new Label(); | |
| 97 | previewField = new Label(); | |
| 98 | ||
| 99 | //======== pane ======== | |
| 100 | { | |
| 101 | pane.setCols( "[shrink 0,fill][300,grow,fill][fill]" ); | |
| 102 | pane.setRows( "[][][][]" ); | |
| 103 | ||
| 104 | //---- urlLabel ---- | |
| 105 | urlLabel.setText( get( "Dialog.image.urlLabel.text" ) ); | |
| 106 | pane.add( urlLabel, "cell 0 0" ); | |
| 107 | ||
| 108 | //---- urlField ---- | |
| 109 | urlField.setEscapeCharacters( "()" ); | |
| 110 | urlField.setText( "http://yourlink.com" ); | |
| 111 | urlField.setPromptText( "http://yourlink.com" ); | |
| 112 | pane.add( urlField, "cell 1 0" ); | |
| 113 | pane.add( linkBrowseFileButton, "cell 2 0" ); | |
| 114 | ||
| 115 | //---- textLabel ---- | |
| 116 | textLabel.setText( get( "Dialog.image.textLabel.text" ) ); | |
| 117 | pane.add( textLabel, "cell 0 1" ); | |
| 118 | ||
| 119 | //---- textField ---- | |
| 120 | textField.setEscapeCharacters( "[]" ); | |
| 121 | pane.add( textField, "cell 1 1 2 1" ); | |
| 122 | ||
| 123 | //---- titleLabel ---- | |
| 124 | titleLabel.setText( get( "Dialog.image.titleLabel.text" ) ); | |
| 125 | pane.add( titleLabel, "cell 0 2" ); | |
| 126 | pane.add( titleField, "cell 1 2 2 1" ); | |
| 127 | ||
| 128 | //---- previewLabel ---- | |
| 129 | previewLabel.setText( get( "Dialog.image.previewLabel.text" ) ); | |
| 130 | pane.add( previewLabel, "cell 0 3" ); | |
| 131 | pane.add( previewField, "cell 1 3 2 1" ); | |
| 132 | } | |
| 133 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 134 | } | |
| 135 | ||
| 136 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 137 | private MigPane pane; | |
| 138 | private EscapeTextField urlField; | |
| 139 | private BrowseFileButton linkBrowseFileButton; | |
| 140 | private EscapeTextField textField; | |
| 141 | private EscapeTextField titleField; | |
| 142 | private Label previewField; | |
| 143 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 144 | } | |
| 1 | 145 |
| 1 | JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8" | |
| 2 | ||
| 3 | new FormModel { | |
| 4 | "i18n.bundlePackage": "com.scrivendor" | |
| 5 | "i18n.bundleName": "messages" | |
| 6 | "i18n.autoExternalize": true | |
| 7 | "i18n.keyPrefix": "ImageDialog" | |
| 8 | contentType: "form/javafx" | |
| 9 | root: new FormRoot { | |
| 10 | add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) { | |
| 11 | "$layoutConstraints": "" | |
| 12 | "$columnConstraints": "[shrink 0,fill][300,grow,fill][fill]" | |
| 13 | "$rowConstraints": "[][][][]" | |
| 14 | } ) { | |
| 15 | name: "pane" | |
| 16 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 17 | name: "urlLabel" | |
| 18 | "text": new FormMessage( null, "ImageDialog.urlLabel.text" ) | |
| 19 | auxiliary() { | |
| 20 | "JavaCodeGenerator.variableLocal": true | |
| 21 | } | |
| 22 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 23 | "value": "cell 0 0" | |
| 24 | } ) | |
| 25 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 26 | name: "urlField" | |
| 27 | "escapeCharacters": "()" | |
| 28 | "text": "http://yourlink.com" | |
| 29 | "promptText": "http://yourlink.com" | |
| 30 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 31 | "value": "cell 1 0" | |
| 32 | } ) | |
| 33 | add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) { | |
| 34 | name: "linkBrowseFileButton" | |
| 35 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 36 | "value": "cell 2 0" | |
| 37 | } ) | |
| 38 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 39 | name: "textLabel" | |
| 40 | "text": new FormMessage( null, "ImageDialog.textLabel.text" ) | |
| 41 | auxiliary() { | |
| 42 | "JavaCodeGenerator.variableLocal": true | |
| 43 | } | |
| 44 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 45 | "value": "cell 0 1" | |
| 46 | } ) | |
| 47 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 48 | name: "textField" | |
| 49 | "escapeCharacters": "[]" | |
| 50 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 51 | "value": "cell 1 1 2 1" | |
| 52 | } ) | |
| 53 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 54 | name: "titleLabel" | |
| 55 | "text": new FormMessage( null, "ImageDialog.titleLabel.text" ) | |
| 56 | auxiliary() { | |
| 57 | "JavaCodeGenerator.variableLocal": true | |
| 58 | } | |
| 59 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 60 | "value": "cell 0 2" | |
| 61 | } ) | |
| 62 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 63 | name: "titleField" | |
| 64 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 65 | "value": "cell 1 2 2 1" | |
| 66 | } ) | |
| 67 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 68 | name: "previewLabel" | |
| 69 | "text": new FormMessage( null, "ImageDialog.previewLabel.text" ) | |
| 70 | auxiliary() { | |
| 71 | "JavaCodeGenerator.variableLocal": true | |
| 72 | } | |
| 73 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 74 | "value": "cell 0 3" | |
| 75 | } ) | |
| 76 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 77 | name: "previewField" | |
| 78 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 79 | "value": "cell 1 3 2 1" | |
| 80 | } ) | |
| 81 | }, new FormLayoutConstraints( null ) { | |
| 82 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 83 | "size": new javafx.geometry.Dimension2D( 500.0, 300.0 ) | |
| 84 | } ) | |
| 85 | } | |
| 86 | } | |
| 1 | 87 |
| 1 | /* | |
| 2 | * Copyright 2016 Karl Tauber and White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.keenwrite.ui.dialogs; | |
| 29 | ||
| 30 | import com.keenwrite.ui.controls.EscapeTextField; | |
| 31 | import com.keenwrite.editors.markdown.HyperlinkModel; | |
| 32 | import javafx.application.Platform; | |
| 33 | import javafx.beans.binding.Bindings; | |
| 34 | import javafx.beans.property.SimpleStringProperty; | |
| 35 | import javafx.beans.property.StringProperty; | |
| 36 | import javafx.scene.control.ButtonBar.ButtonData; | |
| 37 | import javafx.scene.control.DialogPane; | |
| 38 | import javafx.scene.control.Label; | |
| 39 | import javafx.stage.Window; | |
| 40 | import org.tbee.javafx.scene.layout.fxml.MigPane; | |
| 41 | ||
| 42 | import static com.keenwrite.Messages.get; | |
| 43 | import static javafx.scene.control.ButtonType.OK; | |
| 44 | ||
| 45 | /** | |
| 46 | * Dialog to enter a Markdown link. | |
| 47 | */ | |
| 48 | public class LinkDialog extends AbstractDialog<String> { | |
| 49 | ||
| 50 | private final StringProperty link = new SimpleStringProperty(); | |
| 51 | ||
| 52 | public LinkDialog( | |
| 53 | final Window owner, final HyperlinkModel hyperlink ) { | |
| 54 | super( owner, "Dialog.link.title" ); | |
| 55 | ||
| 56 | final DialogPane dialogPane = getDialogPane(); | |
| 57 | dialogPane.setContent( pane ); | |
| 58 | ||
| 59 | dialogPane.lookupButton( OK ).disableProperty().bind( | |
| 60 | urlField.escapedTextProperty().isEmpty() ); | |
| 61 | ||
| 62 | textField.setText( hyperlink.getText() ); | |
| 63 | urlField.setText( hyperlink.getUrl() ); | |
| 64 | titleField.setText( hyperlink.getTitle() ); | |
| 65 | ||
| 66 | link.bind( Bindings.when( titleField.escapedTextProperty().isNotEmpty() ) | |
| 67 | .then( Bindings.format( "[%s](%s \"%s\")", textField.escapedTextProperty(), urlField.escapedTextProperty(), titleField.escapedTextProperty() ) ) | |
| 68 | .otherwise( Bindings.when( textField.escapedTextProperty().isNotEmpty() ) | |
| 69 | .then( Bindings.format( "[%s](%s)", textField.escapedTextProperty(), urlField.escapedTextProperty() ) ) | |
| 70 | .otherwise( urlField.escapedTextProperty() ) ) ); | |
| 71 | ||
| 72 | setResultConverter( dialogButton -> { | |
| 73 | ButtonData data = (dialogButton != null) ? dialogButton.getButtonData() : null; | |
| 74 | return (data == ButtonData.OK_DONE) ? link.get() : null; | |
| 75 | } ); | |
| 76 | ||
| 77 | Platform.runLater( () -> { | |
| 78 | urlField.requestFocus(); | |
| 79 | urlField.selectRange( 0, urlField.getLength() ); | |
| 80 | } ); | |
| 81 | } | |
| 82 | ||
| 83 | @Override | |
| 84 | protected void initComponents() { | |
| 85 | // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents | |
| 86 | pane = new MigPane(); | |
| 87 | Label urlLabel = new Label(); | |
| 88 | urlField = new EscapeTextField(); | |
| 89 | Label textLabel = new Label(); | |
| 90 | textField = new EscapeTextField(); | |
| 91 | Label titleLabel = new Label(); | |
| 92 | titleField = new EscapeTextField(); | |
| 93 | ||
| 94 | //======== pane ======== | |
| 95 | { | |
| 96 | pane.setCols( "[shrink 0,fill][300,grow,fill][fill][fill]" ); | |
| 97 | pane.setRows( "[][][][]" ); | |
| 98 | ||
| 99 | //---- urlLabel ---- | |
| 100 | urlLabel.setText( get( "Dialog.link.urlLabel.text" ) ); | |
| 101 | pane.add( urlLabel, "cell 0 0" ); | |
| 102 | ||
| 103 | //---- urlField ---- | |
| 104 | urlField.setEscapeCharacters( "()" ); | |
| 105 | pane.add( urlField, "cell 1 0" ); | |
| 106 | ||
| 107 | //---- textLabel ---- | |
| 108 | textLabel.setText( get( "Dialog.link.textLabel.text" ) ); | |
| 109 | pane.add( textLabel, "cell 0 1" ); | |
| 110 | ||
| 111 | //---- textField ---- | |
| 112 | textField.setEscapeCharacters( "[]" ); | |
| 113 | pane.add( textField, "cell 1 1 3 1" ); | |
| 114 | ||
| 115 | //---- titleLabel ---- | |
| 116 | titleLabel.setText( get( "Dialog.link.titleLabel.text" ) ); | |
| 117 | pane.add( titleLabel, "cell 0 2" ); | |
| 118 | pane.add( titleField, "cell 1 2 3 1" ); | |
| 119 | } | |
| 120 | // JFormDesigner - End of component initialization //GEN-END:initComponents | |
| 121 | } | |
| 122 | ||
| 123 | // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables | |
| 124 | private MigPane pane; | |
| 125 | private EscapeTextField urlField; | |
| 126 | private EscapeTextField textField; | |
| 127 | private EscapeTextField titleField; | |
| 128 | // JFormDesigner - End of variables declaration //GEN-END:variables | |
| 129 | } | |
| 1 | 130 |
| 1 | JFDML JFormDesigner: "9.9.9.9.9999" Java: "1.8.0_66" encoding: "UTF-8" | |
| 2 | ||
| 3 | new FormModel { | |
| 4 | "i18n.bundlePackage": "com.scrivendor" | |
| 5 | "i18n.bundleName": "messages" | |
| 6 | "i18n.autoExternalize": true | |
| 7 | "i18n.keyPrefix": "LinkDialog" | |
| 8 | contentType: "form/javafx" | |
| 9 | root: new FormRoot { | |
| 10 | add( new FormContainer( "org.tbee.javafx.scene.layout.fxml.MigPane", new FormLayoutManager( class org.tbee.javafx.scene.layout.fxml.MigPane ) { | |
| 11 | "$layoutConstraints": "" | |
| 12 | "$columnConstraints": "[shrink 0,fill][300,grow,fill][fill][fill]" | |
| 13 | "$rowConstraints": "[][][][]" | |
| 14 | } ) { | |
| 15 | name: "pane" | |
| 16 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 17 | name: "urlLabel" | |
| 18 | "text": new FormMessage( null, "LinkDialog.urlLabel.text" ) | |
| 19 | auxiliary() { | |
| 20 | "JavaCodeGenerator.variableLocal": true | |
| 21 | } | |
| 22 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 23 | "value": "cell 0 0" | |
| 24 | } ) | |
| 25 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 26 | name: "urlField" | |
| 27 | "escapeCharacters": "()" | |
| 28 | "text": "http://yourlink.com" | |
| 29 | "promptText": "http://yourlink.com" | |
| 30 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 31 | "value": "cell 1 0" | |
| 32 | } ) | |
| 33 | add( new FormComponent( "com.scrivendor.controls.BrowseDirectoryButton" ) { | |
| 34 | name: "linkBrowseDirectoyButton" | |
| 35 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 36 | "value": "cell 2 0" | |
| 37 | } ) | |
| 38 | add( new FormComponent( "com.scrivendor.controls.BrowseFileButton" ) { | |
| 39 | name: "linkBrowseFileButton" | |
| 40 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 41 | "value": "cell 3 0" | |
| 42 | } ) | |
| 43 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 44 | name: "textLabel" | |
| 45 | "text": new FormMessage( null, "LinkDialog.textLabel.text" ) | |
| 46 | auxiliary() { | |
| 47 | "JavaCodeGenerator.variableLocal": true | |
| 48 | } | |
| 49 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 50 | "value": "cell 0 1" | |
| 51 | } ) | |
| 52 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 53 | name: "textField" | |
| 54 | "escapeCharacters": "[]" | |
| 55 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 56 | "value": "cell 1 1 3 1" | |
| 57 | } ) | |
| 58 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 59 | name: "titleLabel" | |
| 60 | "text": new FormMessage( null, "LinkDialog.titleLabel.text" ) | |
| 61 | auxiliary() { | |
| 62 | "JavaCodeGenerator.variableLocal": true | |
| 63 | } | |
| 64 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 65 | "value": "cell 0 2" | |
| 66 | } ) | |
| 67 | add( new FormComponent( "com.scrivendor.controls.EscapeTextField" ) { | |
| 68 | name: "titleField" | |
| 69 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 70 | "value": "cell 1 2 3 1" | |
| 71 | } ) | |
| 72 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 73 | name: "previewLabel" | |
| 74 | "text": new FormMessage( null, "LinkDialog.previewLabel.text" ) | |
| 75 | auxiliary() { | |
| 76 | "JavaCodeGenerator.variableLocal": true | |
| 77 | } | |
| 78 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 79 | "value": "cell 0 3" | |
| 80 | } ) | |
| 81 | add( new FormComponent( "javafx.scene.control.Label" ) { | |
| 82 | name: "previewField" | |
| 83 | }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { | |
| 84 | "value": "cell 1 3 3 1" | |
| 85 | } ) | |
| 86 | }, new FormLayoutConstraints( null ) { | |
| 87 | "location": new javafx.geometry.Point2D( 0.0, 0.0 ) | |
| 88 | "size": new javafx.geometry.Dimension2D( 500.0, 300.0 ) | |
| 89 | } ) | |
| 90 | } | |
| 91 | } | |
| 1 | 92 |
| 1 | /* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.dialogs; | |
| 3 | ||
| 4 | import com.keenwrite.util.FileWalker; | |
| 5 | import com.keenwrite.util.ResourceWalker; | |
| 6 | import javafx.beans.property.StringProperty; | |
| 7 | import javafx.scene.control.ChoiceDialog; | |
| 8 | import javafx.scene.control.ComboBox; | |
| 9 | import javafx.scene.image.Image; | |
| 10 | import javafx.scene.input.KeyCode; | |
| 11 | import javafx.stage.Stage; | |
| 12 | ||
| 13 | import java.io.File; | |
| 14 | import java.io.FileInputStream; | |
| 15 | import java.io.IOException; | |
| 16 | import java.io.InputStreamReader; | |
| 17 | import java.nio.charset.StandardCharsets; | |
| 18 | import java.nio.file.Path; | |
| 19 | import java.util.Properties; | |
| 20 | import java.util.TreeMap; | |
| 21 | ||
| 22 | import static com.keenwrite.Messages.get; | |
| 23 | import static com.keenwrite.constants.Constants.THEME_NAME_LENGTH; | |
| 24 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG; | |
| 25 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG_NODE; | |
| 26 | import static com.keenwrite.events.StatusEvent.clue; | |
| 27 | import static com.keenwrite.util.FileWalker.walk; | |
| 28 | import static java.lang.Math.max; | |
| 29 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 30 | import static org.codehaus.plexus.util.StringUtils.abbreviate; | |
| 31 | ||
| 32 | /** | |
| 33 | * Responsible for allowing the user to pick from the available themes found | |
| 34 | * in the system. | |
| 35 | */ | |
| 36 | public class ThemePicker extends ChoiceDialog<String> { | |
| 37 | private final File mThemes; | |
| 38 | private final StringProperty mTheme; | |
| 39 | ||
| 40 | /** | |
| 41 | * Construction must use static method to allow caching themes in the | |
| 42 | * future, if needed. | |
| 43 | * | |
| 44 | * @see #choose(File, StringProperty) | |
| 45 | */ | |
| 46 | @SuppressWarnings( "rawtypes" ) | |
| 47 | private ThemePicker( final File themes, final StringProperty theme ) { | |
| 48 | assert themes != null; | |
| 49 | assert theme != null; | |
| 50 | ||
| 51 | mThemes = themes; | |
| 52 | mTheme = theme; | |
| 53 | initIcon(); | |
| 54 | setTitle( get( "Dialog.theme.title" ) ); | |
| 55 | setHeaderText( get( "Dialog.theme.header" ) ); | |
| 56 | ||
| 57 | final var options = (ComboBox) getDialogPane().lookup( ".combo-box" ); | |
| 58 | options.setOnKeyPressed( ( event ) -> { | |
| 59 | // When the user presses the down arrow, open the drop-down. This prevents | |
| 60 | // navigating to the cancel button. | |
| 61 | if( event.getCode() == KeyCode.DOWN && !options.isShowing() ) { | |
| 62 | options.show(); | |
| 63 | event.consume(); | |
| 64 | } | |
| 65 | } ); | |
| 66 | } | |
| 67 | ||
| 68 | private void initIcon() { | |
| 69 | setGraphic( ICON_DIALOG_NODE ); | |
| 70 | setStageGraphic( ICON_DIALOG ); | |
| 71 | } | |
| 72 | ||
| 73 | @SuppressWarnings( "SameParameterValue" ) | |
| 74 | private void setStageGraphic( final Image icon ) { | |
| 75 | if( getDialogPane().getScene().getWindow() instanceof final Stage stage ) { | |
| 76 | stage.getIcons().add( icon ); | |
| 77 | } | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Prompts a user to select a theme, answering {@code false} if no theme | |
| 82 | * was selected. The themes must be on the native file system; using the | |
| 83 | * {@link FileWalker} is a little more optimal than {@link ResourceWalker}. | |
| 84 | * | |
| 85 | * @param themes Theme directory root. | |
| 86 | * @param theme Selected theme property name. | |
| 87 | * @return {@code true} if the user accepted or selected a theme. | |
| 88 | */ | |
| 89 | public static boolean choose( | |
| 90 | final File themes, final StringProperty theme ) { | |
| 91 | assert themes != null; | |
| 92 | assert theme != null; | |
| 93 | ||
| 94 | return new ThemePicker( themes, theme ).pick(); | |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * @return {@code true} if the user accepted or selected a theme. | |
| 99 | * @see #choose(File, StringProperty) | |
| 100 | */ | |
| 101 | private boolean pick() { | |
| 102 | try { | |
| 103 | // List themes in alphabetical order (human readable by directory name). | |
| 104 | final var choices = new TreeMap<String, String>(); | |
| 105 | final String[] selection = new String[]{""}; | |
| 106 | ||
| 107 | // Populate the choices with themes detected on the system. | |
| 108 | walk( mThemes.toPath(), "**/theme.properties", ( path ) -> { | |
| 109 | try { | |
| 110 | final var displayed = readThemeName( path ); | |
| 111 | final var themeName = path.getParent().toFile().getName(); | |
| 112 | choices.put( abbreviate( displayed, THEME_NAME_LENGTH ), themeName ); | |
| 113 | ||
| 114 | // Set the selected item to user's settings value. | |
| 115 | if( themeName.equals( mTheme.get() ) ) { | |
| 116 | selection[ 0 ] = displayed; | |
| 117 | } | |
| 118 | } catch( final Exception ex ) { | |
| 119 | clue( "Main.status.error.theme.name", path ); | |
| 120 | } | |
| 121 | } ); | |
| 122 | ||
| 123 | final var items = getItems(); | |
| 124 | items.addAll( choices.keySet() ); | |
| 125 | setSelectedItem( items.get( max( items.indexOf( selection[ 0 ] ), 0 ) ) ); | |
| 126 | ||
| 127 | final var result = showAndWait(); | |
| 128 | ||
| 129 | if( result.isPresent() ) { | |
| 130 | mTheme.set( choices.get( result.get() ) ); | |
| 131 | return true; | |
| 132 | } | |
| 133 | } catch( final Exception ex ) { | |
| 134 | clue( get( "Main.status.error.theme.missing", mThemes ), ex ); | |
| 135 | } | |
| 136 | ||
| 137 | return false; | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Returns the theme's human-friendly name from a file conforming to | |
| 142 | * {@link Properties}. | |
| 143 | * | |
| 144 | * @param file A fully qualified file name readable using {@link Properties}. | |
| 145 | * @return The human-friendly theme name. | |
| 146 | * @throws IOException The {@link Properties} file cannot be read. | |
| 147 | * @throws NullPointerException The name field is not defined. | |
| 148 | */ | |
| 149 | private String readThemeName( final Path file ) throws Exception { | |
| 150 | return read( file ).get( "name" ).toString(); | |
| 151 | } | |
| 152 | ||
| 153 | /** | |
| 154 | * Reads an instance of {@link Properties} from the given {@link Path} using | |
| 155 | * {@link StandardCharsets#UTF_8} encoding. | |
| 156 | * | |
| 157 | * @param path The fully qualified path to the file. | |
| 158 | * @return The path to the file to read. | |
| 159 | * @throws IOException Could not open the file for reading. | |
| 160 | */ | |
| 161 | private Properties read( final Path path ) throws IOException { | |
| 162 | final var properties = new Properties(); | |
| 163 | ||
| 164 | try( final var in = new InputStreamReader( | |
| 165 | new FileInputStream( path.toFile() ), UTF_8 ) ) { | |
| 166 | properties.load( in ); | |
| 167 | } | |
| 168 | ||
| 169 | return properties; | |
| 170 | } | |
| 171 | } | |
| 1 | 172 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.explorer; | |
| 3 | ||
| 4 | import java.io.File; | |
| 5 | import java.nio.file.Path; | |
| 6 | import java.util.List; | |
| 7 | import java.util.Optional; | |
| 8 | ||
| 9 | /** | |
| 10 | * Responsible for providing the user with a way to select a file. | |
| 11 | */ | |
| 12 | public interface FilePicker { | |
| 13 | ||
| 14 | /** | |
| 15 | * Establishes the default file name to use when the UI is displayed. The | |
| 16 | * path portion of the file, if any, is ignored. | |
| 17 | * | |
| 18 | * @param file The initial {@link File} to choose when prompting the user | |
| 19 | * to select a file. | |
| 20 | */ | |
| 21 | default void setInitialFilename( File file ) {} | |
| 22 | ||
| 23 | /** | |
| 24 | * Establishes the directory to browse when the UI is displayed. | |
| 25 | * | |
| 26 | * @param path The initial {@link Path} to use when navigating the system. | |
| 27 | */ | |
| 28 | default void setInitialDirectory( Path path ) {} | |
| 29 | ||
| 30 | /** | |
| 31 | * Sets the list of file names to display. For example, a single call to | |
| 32 | * this method with values of ("**.pdf", "Portable Document Format (PDF)") | |
| 33 | * would display only a file listing of PDF files. | |
| 34 | * | |
| 35 | * @param glob Pattern that allows matching file names to be listed. | |
| 36 | * @param text Human-readable description of the pattern. | |
| 37 | */ | |
| 38 | default void addIncludeFileFilter( String glob, String text ) {} | |
| 39 | ||
| 40 | /** | |
| 41 | * Sets the list of file names to suppress. For example, a single call to | |
| 42 | * this method with values of (".*") would prevent listing files that begin | |
| 43 | * with a period. | |
| 44 | * | |
| 45 | * @param glob Pattern that allows matching file names to be suppressed. | |
| 46 | */ | |
| 47 | default void addExcludeFileFilter( String glob ) {} | |
| 48 | ||
| 49 | /** | |
| 50 | * Returns the list of {@link File} objects selected by the user. | |
| 51 | * | |
| 52 | * @return A list of {@link File} objects, empty when nothing was selected. | |
| 53 | */ | |
| 54 | Optional<List<File>> choose(); | |
| 55 | } | |
| 1 | 56 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.explorer; | |
| 3 | ||
| 4 | import com.io7m.jwheatsheaf.ui.JWFileChoosers; | |
| 5 | import com.keenwrite.preferences.Workspace; | |
| 6 | import javafx.beans.property.ObjectProperty; | |
| 7 | import javafx.scene.Node; | |
| 8 | import javafx.stage.FileChooser; | |
| 9 | import javafx.stage.Window; | |
| 10 | ||
| 11 | import java.io.File; | |
| 12 | import java.nio.file.Path; | |
| 13 | import java.util.ArrayList; | |
| 14 | import java.util.List; | |
| 15 | import java.util.Locale; | |
| 16 | import java.util.Optional; | |
| 17 | ||
| 18 | import static com.io7m.jwheatsheaf.api.JWFileChooserAction.*; | |
| 19 | import static com.io7m.jwheatsheaf.api.JWFileChooserConfiguration.Builder; | |
| 20 | import static com.io7m.jwheatsheaf.api.JWFileChooserConfiguration.builder; | |
| 21 | import static com.keenwrite.constants.Constants.USER_DIRECTORY; | |
| 22 | import static com.keenwrite.events.StatusEvent.clue; | |
| 23 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_UI_RECENT_DIR; | |
| 24 | import static java.nio.file.FileSystems.getDefault; | |
| 25 | import static java.util.Optional.ofNullable; | |
| 26 | ||
| 27 | /** | |
| 28 | * Shim for a {@link FilePicker} instance that is implemented in pure Java. | |
| 29 | * This particular picker is added to avoid using the bug-ridden JavaFX | |
| 30 | * {@link FileChooser} that invokes the native file chooser. | |
| 31 | */ | |
| 32 | public class FilePickerFactory { | |
| 33 | public enum Options { | |
| 34 | DIRECTORY_OPEN, | |
| 35 | FILE_IMPORT, | |
| 36 | FILE_EXPORT, | |
| 37 | FILE_OPEN_SINGLE, | |
| 38 | FILE_OPEN_MULTIPLE, | |
| 39 | FILE_OPEN_NEW, | |
| 40 | FILE_SAVE_AS, | |
| 41 | PERMIT_CREATE_DIRS, | |
| 42 | } | |
| 43 | ||
| 44 | private final ObjectProperty<File> mDirectory; | |
| 45 | private final Locale mLocale; | |
| 46 | ||
| 47 | public FilePickerFactory( final Workspace workspace ) { | |
| 48 | mDirectory = workspace.fileProperty( KEY_UI_RECENT_DIR ); | |
| 49 | mLocale = workspace.getLocale(); | |
| 50 | } | |
| 51 | ||
| 52 | public FilePicker createModal( | |
| 53 | final Window owner, final Options... options ) { | |
| 54 | final var picker = new PureFilePicker( owner, options ); | |
| 55 | picker.setInitialDirectory( mDirectory.get().toPath() ); | |
| 56 | ||
| 57 | return picker; | |
| 58 | } | |
| 59 | ||
| 60 | public Node createModeless() { | |
| 61 | return new FilesView( mDirectory, mLocale ); | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Pure Java implementation of a file selection widget. | |
| 66 | */ | |
| 67 | private class PureFilePicker implements FilePicker { | |
| 68 | private final Window mParent; | |
| 69 | private final Builder mBuilder; | |
| 70 | ||
| 71 | private PureFilePicker( final Window window, final Options... options ) { | |
| 72 | mParent = window; | |
| 73 | mBuilder = builder().setFileSystem( getDefault() ); | |
| 74 | ||
| 75 | final var args = ofNullable( options ).orElse( options ); | |
| 76 | ||
| 77 | var title = "Dialog.file.choose.open.title"; | |
| 78 | var action = OPEN_EXISTING_SINGLE; | |
| 79 | ||
| 80 | // It is a programming error to provide options that save or export to | |
| 81 | // multiple files. | |
| 82 | for( final var arg : args ) { | |
| 83 | switch( arg ) { | |
| 84 | case FILE_EXPORT -> { | |
| 85 | title = "Dialog.file.choose.export.title"; | |
| 86 | action = CREATE; | |
| 87 | } | |
| 88 | case FILE_SAVE_AS -> { | |
| 89 | title = "Dialog.file.choose.save.title"; | |
| 90 | action = CREATE; | |
| 91 | } | |
| 92 | case FILE_OPEN_SINGLE -> action = OPEN_EXISTING_SINGLE; | |
| 93 | case FILE_OPEN_MULTIPLE -> action = OPEN_EXISTING_MULTIPLE; | |
| 94 | case PERMIT_CREATE_DIRS -> mBuilder.setAllowDirectoryCreation( true ); | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | //mBuilder.setTitle( get( title ) ); | |
| 99 | mBuilder.setAction( action ); | |
| 100 | } | |
| 101 | ||
| 102 | @Override | |
| 103 | public void setInitialDirectory( final Path path ) { | |
| 104 | mBuilder.setInitialDirectory( path ); | |
| 105 | } | |
| 106 | ||
| 107 | // private JWFileChooserFilterType createFileFilters() { | |
| 108 | // final var filters = new JWFilterGlobFactory(); | |
| 109 | // | |
| 110 | // return filters.create( "PDF Files" ) | |
| 111 | // .addRule( INCLUDE, "**/*.pdf" ) | |
| 112 | // .addRule( EXCLUDE_AND_HALT, "**/.*" ) | |
| 113 | // .build(); | |
| 114 | // } | |
| 115 | ||
| 116 | @Override | |
| 117 | public Optional<List<File>> choose() { | |
| 118 | final var config = mBuilder.build(); | |
| 119 | try( final var chooserType = JWFileChoosers.create() ) { | |
| 120 | final var chooser = chooserType.create( mParent, config ); | |
| 121 | final var paths = chooser.showAndWait(); | |
| 122 | final var files = new ArrayList<File>( paths.size() ); | |
| 123 | paths.forEach( path -> { | |
| 124 | final var file = path.toFile(); | |
| 125 | files.add( file ); | |
| 126 | ||
| 127 | // Set to the directory of the last file opened successfully. | |
| 128 | setRecentDirectory( file ); | |
| 129 | } ); | |
| 130 | ||
| 131 | return files.isEmpty() ? Optional.empty() : Optional.of( files ); | |
| 132 | } catch( final Exception ex ) { | |
| 133 | clue( ex ); | |
| 134 | } | |
| 135 | ||
| 136 | return Optional.empty(); | |
| 137 | } | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Sets the value for the most recent directly selected. This will get the | |
| 142 | * parent location from the given file. If the parent is a readable directory | |
| 143 | * then this will update the most recent directory property. | |
| 144 | * | |
| 145 | * @param file A file contained in a directory. | |
| 146 | */ | |
| 147 | private void setRecentDirectory( final File file ) { | |
| 148 | assert file != null; | |
| 149 | ||
| 150 | final var parent = file.getParentFile(); | |
| 151 | final var dir = parent == null ? USER_DIRECTORY : parent; | |
| 152 | ||
| 153 | if( dir.isDirectory() && dir.canRead() ) { | |
| 154 | mDirectory.setValue( dir ); | |
| 155 | } | |
| 156 | } | |
| 157 | } | |
| 1 | 158 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.explorer; | |
| 3 | ||
| 4 | import com.keenwrite.ui.controls.BrowseButton; | |
| 5 | import javafx.beans.property.*; | |
| 6 | import javafx.collections.ObservableList; | |
| 7 | import javafx.collections.transformation.SortedList; | |
| 8 | import javafx.scene.control.*; | |
| 9 | import javafx.scene.layout.BorderPane; | |
| 10 | import javafx.scene.layout.HBox; | |
| 11 | import javafx.stage.FileChooser; | |
| 12 | import javafx.util.Callback; | |
| 13 | ||
| 14 | import java.io.File; | |
| 15 | import java.io.IOException; | |
| 16 | import java.nio.file.Path; | |
| 17 | import java.nio.file.Paths; | |
| 18 | import java.time.Instant; | |
| 19 | import java.time.format.DateTimeFormatter; | |
| 20 | import java.util.List; | |
| 21 | import java.util.Locale; | |
| 22 | import java.util.Optional; | |
| 23 | ||
| 24 | import static com.keenwrite.constants.Constants.UI_CONTROL_SPACING; | |
| 25 | import static com.keenwrite.events.FileOpenEvent.fireFileOpenEvent; | |
| 26 | import static com.keenwrite.events.StatusEvent.clue; | |
| 27 | import static com.keenwrite.ui.fonts.IconFactory.createFileIcon; | |
| 28 | import static java.nio.file.Files.size; | |
| 29 | import static java.time.Instant.ofEpochMilli; | |
| 30 | import static java.time.ZoneId.systemDefault; | |
| 31 | import static java.time.format.DateTimeFormatter.ofPattern; | |
| 32 | import static java.util.Comparator.comparing; | |
| 33 | import static javafx.collections.FXCollections.observableArrayList; | |
| 34 | import static javafx.scene.control.TableView.CONSTRAINED_RESIZE_POLICY; | |
| 35 | import static javafx.scene.input.KeyCode.ENTER; | |
| 36 | import static javafx.scene.layout.Priority.ALWAYS; | |
| 37 | import static org.apache.commons.io.FilenameUtils.getExtension; | |
| 38 | ||
| 39 | /** | |
| 40 | * Responsible for browsing files. | |
| 41 | */ | |
| 42 | public class FilesView extends BorderPane implements FilePicker { | |
| 43 | /** | |
| 44 | * When this directory changes, the input field will update accordingly. | |
| 45 | */ | |
| 46 | private final ObjectProperty<File> mDirectory; | |
| 47 | ||
| 48 | /** | |
| 49 | * Data model for the file list shown in tabular format. | |
| 50 | */ | |
| 51 | private final ObservableList<PathEntry> mItems = observableArrayList(); | |
| 52 | ||
| 53 | /** | |
| 54 | * Used to format a file's date string from a {@code long} value. | |
| 55 | */ | |
| 56 | private final DateTimeFormatter mDateFormatter; | |
| 57 | ||
| 58 | /** | |
| 59 | * Used to format a file's time string from a {@code long} value. | |
| 60 | */ | |
| 61 | private final DateTimeFormatter mTimeFormatter; | |
| 62 | ||
| 63 | /** | |
| 64 | * Constructs a new view of a directory, listing all the files contained | |
| 65 | * therein. This will update the recent directory so that it will be | |
| 66 | * restored upon restart. | |
| 67 | * | |
| 68 | * @param recent Contains the initial (recent) directory. | |
| 69 | * @param locale Contains the language settings. | |
| 70 | */ | |
| 71 | public FilesView( | |
| 72 | final ObjectProperty<File> recent, final Locale locale ) { | |
| 73 | mDirectory = recent; | |
| 74 | mDateFormatter = createFormatter( "yyyy-MMM-dd", locale ); | |
| 75 | mTimeFormatter = createFormatter( "HH:mm:ss", locale ); | |
| 76 | ||
| 77 | final var browse = createDirectoryChooser(); | |
| 78 | final var table = createFileTable(); | |
| 79 | ||
| 80 | final var sortedItems = new SortedList<>( mItems ); | |
| 81 | sortedItems.comparatorProperty().bind( table.comparatorProperty() ); | |
| 82 | table.setItems( sortedItems ); | |
| 83 | ||
| 84 | setTop( browse ); | |
| 85 | setCenter( table ); | |
| 86 | ||
| 87 | mDirectory.addListener( ( c, o, n ) -> updateListing( n ) ); | |
| 88 | updateListing( mDirectory.get() ); | |
| 89 | } | |
| 90 | ||
| 91 | @Override | |
| 92 | public Optional<List<File>> choose() { | |
| 93 | return Optional.empty(); | |
| 94 | } | |
| 95 | ||
| 96 | private void updateListing( final File directory ) { | |
| 97 | if( directory != null ) { | |
| 98 | mItems.clear(); | |
| 99 | ||
| 100 | try { | |
| 101 | if( directory.getParent() != null ) { | |
| 102 | // Allow traversal to parent-directory. | |
| 103 | mItems.add( pathEntry( Paths.get( ".." ) ) ); | |
| 104 | } | |
| 105 | ||
| 106 | for( final var f : directory.list() ) { | |
| 107 | if( !f.startsWith( "." ) ) { | |
| 108 | mItems.add( pathEntry( Paths.get( directory.toString(), f ) ) ); | |
| 109 | } | |
| 110 | } | |
| 111 | } catch( final Exception ex ) { | |
| 112 | clue( ex ); | |
| 113 | } | |
| 114 | } | |
| 115 | } | |
| 116 | ||
| 117 | /** | |
| 118 | * Allows the user to use an instance of {@link FileChooser} to change the | |
| 119 | * directory. | |
| 120 | * | |
| 121 | * @return The browse button and input field. | |
| 122 | */ | |
| 123 | private HBox createDirectoryChooser() { | |
| 124 | final var dirProperty = directoryProperty(); | |
| 125 | final var directory = dirProperty.get(); | |
| 126 | final var hbox = new HBox(); | |
| 127 | final var field = new TextField(); | |
| 128 | ||
| 129 | mDirectory.addListener( ( c, o, n ) -> { | |
| 130 | if( n != null ) { field.setText( n.getAbsolutePath() ); } | |
| 131 | } ); | |
| 132 | ||
| 133 | field.setOnKeyPressed( event -> { | |
| 134 | if( event.getCode() == ENTER ) { | |
| 135 | mDirectory.set( new File( field.getText() ) ); | |
| 136 | } | |
| 137 | } ); | |
| 138 | ||
| 139 | final var button = new BrowseButton( directory, mDirectory::set ); | |
| 140 | ||
| 141 | hbox.getChildren().add( button ); | |
| 142 | hbox.getChildren().add( field ); | |
| 143 | hbox.setSpacing( UI_CONTROL_SPACING ); | |
| 144 | HBox.setHgrow( field, ALWAYS ); | |
| 145 | ||
| 146 | return hbox; | |
| 147 | } | |
| 148 | ||
| 149 | @SuppressWarnings( "unchecked" ) | |
| 150 | private TableView<FilesView.PathEntry> createFileTable() { | |
| 151 | final var style = "-fx-alignment: BASELINE_LEFT;"; | |
| 152 | final var table = new TableView<FilesView.PathEntry>(); | |
| 153 | table.setColumnResizePolicy( CONSTRAINED_RESIZE_POLICY ); | |
| 154 | ||
| 155 | table.setRowFactory( tv -> { | |
| 156 | final var row = new TableRow<PathEntry>(); | |
| 157 | ||
| 158 | row.setOnMouseClicked( event -> { | |
| 159 | if( event.getClickCount() == 2 && !row.isEmpty() ) { | |
| 160 | final var entry = row.getItem(); | |
| 161 | final var dir = mDirectory.get(); | |
| 162 | final var filename = entry.nameProperty().get(); | |
| 163 | final var path = Path.of( dir.toString(), filename ); | |
| 164 | final var file = path.toFile(); | |
| 165 | ||
| 166 | if( file.isFile() ) { | |
| 167 | fireFileOpenEvent( path.toUri() ); | |
| 168 | } | |
| 169 | else if( file.isDirectory() ) { | |
| 170 | mDirectory.set( path.normalize().toFile() ); | |
| 171 | } | |
| 172 | } | |
| 173 | } ); | |
| 174 | ||
| 175 | return row; | |
| 176 | } ); | |
| 177 | ||
| 178 | final TableColumn<PathEntry, Path> colType = createColumn( "Type" ); | |
| 179 | final TableColumn<PathEntry, String> colName = createColumn( "Name" ); | |
| 180 | final TableColumn<PathEntry, Number> colSize = createColumn( "Size" ); | |
| 181 | final TableColumn<PathEntry, String> colDate = createColumn( "Date" ); | |
| 182 | final TableColumn<PathEntry, String> colTime = createColumn( "Modified" ); | |
| 183 | ||
| 184 | colType.setCellFactory( new FileCell<>() ); | |
| 185 | ||
| 186 | colType.setCellValueFactory( stat -> stat.getValue().typeProperty() ); | |
| 187 | colName.setCellValueFactory( stat -> stat.getValue().nameProperty() ); | |
| 188 | colSize.setCellValueFactory( stat -> stat.getValue().sizeProperty() ); | |
| 189 | colDate.setCellValueFactory( stat -> stat.getValue().dateProperty() ); | |
| 190 | colTime.setCellValueFactory( stat -> stat.getValue().timeProperty() ); | |
| 191 | ||
| 192 | colType.setStyle( style ); | |
| 193 | colName.setStyle( style ); | |
| 194 | colSize.setStyle( style ); | |
| 195 | colDate.setStyle( style ); | |
| 196 | colTime.setStyle( style ); | |
| 197 | ||
| 198 | final var columns = table.getColumns(); | |
| 199 | columns.add( colType ); | |
| 200 | columns.add( colName ); | |
| 201 | columns.add( colSize ); | |
| 202 | columns.add( colDate ); | |
| 203 | columns.add( colTime ); | |
| 204 | ||
| 205 | table.getSortOrder().setAll( colName, colDate, colTime ); | |
| 206 | ||
| 207 | colType.setComparator( | |
| 208 | comparing( p -> getExtension( p.getFileName().toString() ) ) | |
| 209 | ); | |
| 210 | ||
| 211 | return table; | |
| 212 | } | |
| 213 | ||
| 214 | public ObjectProperty<File> directoryProperty() { | |
| 215 | return mDirectory; | |
| 216 | } | |
| 217 | ||
| 218 | private static DateTimeFormatter createFormatter( | |
| 219 | final String format, final Locale locale ) { | |
| 220 | return ofPattern( format, locale ).withZone( systemDefault() ); | |
| 221 | } | |
| 222 | ||
| 223 | public PathEntry pathEntry( final Path path ) throws IOException { | |
| 224 | return new PathEntry( path ); | |
| 225 | } | |
| 226 | ||
| 227 | /** | |
| 228 | * Responsible for rendering file system objects as image icons. | |
| 229 | * | |
| 230 | * @param <T> The data model type associated with a fully qualified path. | |
| 231 | * @param <P> Simplifies swapping {@link Path} for {@link File}. | |
| 232 | */ | |
| 233 | private static class FileCell<T, P extends Path> extends TableCell<T, P> | |
| 234 | implements Callback<TableColumn<T, P>, TableCell<T, P>> { | |
| 235 | @Override | |
| 236 | public TableCell<T, P> call( final TableColumn<T, P> param ) { | |
| 237 | return new TableCell<>() { | |
| 238 | @Override | |
| 239 | protected void updateItem( final P path, final boolean empty ) { | |
| 240 | super.updateItem( path, empty ); | |
| 241 | setText( null ); | |
| 242 | ||
| 243 | try { | |
| 244 | setGraphic( empty || path == null ? null : createFileIcon( path ) ); | |
| 245 | } catch( final Exception ex ) { | |
| 246 | clue( ex ); | |
| 247 | } | |
| 248 | } | |
| 249 | }; | |
| 250 | } | |
| 251 | } | |
| 252 | ||
| 253 | protected final class PathEntry { | |
| 254 | private final ObjectProperty<Path> mType; | |
| 255 | private final StringProperty mName; | |
| 256 | private final LongProperty mSize; | |
| 257 | private final StringProperty mDate; | |
| 258 | private final StringProperty mTime; | |
| 259 | ||
| 260 | protected PathEntry( final Path path ) throws IOException { | |
| 261 | this( | |
| 262 | path, | |
| 263 | path.getFileName().toString(), | |
| 264 | size( path ), | |
| 265 | ofEpochMilli( path.toFile().lastModified() ) | |
| 266 | ); | |
| 267 | } | |
| 268 | ||
| 269 | public PathEntry( | |
| 270 | final Path type, | |
| 271 | final String name, | |
| 272 | final long size, | |
| 273 | final Instant modified ) { | |
| 274 | this( | |
| 275 | new SimpleObjectProperty<>( type ), | |
| 276 | new SimpleStringProperty( name ), | |
| 277 | new SimpleLongProperty( size ), | |
| 278 | new SimpleStringProperty( mDateFormatter.format( modified ) ), | |
| 279 | new SimpleStringProperty( mTimeFormatter.format( modified ) ) | |
| 280 | ); | |
| 281 | } | |
| 282 | ||
| 283 | private PathEntry( | |
| 284 | final ObjectProperty<Path> type, | |
| 285 | final StringProperty name, | |
| 286 | final LongProperty size, | |
| 287 | final StringProperty date, | |
| 288 | final StringProperty time ) { | |
| 289 | mType = type; | |
| 290 | mName = name; | |
| 291 | mSize = size; | |
| 292 | mDate = date; | |
| 293 | mTime = time; | |
| 294 | } | |
| 295 | ||
| 296 | private ObjectProperty<Path> typeProperty() { | |
| 297 | return mType; | |
| 298 | } | |
| 299 | ||
| 300 | private StringProperty nameProperty() { | |
| 301 | return mName; | |
| 302 | } | |
| 303 | ||
| 304 | private LongProperty sizeProperty() { | |
| 305 | return mSize; | |
| 306 | } | |
| 307 | ||
| 308 | private StringProperty dateProperty() { | |
| 309 | return mDate; | |
| 310 | } | |
| 311 | ||
| 312 | private StringProperty timeProperty() { | |
| 313 | return mTime; | |
| 314 | } | |
| 315 | } | |
| 316 | ||
| 317 | private <E, T> TableColumn<E, T> createColumn( final String key ) { | |
| 318 | return new TableColumn<>( key ); | |
| 319 | } | |
| 320 | } | |
| 1 | 321 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.fonts; | |
| 3 | ||
| 4 | import com.keenwrite.io.MediaType; | |
| 5 | import com.keenwrite.io.MediaTypeExtension; | |
| 6 | import javafx.scene.Node; | |
| 7 | import javafx.scene.image.Image; | |
| 8 | import javafx.scene.image.ImageView; | |
| 9 | import org.controlsfx.glyphfont.FontAwesome; | |
| 10 | import org.controlsfx.glyphfont.Glyph; | |
| 11 | ||
| 12 | import java.awt.*; | |
| 13 | import java.awt.image.BufferedImage; | |
| 14 | import java.io.IOException; | |
| 15 | import java.io.InputStream; | |
| 16 | import java.nio.file.Path; | |
| 17 | import java.nio.file.attribute.BasicFileAttributes; | |
| 18 | import java.util.HashMap; | |
| 19 | import java.util.Map; | |
| 20 | ||
| 21 | import static com.keenwrite.events.StatusEvent.clue; | |
| 22 | import static com.keenwrite.io.MediaTypeExtension.MEDIA_UNDEFINED; | |
| 23 | import static com.keenwrite.preview.SvgRasterizer.BROKEN_IMAGE_PLACEHOLDER; | |
| 24 | import static com.keenwrite.preview.SvgRasterizer.rasterize; | |
| 25 | import static java.awt.Font.BOLD; | |
| 26 | import static java.nio.file.Files.readAttributes; | |
| 27 | import static javafx.embed.swing.SwingFXUtils.toFXImage; | |
| 28 | import static org.apache.commons.io.FilenameUtils.getExtension; | |
| 29 | import static org.controlsfx.glyphfont.FontAwesome.Glyph.valueOf; | |
| 30 | ||
| 31 | /** | |
| 32 | * Responsible for creating FontAwesome glyphs and graphics. | |
| 33 | */ | |
| 34 | public class IconFactory { | |
| 35 | /** | |
| 36 | * File icon height, in pixels. | |
| 37 | */ | |
| 38 | private static final int ICON_HEIGHT = 16; | |
| 39 | ||
| 40 | /** | |
| 41 | * Singleton to prevent re-loading the TTF file. | |
| 42 | */ | |
| 43 | private static final FontAwesome FONT_AWESOME = new FontAwesome(); | |
| 44 | ||
| 45 | /** | |
| 46 | * Caches file type icons encountered. | |
| 47 | */ | |
| 48 | private static final Map<String, Image> ICONS = new HashMap<>(); | |
| 49 | ||
| 50 | /** | |
| 51 | * Prevent instantiation. Use the {@link #createGraphic(String)} method to | |
| 52 | * create an icon for display. | |
| 53 | */ | |
| 54 | private IconFactory() {} | |
| 55 | ||
| 56 | /** | |
| 57 | * Create a {@link Node} representation for the given icon name. | |
| 58 | * | |
| 59 | * @param icon Name of icon to convert to a UI object (case-insensitive). | |
| 60 | * @return A UI object suitable for display. | |
| 61 | */ | |
| 62 | public static Node createGraphic( final String icon ) { | |
| 63 | assert icon != null; | |
| 64 | ||
| 65 | // Return a label glyph. | |
| 66 | return icon.isEmpty() | |
| 67 | ? new Glyph() | |
| 68 | : createGlyph( icon ); | |
| 69 | } | |
| 70 | ||
| 71 | /** | |
| 72 | * Create a {@link Node} representation for the given FontAwesome glyph. | |
| 73 | * | |
| 74 | * @param glyph The glyph to convert to a {@link Node}. | |
| 75 | * @return The given glyph as a text label. | |
| 76 | */ | |
| 77 | public static Node createGraphic( final FontAwesome.Glyph glyph ) { | |
| 78 | return FONT_AWESOME.create( glyph ); | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Creates a suitable {@link Node} icon representation for the given file. | |
| 83 | * This will first look up the {@link MediaType} before matching based on | |
| 84 | * the file name extension. | |
| 85 | * | |
| 86 | * @param path The file to represent graphically. | |
| 87 | * @return An icon representation for the given file. | |
| 88 | */ | |
| 89 | public static ImageView createFileIcon( final Path path ) throws IOException { | |
| 90 | final var attrs = readAttributes( path, BasicFileAttributes.class ); | |
| 91 | final var filename = path.getFileName().toString(); | |
| 92 | String extension; | |
| 93 | ||
| 94 | if( "..".equals( filename ) ) { | |
| 95 | extension = "folder-up"; | |
| 96 | } | |
| 97 | else if( attrs.isDirectory() ) { | |
| 98 | extension = "folder"; | |
| 99 | } | |
| 100 | else if( attrs.isSymbolicLink() ) { | |
| 101 | extension = "folder-link"; | |
| 102 | } | |
| 103 | else { | |
| 104 | final var mediaType = MediaType.valueFrom( path ); | |
| 105 | final var mte = MediaTypeExtension.valueFrom( mediaType ); | |
| 106 | ||
| 107 | // if the file extension is not known to the app, try loading an icon | |
| 108 | // that corresponds to the extension directly. | |
| 109 | extension = mte == MEDIA_UNDEFINED | |
| 110 | ? getExtension( filename ) | |
| 111 | : mte.getExtension(); | |
| 112 | } | |
| 113 | ||
| 114 | if(extension == null) { | |
| 115 | extension = ""; | |
| 116 | } | |
| 117 | else { | |
| 118 | extension = extension.toLowerCase(); | |
| 119 | } | |
| 120 | ||
| 121 | // Each cell in the table must have a distinct parent, so the image views | |
| 122 | // cannot be reused. The underlying buffered image can be cached, though. | |
| 123 | final var image = | |
| 124 | ICONS.computeIfAbsent( extension, IconFactory::createFxImage ); | |
| 125 | final var imageView = new ImageView(); | |
| 126 | imageView.setPreserveRatio( true ); | |
| 127 | imageView.setFitHeight( ICON_HEIGHT ); | |
| 128 | imageView.setImage( image ); | |
| 129 | ||
| 130 | return imageView; | |
| 131 | } | |
| 132 | ||
| 133 | private static Image createFxImage( final String extension ) { | |
| 134 | return toFXImage( createImage( extension ), null ); | |
| 135 | } | |
| 136 | ||
| 137 | private static BufferedImage createImage( final String extension ) { | |
| 138 | try( final var icon = open( "icons/" + extension + ".svg" ) ) { | |
| 139 | if( icon == null ) { | |
| 140 | throw new IllegalArgumentException( extension ); | |
| 141 | } | |
| 142 | ||
| 143 | return rasterize( icon ); | |
| 144 | } catch( final Exception ex ) { | |
| 145 | clue( ex ); | |
| 146 | ||
| 147 | // If the extension was unknown, fall back to a blank icon, falling | |
| 148 | // back again to a broken image if blank cannot be found (to avoid | |
| 149 | // infinite recursion). | |
| 150 | return "blank".equals( extension ) | |
| 151 | ? BROKEN_IMAGE_PLACEHOLDER | |
| 152 | : createImage( "blank" ); | |
| 153 | } | |
| 154 | } | |
| 155 | ||
| 156 | private static InputStream open( final String resource ) { | |
| 157 | return IconFactory.class.getResourceAsStream( resource ); | |
| 158 | } | |
| 159 | ||
| 160 | public static Font getIconFont( final int size ) { | |
| 161 | return new Font( FONT_AWESOME.getName(), BOLD, size ); | |
| 162 | } | |
| 163 | ||
| 164 | private static Node createGlyph( final String icon ) { | |
| 165 | return createGraphic( valueOf( icon.toUpperCase() ) ); | |
| 166 | } | |
| 167 | } | |
| 1 | 168 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.heuristics; | |
| 3 | ||
| 4 | import com.keenwrite.events.DocumentChangedEvent; | |
| 5 | import com.keenwrite.preferences.Workspace; | |
| 6 | import com.keenwrite.preview.HtmlPanel; | |
| 7 | import com.whitemagicsoftware.wordcount.TokenizerException; | |
| 8 | import javafx.beans.property.IntegerProperty; | |
| 9 | import javafx.beans.property.SimpleIntegerProperty; | |
| 10 | import javafx.beans.property.SimpleStringProperty; | |
| 11 | import javafx.beans.property.StringProperty; | |
| 12 | import javafx.collections.ObservableList; | |
| 13 | import javafx.collections.transformation.SortedList; | |
| 14 | import javafx.scene.control.TableColumn; | |
| 15 | import javafx.scene.control.TableView; | |
| 16 | import org.greenrobot.eventbus.Subscribe; | |
| 17 | ||
| 18 | import static com.keenwrite.events.Bus.register; | |
| 19 | import static com.keenwrite.events.StatusEvent.clue; | |
| 20 | import static com.keenwrite.events.WordCountEvent.fireWordCountEvent; | |
| 21 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_LANGUAGE_LOCALE; | |
| 22 | import static com.keenwrite.preferences.WorkspaceKeys.KEY_UI_FONT_EDITOR_NAME; | |
| 23 | import static com.keenwrite.ui.heuristics.DocumentStatistics.StatEntry; | |
| 24 | import static java.lang.String.format; | |
| 25 | import static javafx.application.Platform.runLater; | |
| 26 | import static javafx.collections.FXCollections.observableArrayList; | |
| 27 | ||
| 28 | /** | |
| 29 | * Responsible for displaying document statistics, such as word count and | |
| 30 | * word frequency. | |
| 31 | */ | |
| 32 | public final class DocumentStatistics extends TableView<StatEntry> { | |
| 33 | ||
| 34 | private WordCounter mWordCounter; | |
| 35 | private final ObservableList<StatEntry> mItems = observableArrayList(); | |
| 36 | ||
| 37 | /** | |
| 38 | * Creates a new observer of document change events that will gather and | |
| 39 | * display document statistics (e.g., word counts). | |
| 40 | * | |
| 41 | * @param workspace Settings used to configure the statistics engine. | |
| 42 | */ | |
| 43 | public DocumentStatistics( final Workspace workspace ) { | |
| 44 | mWordCounter = WordCounter.create( workspace.getLocale() ); | |
| 45 | ||
| 46 | final var sortedItems = new SortedList<>( mItems ); | |
| 47 | sortedItems.comparatorProperty().bind( comparatorProperty() ); | |
| 48 | setItems( sortedItems ); | |
| 49 | ||
| 50 | initView(); | |
| 51 | initListeners( workspace ); | |
| 52 | register( this ); | |
| 53 | ||
| 54 | final var fontName = workspace.stringProperty( KEY_UI_FONT_EDITOR_NAME ); | |
| 55 | ||
| 56 | fontName.addListener( | |
| 57 | ( c, o, n ) -> { | |
| 58 | if( n != null ) { | |
| 59 | setFontFamily( n ); | |
| 60 | } | |
| 61 | } | |
| 62 | ); | |
| 63 | ||
| 64 | setFontFamily( fontName.getValue() ); | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Called when the hash code for the current document changes. This happens | |
| 69 | * when non-collapsable-whitespace is added to the document. When the | |
| 70 | * document is sent to {@link HtmlPanel} for rendering, the parsed document | |
| 71 | * is converted to text. If that text differs in its hash code, then this | |
| 72 | * method is called. The implication is that all variables and executable | |
| 73 | * statements have been replaced. An event bus subscriber is used so that | |
| 74 | * text processing occurs outside of the UI processing threads. | |
| 75 | * | |
| 76 | * @param event Container for the document text that has changed. | |
| 77 | */ | |
| 78 | @Subscribe | |
| 79 | public void handle( final DocumentChangedEvent event ) { | |
| 80 | try { | |
| 81 | runLater( () -> { | |
| 82 | mItems.clear(); | |
| 83 | final var document = event.getDocument(); | |
| 84 | final var wordCount = mWordCounter.count( | |
| 85 | document, ( k, count ) -> { | |
| 86 | // Generate statistics for words that occur thrice or more. | |
| 87 | if( count > 2 ) { | |
| 88 | mItems.add( new StatEntry( k, count ) ); | |
| 89 | } | |
| 90 | } | |
| 91 | ); | |
| 92 | ||
| 93 | fireWordCountEvent( wordCount ); | |
| 94 | } ); | |
| 95 | } catch( final TokenizerException ex ) { | |
| 96 | clue( ex ); | |
| 97 | } | |
| 98 | } | |
| 99 | ||
| 100 | @SuppressWarnings( "unchecked" ) | |
| 101 | private void initView() { | |
| 102 | final TableColumn<StatEntry, String> colWord = createColumn( "Word" ); | |
| 103 | final TableColumn<StatEntry, Number> colCount = createColumn( "Count" ); | |
| 104 | ||
| 105 | colWord.setCellValueFactory( stat -> stat.getValue().wordProperty() ); | |
| 106 | colCount.setCellValueFactory( stat -> stat.getValue().tallyProperty() ); | |
| 107 | colCount.setComparator( colCount.getComparator().reversed() ); | |
| 108 | ||
| 109 | final var columns = getColumns(); | |
| 110 | columns.add( colWord ); | |
| 111 | columns.add( colCount ); | |
| 112 | ||
| 113 | setMaxWidth( Double.MAX_VALUE ); | |
| 114 | setPrefWidth( 128 ); | |
| 115 | setColumnResizePolicy( CONSTRAINED_RESIZE_POLICY ); | |
| 116 | getSortOrder().setAll( colCount, colWord ); | |
| 117 | ||
| 118 | getStyleClass().add( "" ); | |
| 119 | } | |
| 120 | ||
| 121 | private void initListeners( final Workspace workspace ) { | |
| 122 | final var property = workspace.localeProperty( KEY_LANGUAGE_LOCALE ); | |
| 123 | property.addListener( | |
| 124 | ( c, o, n ) -> mWordCounter = WordCounter.create( property.toLocale() ) | |
| 125 | ); | |
| 126 | } | |
| 127 | ||
| 128 | private <E, T> TableColumn<E, T> createColumn( final String key ) { | |
| 129 | return new TableColumn<>( key ); | |
| 130 | } | |
| 131 | ||
| 132 | private void setFontFamily( final String value ) { | |
| 133 | runLater( () -> setStyle( format( "-fx-font-family:'%s';", value ) ) ); | |
| 134 | } | |
| 135 | ||
| 136 | /** | |
| 137 | * Represents the number of times a word appears in a document. | |
| 138 | */ | |
| 139 | protected static final class StatEntry { | |
| 140 | private final StringProperty mWord; | |
| 141 | private final IntegerProperty mTally; | |
| 142 | ||
| 143 | public StatEntry( final String word, final int tally ) { | |
| 144 | mWord = new SimpleStringProperty( word ); | |
| 145 | mTally = new SimpleIntegerProperty( tally ); | |
| 146 | } | |
| 147 | ||
| 148 | private StringProperty wordProperty() { | |
| 149 | return mWord; | |
| 150 | } | |
| 151 | ||
| 152 | private IntegerProperty tallyProperty() { | |
| 153 | return mTally; | |
| 154 | } | |
| 155 | } | |
| 156 | } | |
| 1 | 157 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.heuristics; | |
| 3 | ||
| 4 | import com.whitemagicsoftware.wordcount.Tokenizer; | |
| 5 | import com.whitemagicsoftware.wordcount.TokenizerFactory; | |
| 6 | ||
| 7 | import java.util.Locale; | |
| 8 | import java.util.function.BiConsumer; | |
| 9 | ||
| 10 | /** | |
| 11 | * Responsible for counting unique words as well as total words in a document. | |
| 12 | */ | |
| 13 | public class WordCounter { | |
| 14 | /** | |
| 15 | * Parses documents into word counts. | |
| 16 | */ | |
| 17 | private final Tokenizer mTokenizer; | |
| 18 | ||
| 19 | /** | |
| 20 | * Constructs a new {@link WordCounter} instance using the given tokenizer. | |
| 21 | * | |
| 22 | * @param tokenizer The class responsible for parsing a document into unique | |
| 23 | * and total word counts. | |
| 24 | */ | |
| 25 | private WordCounter( final Tokenizer tokenizer ) { | |
| 26 | mTokenizer = tokenizer; | |
| 27 | } | |
| 28 | ||
| 29 | /** | |
| 30 | * Counts the number of unique words in the document. | |
| 31 | * | |
| 32 | * @param document The document to tally. | |
| 33 | * @return The total number of words in the document. | |
| 34 | */ | |
| 35 | public int count( final String document ) { | |
| 36 | return count( document, ( k, count ) -> {} ); | |
| 37 | } | |
| 38 | ||
| 39 | /** | |
| 40 | * Counts the number of unique words in the document. | |
| 41 | * | |
| 42 | * @param document The document to tally. | |
| 43 | * @param consumer The action to take for each unique word/count pair. | |
| 44 | * @return The total number of words in the document. | |
| 45 | */ | |
| 46 | public int count( | |
| 47 | final String document, final BiConsumer<String, Integer> consumer ) { | |
| 48 | final var tokens = mTokenizer.tokenize( document ); | |
| 49 | final var sum = new int[]{0}; | |
| 50 | ||
| 51 | tokens.forEach( ( k, v ) -> { | |
| 52 | final var count = v[ 0 ]; | |
| 53 | consumer.accept( k, count ); | |
| 54 | sum[ 0 ] += count; | |
| 55 | } ); | |
| 56 | ||
| 57 | return sum[ 0 ]; | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Constructs a new {@link WordCounter} capable of tokenizing a document | |
| 62 | * into words using the given {@link Locale}. | |
| 63 | * | |
| 64 | * @param locale The {@link Tokenizer}'s language settings. | |
| 65 | */ | |
| 66 | public static WordCounter create( final Locale locale ) { | |
| 67 | return new WordCounter( createTokenizer( locale ) ); | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Creates a tokenizer for English text (can handle most Latin languages). | |
| 72 | * | |
| 73 | * @return An English-based tokenizer for counting words. | |
| 74 | */ | |
| 75 | private static Tokenizer createTokenizer( final Locale language ) { | |
| 76 | return TokenizerFactory.create( language ); | |
| 77 | } | |
| 78 | } | |
| 1 | 79 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.listeners; | |
| 3 | ||
| 4 | import com.keenwrite.Caret; | |
| 5 | import com.keenwrite.editors.TextEditor; | |
| 6 | import com.keenwrite.events.WordCountEvent; | |
| 7 | import javafx.beans.property.ReadOnlyObjectProperty; | |
| 8 | import javafx.beans.value.ChangeListener; | |
| 9 | import javafx.beans.value.ObservableValue; | |
| 10 | import javafx.scene.control.Label; | |
| 11 | import javafx.scene.layout.VBox; | |
| 12 | import org.greenrobot.eventbus.Subscribe; | |
| 13 | ||
| 14 | import static com.keenwrite.events.Bus.register; | |
| 15 | import static javafx.application.Platform.runLater; | |
| 16 | import static javafx.geometry.Pos.BASELINE_CENTER; | |
| 17 | ||
| 18 | /** | |
| 19 | * Responsible for updating the UI whenever the caret changes position. | |
| 20 | * Only one instance of {@link CaretListener} is allowed, which prevents | |
| 21 | * duplicate adds to the observable property. | |
| 22 | */ | |
| 23 | public class CaretListener extends VBox implements ChangeListener<Integer> { | |
| 24 | ||
| 25 | /** | |
| 26 | * Use an instance of {@link Label} for its built-in CSS style class. | |
| 27 | */ | |
| 28 | private final Label mLineNumberText = new Label(); | |
| 29 | private volatile Caret mCaret; | |
| 30 | ||
| 31 | /** | |
| 32 | * Approximate number of words in the document. | |
| 33 | */ | |
| 34 | private volatile int mCount; | |
| 35 | ||
| 36 | public CaretListener( final ReadOnlyObjectProperty<TextEditor> editor ) { | |
| 37 | assert editor != null; | |
| 38 | ||
| 39 | setAlignment( BASELINE_CENTER ); | |
| 40 | getChildren().add( mLineNumberText ); | |
| 41 | ||
| 42 | editor.addListener( ( c, o, n ) -> { | |
| 43 | if( n != null ) { | |
| 44 | updateListener( n.getCaret() ); | |
| 45 | } | |
| 46 | } ); | |
| 47 | ||
| 48 | updateListener( editor.get().getCaret() ); | |
| 49 | register( this ); | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Called whenever the caret position changes. | |
| 54 | * | |
| 55 | * @param c The caret position property. | |
| 56 | * @param o The old caret position offset. | |
| 57 | * @param n The new caret position offset. | |
| 58 | */ | |
| 59 | @Override | |
| 60 | public void changed( | |
| 61 | final ObservableValue<? extends Integer> c, | |
| 62 | final Integer o, final Integer n ) { | |
| 63 | updateLineNumber(); | |
| 64 | } | |
| 65 | ||
| 66 | @Subscribe | |
| 67 | public void handle( final WordCountEvent event ) { | |
| 68 | mCount = event.getCount(); | |
| 69 | updateLineNumber(); | |
| 70 | } | |
| 71 | ||
| 72 | private void updateListener( final Caret caret ) { | |
| 73 | assert caret != null; | |
| 74 | ||
| 75 | final var property = caret.textOffsetProperty(); | |
| 76 | ||
| 77 | property.removeListener( this ); | |
| 78 | mCaret = caret; | |
| 79 | property.addListener( this ); | |
| 80 | updateLineNumber(); | |
| 81 | } | |
| 82 | ||
| 83 | private void updateLineNumber() { | |
| 84 | runLater( | |
| 85 | () -> mLineNumberText.setText( mCaret.toString() + " | " + mCount ) | |
| 86 | ); | |
| 87 | } | |
| 88 | } | |
| 1 | 89 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.logging; | |
| 3 | ||
| 4 | import com.keenwrite.events.StatusEvent; | |
| 5 | import javafx.beans.property.SimpleStringProperty; | |
| 6 | import javafx.beans.property.StringProperty; | |
| 7 | import javafx.collections.ObservableList; | |
| 8 | import javafx.scene.control.*; | |
| 9 | import javafx.scene.input.ClipboardContent; | |
| 10 | import javafx.scene.input.KeyCodeCombination; | |
| 11 | import javafx.stage.Stage; | |
| 12 | import org.greenrobot.eventbus.Subscribe; | |
| 13 | ||
| 14 | import java.time.LocalDateTime; | |
| 15 | import java.util.Objects; | |
| 16 | import java.util.TreeSet; | |
| 17 | ||
| 18 | import static com.keenwrite.Messages.get; | |
| 19 | import static com.keenwrite.constants.Constants.ACTION_PREFIX; | |
| 20 | import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG; | |
| 21 | import static com.keenwrite.events.Bus.register; | |
| 22 | import static com.keenwrite.events.StatusEvent.clue; | |
| 23 | import static java.time.LocalDateTime.now; | |
| 24 | import static java.time.format.DateTimeFormatter.ofPattern; | |
| 25 | import static javafx.application.Platform.runLater; | |
| 26 | import static javafx.collections.FXCollections.observableArrayList; | |
| 27 | import static javafx.event.ActionEvent.ACTION; | |
| 28 | import static javafx.scene.control.Alert.AlertType.INFORMATION; | |
| 29 | import static javafx.scene.control.ButtonType.OK; | |
| 30 | import static javafx.scene.control.SelectionMode.MULTIPLE; | |
| 31 | import static javafx.scene.input.Clipboard.getSystemClipboard; | |
| 32 | import static javafx.scene.input.KeyCode.C; | |
| 33 | import static javafx.scene.input.KeyCode.INSERT; | |
| 34 | import static javafx.scene.input.KeyCombination.CONTROL_ANY; | |
| 35 | import static javafx.stage.Modality.NONE; | |
| 36 | ||
| 37 | /** | |
| 38 | * Responsible for logging application issues to {@link TableView} entries. | |
| 39 | */ | |
| 40 | public final class LogView extends Alert { | |
| 41 | /** | |
| 42 | * Number of error messages to retain in the {@link TableView}; must be | |
| 43 | * greater than zero. | |
| 44 | */ | |
| 45 | private static final int CACHE_SIZE = 150; | |
| 46 | ||
| 47 | private final ObservableList<LogEntry> mItems = observableArrayList(); | |
| 48 | private final TableView<LogEntry> mTable = new TableView<>( mItems ); | |
| 49 | ||
| 50 | public LogView() { | |
| 51 | super( INFORMATION ); | |
| 52 | setTitle( get( ACTION_PREFIX + "view.log.text" ) ); | |
| 53 | initModality( NONE ); | |
| 54 | initTableView(); | |
| 55 | setResizable( true ); | |
| 56 | initButtons(); | |
| 57 | initIcon(); | |
| 58 | initActions(); | |
| 59 | register( this ); | |
| 60 | } | |
| 61 | ||
| 62 | @Subscribe | |
| 63 | public void log( final StatusEvent event ) { | |
| 64 | runLater( () -> { | |
| 65 | final var logEntry = new LogEntry( event ); | |
| 66 | ||
| 67 | if( !mItems.contains( logEntry ) ) { | |
| 68 | mItems.add( logEntry ); | |
| 69 | ||
| 70 | while( mItems.size() > CACHE_SIZE ) { | |
| 71 | mItems.remove( 0 ); | |
| 72 | } | |
| 73 | ||
| 74 | mTable.scrollTo( logEntry ); | |
| 75 | } | |
| 76 | } ); | |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Brings the dialog to the foreground, showing it if needed. | |
| 81 | */ | |
| 82 | public void view() { | |
| 83 | super.show(); | |
| 84 | getStage().toFront(); | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Removes all the entries from the list. | |
| 89 | */ | |
| 90 | public void clear() { | |
| 91 | mItems.clear(); | |
| 92 | clue(); | |
| 93 | } | |
| 94 | ||
| 95 | private void initTableView() { | |
| 96 | final var ctrlC = new KeyCodeCombination( C, CONTROL_ANY ); | |
| 97 | final var ctrlInsert = new KeyCodeCombination( INSERT, CONTROL_ANY ); | |
| 98 | ||
| 99 | final var colDate = new TableColumn<LogEntry, String>( "Timestamp" ); | |
| 100 | final var colMessage = new TableColumn<LogEntry, String>( "Message" ); | |
| 101 | final var colTrace = new TableColumn<LogEntry, String>( "Trace" ); | |
| 102 | ||
| 103 | colDate.setCellValueFactory( log -> log.getValue().dateProperty() ); | |
| 104 | colMessage.setCellValueFactory( log -> log.getValue().messageProperty() ); | |
| 105 | colTrace.setCellValueFactory( log -> log.getValue().traceProperty() ); | |
| 106 | ||
| 107 | final var columns = mTable.getColumns(); | |
| 108 | columns.add( colDate ); | |
| 109 | columns.add( colMessage ); | |
| 110 | columns.add( colTrace ); | |
| 111 | ||
| 112 | mTable.setMaxWidth( Double.MAX_VALUE ); | |
| 113 | mTable.setPrefWidth( 1024 ); | |
| 114 | mTable.getSelectionModel().setSelectionMode( MULTIPLE ); | |
| 115 | mTable.setOnKeyPressed( event -> { | |
| 116 | if( ctrlC.match( event ) || ctrlInsert.match( event ) ) { | |
| 117 | copyToClipboard( mTable ); | |
| 118 | } | |
| 119 | } ); | |
| 120 | ||
| 121 | final var pane = getDialogPane(); | |
| 122 | pane.setContent( mTable ); | |
| 123 | } | |
| 124 | ||
| 125 | private void initButtons() { | |
| 126 | final var pane = getDialogPane(); | |
| 127 | final var CLEAR = new ButtonType( "CLEAR" ); | |
| 128 | pane.getButtonTypes().add( CLEAR ); | |
| 129 | ||
| 130 | final var buttonOk = (Button) pane.lookupButton( OK ); | |
| 131 | final var buttonClear = (Button) pane.lookupButton( CLEAR ); | |
| 132 | ||
| 133 | buttonOk.setDefaultButton( true ); | |
| 134 | buttonClear.addEventFilter( ACTION, event -> { | |
| 135 | clear(); | |
| 136 | event.consume(); | |
| 137 | } ); | |
| 138 | ||
| 139 | pane.setOnKeyReleased( t -> { | |
| 140 | switch( t.getCode() ) { | |
| 141 | case ENTER, ESCAPE -> buttonOk.fire(); | |
| 142 | } | |
| 143 | } ); | |
| 144 | } | |
| 145 | ||
| 146 | private void initIcon() { | |
| 147 | getStage().getIcons().add( ICON_DIALOG ); | |
| 148 | } | |
| 149 | ||
| 150 | private void initActions() { | |
| 151 | final var stage = getStage(); | |
| 152 | stage.setOnCloseRequest( event -> stage.hide() ); | |
| 153 | } | |
| 154 | ||
| 155 | private Stage getStage() { | |
| 156 | return (Stage) getDialogPane().getScene().getWindow(); | |
| 157 | } | |
| 158 | ||
| 159 | private static final class LogEntry { | |
| 160 | private final StringProperty mDate; | |
| 161 | private final StringProperty mMessage; | |
| 162 | private final StringProperty mTrace; | |
| 163 | ||
| 164 | /** | |
| 165 | * Constructs a new {@link LogEntry} for the current time. | |
| 166 | */ | |
| 167 | public LogEntry( final StatusEvent event ) { | |
| 168 | mDate = new SimpleStringProperty( toString( now() ) ); | |
| 169 | mMessage = new SimpleStringProperty( event.getMessage() ); | |
| 170 | mTrace = new SimpleStringProperty( event.getProblem() ); | |
| 171 | } | |
| 172 | ||
| 173 | private StringProperty messageProperty() { | |
| 174 | return mMessage; | |
| 175 | } | |
| 176 | ||
| 177 | private StringProperty dateProperty() { | |
| 178 | return mDate; | |
| 179 | } | |
| 180 | ||
| 181 | private StringProperty traceProperty() { | |
| 182 | return mTrace; | |
| 183 | } | |
| 184 | ||
| 185 | @Override | |
| 186 | public boolean equals( final Object o ) { | |
| 187 | if( this == o ) { return true; } | |
| 188 | if( o == null || getClass() != o.getClass() ) { return false; } | |
| 189 | ||
| 190 | return Objects.equals( mMessage.get(), ((LogEntry) o).mMessage.get() ); | |
| 191 | } | |
| 192 | ||
| 193 | @Override | |
| 194 | public int hashCode() { | |
| 195 | return mMessage != null ? mMessage.hashCode() : 0; | |
| 196 | } | |
| 197 | ||
| 198 | @Override | |
| 199 | public String toString() { | |
| 200 | final var date = mDate == null ? "" : mDate.get(); | |
| 201 | final var message = mMessage == null ? "" : mMessage.get(); | |
| 202 | final var trace = mTrace == null ? "" : mTrace.get(); | |
| 203 | ||
| 204 | return "LogEntry{" + | |
| 205 | "mDate=" + (date == null ? "''" : date) + | |
| 206 | ", mMessage=" + (message == null ? "''" : message) + | |
| 207 | ", mTrace=" + (trace == null ? "''" : trace) + | |
| 208 | '}'; | |
| 209 | } | |
| 210 | ||
| 211 | private String toString( final LocalDateTime date ) { | |
| 212 | return date.format( ofPattern( "d MMM u HH:mm:ss" ) ); | |
| 213 | } | |
| 214 | } | |
| 215 | ||
| 216 | /** | |
| 217 | * Copies the contents of the selected rows into the clipboard; code is from | |
| 218 | * <a href="https://stackoverflow.com/a/48126059/59087">StackOverflow</a>. | |
| 219 | * | |
| 220 | * @param table The {@link TableView} having selected rows to copy. | |
| 221 | */ | |
| 222 | public void copyToClipboard( final TableView<?> table ) { | |
| 223 | final var sb = new StringBuilder(); | |
| 224 | final var rows = new TreeSet<Integer>(); | |
| 225 | boolean firstRow = true; | |
| 226 | ||
| 227 | for( final var position : table.getSelectionModel().getSelectedCells() ) { | |
| 228 | rows.add( position.getRow() ); | |
| 229 | } | |
| 230 | ||
| 231 | for( final var row : rows ) { | |
| 232 | if( !firstRow ) { | |
| 233 | sb.append( '\n' ); | |
| 234 | } | |
| 235 | ||
| 236 | firstRow = false; | |
| 237 | boolean firstCol = true; | |
| 238 | ||
| 239 | for( final var column : table.getColumns() ) { | |
| 240 | if( !firstCol ) { | |
| 241 | sb.append( '\t' ); | |
| 242 | } | |
| 243 | ||
| 244 | firstCol = false; | |
| 245 | final var data = column.getCellData( row ); | |
| 246 | sb.append( data == null ? "" : data.toString() ); | |
| 247 | } | |
| 248 | } | |
| 249 | ||
| 250 | final var contents = new ClipboardContent(); | |
| 251 | contents.putString( sb.toString() ); | |
| 252 | getSystemClipboard().setContent( contents ); | |
| 253 | } | |
| 254 | } | |
| 1 | 255 |
| 1 | package com.keenwrite.ui.outline; | |
| 2 | ||
| 3 | import com.keenwrite.events.Bus; | |
| 4 | import com.keenwrite.events.ParseHeadingEvent; | |
| 5 | import javafx.scene.Node; | |
| 6 | import javafx.scene.control.TreeCell; | |
| 7 | import javafx.scene.control.TreeItem; | |
| 8 | import javafx.scene.control.TreeView; | |
| 9 | import javafx.util.Callback; | |
| 10 | import org.greenrobot.eventbus.Subscribe; | |
| 11 | ||
| 12 | import static com.keenwrite.events.Bus.register; | |
| 13 | import static com.keenwrite.events.CaretNavigationEvent.fireCaretNavigationEvent; | |
| 14 | import static com.keenwrite.ui.fonts.IconFactory.createGraphic; | |
| 15 | import static javafx.application.Platform.runLater; | |
| 16 | import static javafx.scene.input.MouseButton.PRIMARY; | |
| 17 | import static javafx.scene.input.MouseEvent.MOUSE_PRESSED; | |
| 18 | ||
| 19 | public class DocumentOutline extends TreeView<ParseHeadingEvent> { | |
| 20 | private TreeItem<ParseHeadingEvent> mCurrent; | |
| 21 | ||
| 22 | /** | |
| 23 | * Registers with the {@link Bus}. | |
| 24 | */ | |
| 25 | public DocumentOutline() { | |
| 26 | // Override double-click to issue a caret navigation event. | |
| 27 | setCellFactory( new Callback<>() { | |
| 28 | @Override | |
| 29 | public TreeCell<ParseHeadingEvent> call( | |
| 30 | TreeView<ParseHeadingEvent> treeView ) { | |
| 31 | TreeCell<ParseHeadingEvent> cell = new TreeCell<>() { | |
| 32 | @Override | |
| 33 | protected void updateItem( ParseHeadingEvent item, boolean empty ) { | |
| 34 | super.updateItem( item, empty ); | |
| 35 | if( empty || item == null ) { | |
| 36 | setText( null ); | |
| 37 | setGraphic( null ); | |
| 38 | } | |
| 39 | else { | |
| 40 | setText( item.toString() ); | |
| 41 | setGraphic( createIcon() ); | |
| 42 | } | |
| 43 | } | |
| 44 | }; | |
| 45 | ||
| 46 | cell.addEventFilter( MOUSE_PRESSED, event -> { | |
| 47 | if( event.getButton() == PRIMARY && event.getClickCount() % 2 == 0 ) { | |
| 48 | fireCaretNavigationEvent( cell.getItem().getOffset() ); | |
| 49 | event.consume(); | |
| 50 | } | |
| 51 | } ); | |
| 52 | ||
| 53 | return cell; | |
| 54 | } | |
| 55 | } ); | |
| 56 | ||
| 57 | register( this ); | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Updates the {@link TreeView} with the given event data. This method will | |
| 62 | * track the most recently added {@link TreeItem} so that the nesting | |
| 63 | * hierarchy reflects the document hierarchy. | |
| 64 | * | |
| 65 | * @param event Represents a document heading to add to the tree. | |
| 66 | */ | |
| 67 | @Subscribe | |
| 68 | public void handle( final ParseHeadingEvent event ) { | |
| 69 | runLater( | |
| 70 | () -> mCurrent = event.isNewOutline() ? clear( event ) : addItem( event ) | |
| 71 | ); | |
| 72 | } | |
| 73 | ||
| 74 | private TreeItem<ParseHeadingEvent> clear( final ParseHeadingEvent event ) { | |
| 75 | final var root = createTreeItem( event ); | |
| 76 | setRoot( root ); | |
| 77 | setShowRoot( false ); | |
| 78 | return root; | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * This method is called once for every heading in the document. The event | |
| 83 | * data directly corresponds to the sequence of headings in the document. | |
| 84 | * The given event data contains a level that is relative to the last | |
| 85 | * item in the tree. | |
| 86 | * | |
| 87 | * @param next Contains a level value to indicate heading depth. | |
| 88 | */ | |
| 89 | private TreeItem<ParseHeadingEvent> addItem( final ParseHeadingEvent next ) { | |
| 90 | var parent = mCurrent; | |
| 91 | final var item = createTreeItem( next ); | |
| 92 | final var curr = parent.getValue(); | |
| 93 | final var currLevel = curr.getLevel(); | |
| 94 | final var nextLevel = next.getLevel(); | |
| 95 | var deltaLevel = currLevel - nextLevel + 1; | |
| 96 | ||
| 97 | while( deltaLevel > 0 && parent != null ) { | |
| 98 | parent = parent.getParent(); | |
| 99 | deltaLevel--; | |
| 100 | } | |
| 101 | ||
| 102 | if( parent == null ) { | |
| 103 | parent = getRoot(); | |
| 104 | } | |
| 105 | ||
| 106 | parent.getChildren().add( item ); | |
| 107 | ||
| 108 | return item; | |
| 109 | } | |
| 110 | ||
| 111 | private TreeItem<ParseHeadingEvent> createTreeItem( | |
| 112 | final ParseHeadingEvent event ) { | |
| 113 | final var item = new TreeItem<>( event, createIcon() ); | |
| 114 | item.setExpanded( true ); | |
| 115 | return item; | |
| 116 | } | |
| 117 | ||
| 118 | private Node createIcon() { | |
| 119 | return createGraphic( "BOOKMARK" ); | |
| 120 | } | |
| 121 | } | |
| 1 | 122 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.tree; | |
| 3 | ||
| 4 | import javafx.beans.property.Property; | |
| 5 | import javafx.beans.property.SimpleStringProperty; | |
| 6 | import javafx.beans.value.ChangeListener; | |
| 7 | import javafx.beans.value.ObservableValue; | |
| 8 | import javafx.event.EventHandler; | |
| 9 | import javafx.scene.control.TextField; | |
| 10 | import javafx.scene.control.cell.TextFieldTreeCell; | |
| 11 | import javafx.scene.input.KeyEvent; | |
| 12 | import javafx.util.StringConverter; | |
| 13 | ||
| 14 | import static javafx.application.Platform.runLater; | |
| 15 | import static javafx.scene.input.KeyCode.ENTER; | |
| 16 | import static javafx.scene.input.KeyCode.TAB; | |
| 17 | import static javafx.scene.input.KeyEvent.KEY_RELEASED; | |
| 18 | ||
| 19 | /** | |
| 20 | * Responsible for enhancing the existing cell behaviour with fairly common | |
| 21 | * functionality, including commit on focus loss and Enter to commit. | |
| 22 | * | |
| 23 | * @param <T> The type of data stored by the tree. | |
| 24 | */ | |
| 25 | public class AltTreeCell<T> extends TextFieldTreeCell<T> { | |
| 26 | private final KeyHandler mKeyHandler = new KeyHandler(); | |
| 27 | private final Property<String> mInputText = new SimpleStringProperty(); | |
| 28 | private FocusListener mFocusListener; | |
| 29 | ||
| 30 | public AltTreeCell( final StringConverter<T> converter ) { | |
| 31 | super( converter ); | |
| 32 | assert converter != null; | |
| 33 | ||
| 34 | // When the text field is added as the graphics context, we hook into | |
| 35 | // the changed value to get a handle on the text field. From there it is | |
| 36 | // possible to add change the keyboard and focus behaviours. | |
| 37 | graphicProperty().addListener( ( c, o, n ) -> { | |
| 38 | if( o instanceof TextField ) { | |
| 39 | o.removeEventHandler( KEY_RELEASED, mKeyHandler ); | |
| 40 | o.focusedProperty().removeListener( mFocusListener ); | |
| 41 | } | |
| 42 | ||
| 43 | if( n instanceof final TextField input ) { | |
| 44 | n.addEventFilter( KEY_RELEASED, mKeyHandler ); | |
| 45 | mInputText.bind( input.textProperty() ); | |
| 46 | mFocusListener = new FocusListener( input ); | |
| 47 | n.focusedProperty().addListener( mFocusListener ); | |
| 48 | } | |
| 49 | } ); | |
| 50 | } | |
| 51 | ||
| 52 | private void commitEdit() { | |
| 53 | commitEdit( getConverter().fromString( mInputText.getValue() ) ); | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Responsible for accepting the text when users press the Enter or Tab key. | |
| 58 | */ | |
| 59 | private class KeyHandler implements EventHandler<KeyEvent> { | |
| 60 | @Override | |
| 61 | public void handle( final KeyEvent event ) { | |
| 62 | if( event.getCode() == ENTER || event.getCode() == TAB ) { | |
| 63 | commitEdit(); | |
| 64 | event.consume(); | |
| 65 | } | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | /** | |
| 70 | * Responsible for committing edits when focus is lost. This will also | |
| 71 | * deselect the input field when focus is gained so that typing text won't | |
| 72 | * overwrite the entire existing text. | |
| 73 | */ | |
| 74 | private class FocusListener implements ChangeListener<Boolean> { | |
| 75 | private final TextField mInput; | |
| 76 | ||
| 77 | private FocusListener( final TextField input ) { | |
| 78 | mInput = input; | |
| 79 | } | |
| 80 | ||
| 81 | @Override | |
| 82 | public void changed( | |
| 83 | final ObservableValue<? extends Boolean> c, | |
| 84 | final Boolean endedFocus, final Boolean beganFocus ) { | |
| 85 | ||
| 86 | if( beganFocus ) { | |
| 87 | runLater( mInput::deselect ); | |
| 88 | } | |
| 89 | else if( endedFocus ) { | |
| 90 | commitEdit(); | |
| 91 | } | |
| 92 | } | |
| 93 | } | |
| 94 | } | |
| 1 | 95 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.tree; | |
| 3 | ||
| 4 | import javafx.scene.control.TreeCell; | |
| 5 | import javafx.scene.control.TreeView; | |
| 6 | import javafx.util.Callback; | |
| 7 | import javafx.util.StringConverter; | |
| 8 | ||
| 9 | /** | |
| 10 | * Responsible for creating new {@link TreeCell} instances. | |
| 11 | * <p> | |
| 12 | * TODO: #22 -- Upon refactoring variable functionality, re-instate drag & drop. | |
| 13 | * </p> | |
| 14 | * | |
| 15 | * @param <T> The data type stored in the tree. | |
| 16 | */ | |
| 17 | public class AltTreeCellFactory<T> | |
| 18 | implements Callback<TreeView<T>, TreeCell<T>> { | |
| 19 | private final StringConverter<T> mConverter; | |
| 20 | ||
| 21 | public AltTreeCellFactory( final StringConverter<T> converter ) { | |
| 22 | mConverter = converter; | |
| 23 | } | |
| 24 | ||
| 25 | @Override | |
| 26 | public TreeCell<T> call( final TreeView<T> treeView ) { | |
| 27 | return new AltTreeCell<>( mConverter ); | |
| 28 | } | |
| 29 | } | |
| 1 | 30 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.tree; | |
| 3 | ||
| 4 | import javafx.scene.control.TreeItem; | |
| 5 | import javafx.scene.control.TreeView; | |
| 6 | import javafx.util.StringConverter; | |
| 7 | ||
| 8 | /** | |
| 9 | * Responsible for allowing users to edit items in the tree as well as | |
| 10 | * drag and drop. The goal is to be a drop-in replacement for the regular | |
| 11 | * JavaFX {@link TreeView}, which does not offer editing and moving {@link | |
| 12 | * TreeItem} instances. | |
| 13 | * | |
| 14 | * @param <T> The type of data to edit. | |
| 15 | */ | |
| 16 | public class AltTreeView<T> extends TreeView<T> { | |
| 17 | public AltTreeView( | |
| 18 | final TreeItem<T> root, final StringConverter<T> converter ) { | |
| 19 | super( root ); | |
| 20 | ||
| 21 | setEditable( true ); | |
| 22 | setCellFactory( new AltTreeCellFactory<>( converter ) ); | |
| 23 | setShowRoot( false ); | |
| 24 | ||
| 25 | // When focus is lost, clear the selected item only when not editing. | |
| 26 | focusedProperty().addListener( ( c, o, n ) -> { | |
| 27 | if( o && getEditingItem() == null ) { | |
| 28 | getSelectionModel().clearSelection(); | |
| 29 | } | |
| 30 | } ); | |
| 31 | } | |
| 32 | } | |
| 1 | 33 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.ui.tree; | |
| 3 | ||
| 4 | import javafx.util.StringConverter; | |
| 5 | ||
| 6 | /** | |
| 7 | * Responsible for converting objects to and from string instances. The | |
| 8 | * tree items contain only strings, so this effectively is a string-to-string | |
| 9 | * converter, which allows the implementation to retain its generics. | |
| 10 | */ | |
| 11 | public class TreeItemConverter extends StringConverter<String> { | |
| 12 | ||
| 13 | @Override | |
| 14 | public String toString( final String object ) { | |
| 15 | return sanitize( object ); | |
| 16 | } | |
| 17 | ||
| 18 | @Override | |
| 19 | public String fromString( final String string ) { | |
| 20 | return sanitize( string ); | |
| 21 | } | |
| 22 | ||
| 23 | private String sanitize( final String string ) { | |
| 24 | return string == null ? "" : string; | |
| 25 | } | |
| 26 | } | |
| 1 | 27 |
| 1 | /* | |
| 2 | * The Alphanum Algorithm is an improved sorting algorithm for strings | |
| 3 | * containing numbers. Rather than sort numbers in ASCII order like | |
| 4 | * a standard sort, this algorithm sorts numbers in numeric order. | |
| 5 | * | |
| 6 | * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com | |
| 7 | * | |
| 8 | * Released under the MIT License - https://opensource.org/licenses/MIT | |
| 9 | * | |
| 10 | * Copyright 2007-2017 David Koelle | |
| 11 | * | |
| 12 | * Permission is hereby granted, free of charge, to any person obtaining | |
| 13 | * a copy of this software and associated documentation files (the "Software"), | |
| 14 | * to deal in the Software without restriction, including without limitation | |
| 15 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
| 16 | * and/or sell copies of the Software, and to permit persons to whom the | |
| 17 | * Software is furnished to do so, subject to the following conditions: | |
| 18 | * | |
| 19 | * The above copyright notice and this permission notice shall be included | |
| 20 | * in all copies or substantial portions of the Software. | |
| 21 | * | |
| 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 24 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 25 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
| 26 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
| 27 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
| 28 | * USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 29 | */ | |
| 30 | package com.keenwrite.util; | |
| 31 | ||
| 32 | import java.util.Comparator; | |
| 33 | ||
| 34 | import static java.lang.Character.isDigit; | |
| 35 | ||
| 36 | /** | |
| 37 | * Responsible for sorting lists that may contain numeric values. Usage: | |
| 38 | * <pre> | |
| 39 | * Collections.sort(list, new AlphanumComparator()); | |
| 40 | * </pre> | |
| 41 | * <p> | |
| 42 | * Where "list" is the list to sort alphanumerically, not lexicographically. | |
| 43 | * </p> | |
| 44 | */ | |
| 45 | public final class AlphanumComparator<T> implements Comparator<T> { | |
| 46 | /** | |
| 47 | * Returns a chunk of text that is continuous with respect to digits or | |
| 48 | * non-digits. | |
| 49 | * | |
| 50 | * @param s The string to compare. | |
| 51 | * @param length The string length, for improved efficiency. | |
| 52 | * @param marker The current index into a subset of the given string. | |
| 53 | * @return The substring {@code s} that is a continuous text chunk of the | |
| 54 | * same character type. | |
| 55 | */ | |
| 56 | private StringBuilder chunk( final String s, final int length, int marker ) { | |
| 57 | assert s != null; | |
| 58 | assert length >= 0; | |
| 59 | assert marker < length; | |
| 60 | ||
| 61 | // Prevent any possible memory re-allocations by using the length. | |
| 62 | final var chunk = new StringBuilder( length ); | |
| 63 | var c = s.charAt( marker ); | |
| 64 | final var chunkType = isDigit( c ); | |
| 65 | ||
| 66 | // While the character at the current position is the same type (numeric or | |
| 67 | // alphabetic), append the character to the current chunk. | |
| 68 | while( marker < length && | |
| 69 | isDigit( c = s.charAt( marker++ ) ) == chunkType ) { | |
| 70 | chunk.append( c ); | |
| 71 | } | |
| 72 | ||
| 73 | return chunk; | |
| 74 | } | |
| 75 | ||
| 76 | /** | |
| 77 | * Performs an alphanumeric comparison of two strings, sorting numerically | |
| 78 | * first when numbers are found within the string. If either argument is | |
| 79 | * {@code null}, this will return zero. | |
| 80 | * | |
| 81 | * @param o1 The object to compare against {@code s2}, converted to string. | |
| 82 | * @param o2 The object to compare against {@code s1}, converted to string. | |
| 83 | * @return a negative integer, zero, or a positive integer if the first | |
| 84 | * argument is less than, equal to, or greater than the second, respectively. | |
| 85 | */ | |
| 86 | @Override | |
| 87 | public int compare( final T o1, final T o2 ) { | |
| 88 | if( o1 == null || o2 == null ) { | |
| 89 | return 0; | |
| 90 | } | |
| 91 | ||
| 92 | final var s1 = o1.toString(); | |
| 93 | final var s2 = o2.toString(); | |
| 94 | final var s1Length = s1.length(); | |
| 95 | final var s2Length = s2.length(); | |
| 96 | ||
| 97 | var thisMarker = 0; | |
| 98 | var thatMarker = 0; | |
| 99 | ||
| 100 | while( thisMarker < s1Length && thatMarker < s2Length ) { | |
| 101 | final var thisChunk = chunk( s1, s1Length, thisMarker ); | |
| 102 | final var thisChunkLength = thisChunk.length(); | |
| 103 | thisMarker += thisChunkLength; | |
| 104 | final var thatChunk = chunk( s2, s2Length, thatMarker ); | |
| 105 | final var thatChunkLength = thatChunk.length(); | |
| 106 | thatMarker += thatChunkLength; | |
| 107 | ||
| 108 | // If both chunks contain numeric characters, sort them numerically | |
| 109 | int result; | |
| 110 | ||
| 111 | if( isDigit( thisChunk.charAt( 0 ) ) && | |
| 112 | isDigit( thatChunk.charAt( 0 ) ) ) { | |
| 113 | // If equal, the first different number counts | |
| 114 | if( (result = thisChunkLength - thatChunkLength) == 0 ) { | |
| 115 | for( var i = 0; i < thisChunkLength; i++ ) { | |
| 116 | if( (result = thisChunk.charAt( i ) - thatChunk.charAt( i )) != 0 ) { | |
| 117 | return result; | |
| 118 | } | |
| 119 | } | |
| 120 | } | |
| 121 | } | |
| 122 | else { | |
| 123 | result = thisChunk.compareTo( thatChunk ); | |
| 124 | } | |
| 125 | ||
| 126 | if( result != 0 ) { | |
| 127 | return result; | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | return s1Length - s2Length; | |
| 132 | } | |
| 133 | } | |
| 1 | 134 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import java.util.LinkedHashMap; | |
| 5 | import java.util.Map; | |
| 6 | ||
| 7 | /** | |
| 8 | * A map that removes the oldest entry once its capacity (cache size) has | |
| 9 | * been reached. | |
| 10 | * | |
| 11 | * @param <K> The type of key mapped to a value. | |
| 12 | * @param <V> The type of value mapped to a key. | |
| 13 | */ | |
| 14 | public final class BoundedCache<K, V> extends LinkedHashMap<K, V> { | |
| 15 | private final int mCacheSize; | |
| 16 | ||
| 17 | /** | |
| 18 | * Constructs a new instance having a finite size. | |
| 19 | * | |
| 20 | * @param cacheSize The maximum number of entries. | |
| 21 | */ | |
| 22 | public BoundedCache( final int cacheSize ) { | |
| 23 | mCacheSize = cacheSize; | |
| 24 | } | |
| 25 | ||
| 26 | @Override | |
| 27 | protected boolean removeEldestEntry( final Map.Entry<K, V> eldest ) { | |
| 28 | return size() > mCacheSize; | |
| 29 | } | |
| 30 | } | |
| 1 | 31 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import java.util.List; | |
| 5 | import java.util.ListIterator; | |
| 6 | import java.util.NoSuchElementException; | |
| 7 | ||
| 8 | /** | |
| 9 | * Responsible for iterating over a list either forwards or backwards. When | |
| 10 | * the iterator reaches the last element in the list, the next element will | |
| 11 | * be the first. When the iterator reaches the first element in the list, | |
| 12 | * the previous element will be the last. | |
| 13 | * <p> | |
| 14 | * Due to the ability to move forwards and backwards through the list, rather | |
| 15 | * than force client classes to track the list index independently, this | |
| 16 | * iterator provides an accessor to the index. The index is zero-based. | |
| 17 | * </p> | |
| 18 | * | |
| 19 | * @param <T> The type of list to be cycled. | |
| 20 | */ | |
| 21 | public final class CyclicIterator<T> implements ListIterator<T> { | |
| 22 | private final List<T> mList; | |
| 23 | ||
| 24 | /** | |
| 25 | * Initialize to an invalid index so that the first calls to either | |
| 26 | * {@link #previous()} or {@link #next()} will return the starting or ending | |
| 27 | * element. | |
| 28 | */ | |
| 29 | private int mIndex = -1; | |
| 30 | ||
| 31 | /** | |
| 32 | * Creates an iterator that cycles indefinitely through the given list. | |
| 33 | * | |
| 34 | * @param list The list to cycle through indefinitely. | |
| 35 | */ | |
| 36 | public CyclicIterator( final List<T> list ) { | |
| 37 | mList = list; | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * @return {@code true} if there is at least one element. | |
| 42 | */ | |
| 43 | @Override | |
| 44 | public boolean hasNext() { | |
| 45 | return !mList.isEmpty(); | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * @return {@code true} if there is at least one element. | |
| 50 | */ | |
| 51 | @Override | |
| 52 | public boolean hasPrevious() { | |
| 53 | return !mList.isEmpty(); | |
| 54 | } | |
| 55 | ||
| 56 | @Override | |
| 57 | public int nextIndex() { | |
| 58 | return computeIndex( +1 ); | |
| 59 | } | |
| 60 | ||
| 61 | @Override | |
| 62 | public int previousIndex() { | |
| 63 | return computeIndex( -1 ); | |
| 64 | } | |
| 65 | ||
| 66 | @Override | |
| 67 | public void remove() { | |
| 68 | mList.remove( mIndex ); | |
| 69 | } | |
| 70 | ||
| 71 | @Override | |
| 72 | public void set( final T t ) { | |
| 73 | mList.set( mIndex, t ); | |
| 74 | } | |
| 75 | ||
| 76 | @Override | |
| 77 | public void add( final T t ) { | |
| 78 | mList.add( mIndex, t ); | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Returns the next item in the list, which will cycle to the first | |
| 83 | * item as necessary. | |
| 84 | * | |
| 85 | * @return The next item in the list, cycling to the start if needed. | |
| 86 | */ | |
| 87 | @Override | |
| 88 | public T next() { | |
| 89 | return cycle( +1 ); | |
| 90 | } | |
| 91 | ||
| 92 | /** | |
| 93 | * Returns the previous item in the list, which will cycle to the last | |
| 94 | * item as necessary. | |
| 95 | * | |
| 96 | * @return The previous item in the list, cycling to the end if needed. | |
| 97 | */ | |
| 98 | @Override | |
| 99 | public T previous() { | |
| 100 | return cycle( -1 ); | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Cycles to the next or previous element, depending on the direction value. | |
| 105 | * | |
| 106 | * @param direction Use -1 for previous, +1 for next. | |
| 107 | * @return The next or previous item in the list. | |
| 108 | */ | |
| 109 | private T cycle( final int direction ) { | |
| 110 | try { | |
| 111 | return mList.get( mIndex = computeIndex( direction ) ); | |
| 112 | } catch( final Exception ex ) { | |
| 113 | throw new NoSuchElementException( ex ); | |
| 114 | } | |
| 115 | } | |
| 116 | ||
| 117 | /** | |
| 118 | * Returns the index of the value retrieved from the most recent call to | |
| 119 | * either {@link #previous()} or {@link #next()}. | |
| 120 | * | |
| 121 | * @return The list item index or -1 if no calls have been made to retrieve | |
| 122 | * an item from the list. | |
| 123 | */ | |
| 124 | public int getIndex() { | |
| 125 | return mIndex; | |
| 126 | } | |
| 127 | ||
| 128 | private int computeIndex( final int direction ) { | |
| 129 | final var i = mIndex + direction; | |
| 130 | final var size = mList.size(); | |
| 131 | final var result = i < 0 | |
| 132 | ? size - 1 | |
| 133 | : size == 0 ? 0 : i % size; | |
| 134 | ||
| 135 | // Ensure the invariant holds. | |
| 136 | assert 0 <= result && result < size || size == 0 && result <= 0; | |
| 137 | ||
| 138 | return result; | |
| 139 | } | |
| 140 | } | |
| 1 | 141 |
| 1 | /* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import java.io.IOException; | |
| 5 | import java.nio.file.Files; | |
| 6 | import java.nio.file.Path; | |
| 7 | import java.util.function.Consumer; | |
| 8 | ||
| 9 | import static java.nio.file.FileSystems.getDefault; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for finding files in a file system that match a particular | |
| 13 | * globbing file name pattern. | |
| 14 | * | |
| 15 | * @see ResourceWalker#walk(String, String, Consumer) | |
| 16 | */ | |
| 17 | public class FileWalker { | |
| 18 | /** | |
| 19 | * Walks the given directory hierarchy for files that match the given | |
| 20 | * globbing file name pattern. This will search to a depth of 10 directories | |
| 21 | * deep (to avoid infinite recursion). | |
| 22 | * | |
| 23 | * @param path Root directory to scan for files matching the glob. | |
| 24 | * @param glob Only files matching the pattern will be consumed. | |
| 25 | * @param c Function to call for each matching path found. | |
| 26 | * @throws IOException Could not walk the tree. | |
| 27 | */ | |
| 28 | public static void walk( | |
| 29 | final Path path, final String glob, final Consumer<Path> c ) | |
| 30 | throws IOException { | |
| 31 | final var matcher = getDefault().getPathMatcher( "glob:" + glob ); | |
| 32 | ||
| 33 | try( final var walk = Files.walk( path, 10 ) ) { | |
| 34 | for( final var it = walk.iterator(); it.hasNext(); ) { | |
| 35 | final var p = it.next(); | |
| 36 | if( matcher.matches( p ) ) { | |
| 37 | c.accept( p ); | |
| 38 | } | |
| 39 | } | |
| 40 | } | |
| 41 | } | |
| 42 | } | |
| 1 | 43 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import com.keenwrite.preview.HtmlPreview; | |
| 5 | ||
| 6 | import java.awt.*; | |
| 7 | import java.awt.font.TextAttribute; | |
| 8 | import java.io.FileInputStream; | |
| 9 | import java.io.IOException; | |
| 10 | import java.io.InputStream; | |
| 11 | import java.net.URI; | |
| 12 | import java.util.Map; | |
| 13 | ||
| 14 | import static com.keenwrite.constants.Constants.FONT_DIRECTORY; | |
| 15 | import static com.keenwrite.events.StatusEvent.clue; | |
| 16 | import static com.keenwrite.util.ProtocolScheme.valueFrom; | |
| 17 | import static com.keenwrite.util.ResourceWalker.walk; | |
| 18 | import static java.awt.Font.TRUETYPE_FONT; | |
| 19 | import static java.awt.Font.createFont; | |
| 20 | import static java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment; | |
| 21 | import static java.awt.font.TextAttribute.*; | |
| 22 | ||
| 23 | /** | |
| 24 | * Responsible for loading fonts into the application's | |
| 25 | * {@link GraphicsEnvironment} so that the {@link HtmlPreview} can display | |
| 26 | * the text using a non-system font. | |
| 27 | */ | |
| 28 | public final class FontLoader { | |
| 29 | /** | |
| 30 | * Globbing pattern to match font names. | |
| 31 | */ | |
| 32 | public static final String GLOB_FONTS = "**.{ttf,otf}"; | |
| 33 | ||
| 34 | /** | |
| 35 | * Walks the resources associated with the application to load all TrueType | |
| 36 | * font resources found. This method must run before the windowing system | |
| 37 | * kicks in, otherwise the fonts will not be found. | |
| 38 | * <p> | |
| 39 | * All fonts must be TrueType fonts. No PostScript Type 1 fonts are | |
| 40 | * supported. | |
| 41 | * </p> | |
| 42 | */ | |
| 43 | public static void initFonts() { | |
| 44 | // Editor, preview, and TeX fonts | |
| 45 | initFonts( FONT_DIRECTORY ); | |
| 46 | ||
| 47 | // FontAwesome font | |
| 48 | initFonts( "/org" ); | |
| 49 | } | |
| 50 | ||
| 51 | @SuppressWarnings( "unchecked" ) | |
| 52 | private static void initFonts( final String directory ) { | |
| 53 | try { | |
| 54 | final var ge = getLocalGraphicsEnvironment(); | |
| 55 | walk( | |
| 56 | directory, GLOB_FONTS, path -> { | |
| 57 | final var uri = path.toUri(); | |
| 58 | final var filename = path.toString(); | |
| 59 | ||
| 60 | try( final var is = openFont( uri, filename ) ) { | |
| 61 | final var font = createFont( TRUETYPE_FONT, is ); | |
| 62 | final var attributes = | |
| 63 | (Map<TextAttribute, Integer>) font.getAttributes(); | |
| 64 | ||
| 65 | attributes.put( LIGATURES, LIGATURES_ON ); | |
| 66 | attributes.put( KERNING, KERNING_ON ); | |
| 67 | ge.registerFont( font.deriveFont( attributes ) ); | |
| 68 | } catch( final Exception ex ) { | |
| 69 | clue( ex ); | |
| 70 | } | |
| 71 | } | |
| 72 | ); | |
| 73 | } catch( final Exception ex ) { | |
| 74 | clue( ex ); | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | /** | |
| 79 | * Attempts to open a font, regardless of whether the font is a resource in | |
| 80 | * a JAR file or somewhere on the file system. | |
| 81 | * | |
| 82 | * @param uri Directory or archive containing a font. | |
| 83 | * @param filename Name of the font file. | |
| 84 | * @return An open file handled to the font. | |
| 85 | * @throws IOException Could not open the resource as a stream. | |
| 86 | */ | |
| 87 | private static InputStream openFont( final URI uri, final String filename ) | |
| 88 | throws IOException { | |
| 89 | return valueFrom( uri ).isJar() | |
| 90 | ? FontLoader.class.getResourceAsStream( filename ) | |
| 91 | : new FileInputStream( filename ); | |
| 92 | } | |
| 93 | } | |
| 1 | 94 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import java.util.ArrayList; | |
| 5 | import java.util.List; | |
| 6 | import java.util.function.BiConsumer; | |
| 7 | import java.util.function.Consumer; | |
| 8 | import java.util.function.Function; | |
| 9 | import java.util.function.Supplier; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for constructing objects that would otherwise require | |
| 13 | * a long list of constructor parameters. | |
| 14 | * <p> | |
| 15 | * See <a href="https://stackoverflow.com/a/31754787/59087">source</a> for | |
| 16 | * details. | |
| 17 | * </p> | |
| 18 | * | |
| 19 | * @param <MT> The mutable definition for the type of object to build. | |
| 20 | * @param <IT> The immutable definition for the type of object to build. | |
| 21 | */ | |
| 22 | public class GenericBuilder<MT, IT> { | |
| 23 | /** | |
| 24 | * Provides the methods to use for setting object properties. | |
| 25 | */ | |
| 26 | private final Supplier<MT> mMutable; | |
| 27 | ||
| 28 | /** | |
| 29 | * Calling {@link #build()} will instantiate the immutable instance using | |
| 30 | * the mutator. | |
| 31 | */ | |
| 32 | private final Function<MT, IT> mImmutable; | |
| 33 | ||
| 34 | /** | |
| 35 | * Adds a modifier to call when building an instance. | |
| 36 | */ | |
| 37 | private final List<Consumer<MT>> mModifiers = new ArrayList<>(); | |
| 38 | ||
| 39 | /** | |
| 40 | * Constructs a new builder instance that is capable of populating values for | |
| 41 | * any type of object. | |
| 42 | * | |
| 43 | * @param mutator Provides methods to use for setting object properties. | |
| 44 | */ | |
| 45 | protected GenericBuilder( | |
| 46 | final Supplier<MT> mutator, final Function<MT, IT> immutable ) { | |
| 47 | assert mutator != null; | |
| 48 | assert immutable != null; | |
| 49 | ||
| 50 | mMutable = mutator; | |
| 51 | mImmutable = immutable; | |
| 52 | } | |
| 53 | ||
| 54 | /** | |
| 55 | * Starting point for building an instance of a particular class. | |
| 56 | * | |
| 57 | * @param supplier Returns the instance to build. | |
| 58 | * @param <MT> The type of class to build. | |
| 59 | * @return A new {@link GenericBuilder} capable of populating data for an | |
| 60 | * instance of the class provided by the {@link Supplier}. | |
| 61 | */ | |
| 62 | public static <MT, IT> GenericBuilder<MT, IT> of( | |
| 63 | final Supplier<MT> supplier, final Function<MT, IT> immutable ) { | |
| 64 | return new GenericBuilder<>( supplier, immutable ); | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Registers a new value with the builder. | |
| 69 | * | |
| 70 | * @param consumer Accepts a value to be set upon the built object. | |
| 71 | * @param value The value to use when building. | |
| 72 | * @param <V> The type of value used when building. | |
| 73 | * @return This {@link GenericBuilder} instance. | |
| 74 | */ | |
| 75 | public <V> GenericBuilder<MT, IT> with( | |
| 76 | final BiConsumer<MT, V> consumer, final V value ) { | |
| 77 | mModifiers.add( instance -> consumer.accept( instance, value ) ); | |
| 78 | return this; | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Instantiates then populates the immutable object to build. | |
| 83 | * | |
| 84 | * @return The newly built object. | |
| 85 | */ | |
| 86 | public IT build() { | |
| 87 | final var value = mMutable.get(); | |
| 88 | mModifiers.forEach( modifier -> modifier.accept( value ) ); | |
| 89 | mModifiers.clear(); | |
| 90 | return mImmutable.apply( value ); | |
| 91 | } | |
| 92 | } | |
| 1 | 93 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import java.io.File; | |
| 5 | import java.net.MalformedURLException; | |
| 6 | import java.net.URI; | |
| 7 | import java.net.URL; | |
| 8 | ||
| 9 | import static com.keenwrite.events.StatusEvent.clue; | |
| 10 | ||
| 11 | /** | |
| 12 | * Represents the type of data encoding scheme used for a universal resource | |
| 13 | * indicator. Prefer to use the {@code is*} methods to check equality because | |
| 14 | * there are cases where the protocol represents more than one possible type | |
| 15 | * (e.g., a Java Archive is a file, so comparing {@link #FILE} directly could | |
| 16 | * lead to incorrect results). | |
| 17 | */ | |
| 18 | public enum ProtocolScheme { | |
| 19 | /** | |
| 20 | * Denotes a local file. | |
| 21 | */ | |
| 22 | FILE, | |
| 23 | /** | |
| 24 | * Denotes either HTTP or HTTPS. | |
| 25 | */ | |
| 26 | HTTP, | |
| 27 | /** | |
| 28 | * Denotes the File Transfer Protocol. | |
| 29 | */ | |
| 30 | FTP, | |
| 31 | /** | |
| 32 | * Denotes Java archive file. | |
| 33 | */ | |
| 34 | JAR, | |
| 35 | /** | |
| 36 | * Could not determine schema (or is not supported by the application). | |
| 37 | */ | |
| 38 | UNKNOWN; | |
| 39 | ||
| 40 | /** | |
| 41 | * Returns the protocol for a given URI or file name. | |
| 42 | * | |
| 43 | * @param uri Determine the protocol for this URI or file name. | |
| 44 | * @return The protocol for the given resource. | |
| 45 | */ | |
| 46 | public static ProtocolScheme getProtocol( final String uri ) { | |
| 47 | try { | |
| 48 | return getProtocol( new URI( uri ) ); | |
| 49 | } catch( final Exception ex ) { | |
| 50 | // Using double-slashes is a short-hand to instruct the browser to | |
| 51 | // reference a resource using the parent URL's security model. This | |
| 52 | // is known as a protocol-relative URL. | |
| 53 | return uri.startsWith( "//" ) ? HTTP : valueFrom( new File( uri ) ); | |
| 54 | } | |
| 55 | } | |
| 56 | ||
| 57 | /** | |
| 58 | * Returns the protocol for a given URI or file name. | |
| 59 | * | |
| 60 | * @param uri Determine the protocol for this URI or file name. | |
| 61 | * @return The protocol for the given resource. | |
| 62 | */ | |
| 63 | public static ProtocolScheme getProtocol( final URI uri ) | |
| 64 | throws MalformedURLException { | |
| 65 | return uri.isAbsolute() | |
| 66 | ? valueFrom( uri ) | |
| 67 | : valueFrom( uri.toURL() ); | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Determines the protocol scheme for a given string. | |
| 72 | * | |
| 73 | * @param protocol A string representing data encoding protocol scheme. | |
| 74 | * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a | |
| 75 | * valid value from this enumeration. | |
| 76 | */ | |
| 77 | public static ProtocolScheme valueFrom( final String protocol ) { | |
| 78 | final var sanitized = protocol == null ? "" : protocol.toUpperCase(); | |
| 79 | ||
| 80 | for( final var scheme : values() ) { | |
| 81 | // This will match HTTP/HTTPS as well as FILE*, which may be inaccurate. | |
| 82 | if( sanitized.startsWith( scheme.name() ) ) { | |
| 83 | return scheme; | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | return UNKNOWN; | |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * Determines the protocol scheme for a given {@link File}. | |
| 92 | * | |
| 93 | * @param file A file having a URI that contains a protocol scheme. | |
| 94 | * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a | |
| 95 | * valid value from this enumeration. | |
| 96 | */ | |
| 97 | public static ProtocolScheme valueFrom( final File file ) { | |
| 98 | return valueFrom( file.toURI() ); | |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Determines the protocol scheme for a given {@link URI}. | |
| 103 | * | |
| 104 | * @param uri A URI that contains a protocol scheme. | |
| 105 | * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a | |
| 106 | * valid value from this enumeration. | |
| 107 | */ | |
| 108 | public static ProtocolScheme valueFrom( final URI uri ) { | |
| 109 | try { | |
| 110 | return valueFrom( uri.toURL() ); | |
| 111 | } catch( final Exception ex ) { | |
| 112 | clue( ex ); | |
| 113 | return UNKNOWN; | |
| 114 | } | |
| 115 | } | |
| 116 | ||
| 117 | /** | |
| 118 | * Determines the protocol scheme for a given {@link URL}. | |
| 119 | * | |
| 120 | * @param url The {@link URL} containing a protocol scheme. | |
| 121 | * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a | |
| 122 | * valid value from this enumeration. | |
| 123 | */ | |
| 124 | public static ProtocolScheme valueFrom( final URL url ) { | |
| 125 | return valueFrom( url.getProtocol() ); | |
| 126 | } | |
| 127 | ||
| 128 | /** | |
| 129 | * Answers whether the given {@link URL} points to a remote resource. | |
| 130 | * | |
| 131 | * @param url The {@link URL} containing a protocol scheme. | |
| 132 | * @return {@link true} if the protocol must be fetched via HTTP or FTP. | |
| 133 | */ | |
| 134 | public static boolean isRemote( final URL url ) { | |
| 135 | return valueFrom( url ).isRemote(); | |
| 136 | } | |
| 137 | ||
| 138 | /** | |
| 139 | * Answers {@code true} if the given protocol is for a local file, which | |
| 140 | * includes a JAR file. | |
| 141 | * | |
| 142 | * @return {@code false} the protocol is not a local file reference. | |
| 143 | */ | |
| 144 | public boolean isFile() { | |
| 145 | return this == FILE || this == JAR; | |
| 146 | } | |
| 147 | ||
| 148 | /** | |
| 149 | * Answers whether the given protocol is HTTP or HTTPS. | |
| 150 | * | |
| 151 | * @return {@code true} the protocol is either HTTP or HTTPS. | |
| 152 | */ | |
| 153 | public boolean isHttp() { | |
| 154 | return this == HTTP; | |
| 155 | } | |
| 156 | ||
| 157 | /** | |
| 158 | * Answers whether the given protocol is FTP. | |
| 159 | * | |
| 160 | * @return {@code true} the protocol is FTP. | |
| 161 | */ | |
| 162 | public boolean isFtp() { | |
| 163 | return this == HTTP; | |
| 164 | } | |
| 165 | ||
| 166 | /** | |
| 167 | * Answers whether the given protocol represents a remote resource. | |
| 168 | * | |
| 169 | * @return {@code true} the protocol is HTTP or FTP. | |
| 170 | */ | |
| 171 | public boolean isRemote() { | |
| 172 | return isHttp() || isFtp(); | |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * Answers {@code true} if the given protocol is for a Java archive file. | |
| 177 | * | |
| 178 | * @return {@code false} the protocol is not a Java archive file. | |
| 179 | */ | |
| 180 | public boolean isJar() { | |
| 181 | return this == JAR; | |
| 182 | } | |
| 183 | } | |
| 1 | 184 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import java.io.IOException; | |
| 5 | import java.net.URISyntaxException; | |
| 6 | import java.nio.file.FileSystem; | |
| 7 | import java.nio.file.Path; | |
| 8 | import java.nio.file.Paths; | |
| 9 | import java.util.function.Consumer; | |
| 10 | ||
| 11 | import static com.keenwrite.util.ProtocolScheme.JAR; | |
| 12 | import static com.keenwrite.util.ProtocolScheme.valueFrom; | |
| 13 | import static java.nio.file.FileSystems.newFileSystem; | |
| 14 | import static java.util.Collections.emptyMap; | |
| 15 | ||
| 16 | /** | |
| 17 | * Responsible for finding file resources, regardless if they exist within | |
| 18 | * a Java Archive (.jar) file or on the native file system. | |
| 19 | * | |
| 20 | * @see FileWalker#walk(Path, String, Consumer) | |
| 21 | */ | |
| 22 | public final class ResourceWalker { | |
| 23 | ||
| 24 | /** | |
| 25 | * Walks the given directory hierarchy for files that match the given | |
| 26 | * globbing file name pattern. | |
| 27 | * | |
| 28 | * @param directory Root directory to scan for files matching the glob. | |
| 29 | * @param glob Only files matching the pattern will be consumed. | |
| 30 | * @param c Function to call for each matching path found. | |
| 31 | * @throws IOException Could not walk the tree. | |
| 32 | * @throws URISyntaxException Could not convert the resource to a URI. | |
| 33 | */ | |
| 34 | public static void walk( | |
| 35 | final String directory, final String glob, final Consumer<Path> c ) | |
| 36 | throws URISyntaxException, IOException { | |
| 37 | final var resource = ResourceWalker.class.getResource( directory ); | |
| 38 | ||
| 39 | if( resource != null ) { | |
| 40 | final var uri = resource.toURI(); | |
| 41 | final Path path; | |
| 42 | FileSystem fs = null; | |
| 43 | ||
| 44 | if( valueFrom( uri ) == JAR ) { | |
| 45 | fs = newFileSystem( uri, emptyMap() ); | |
| 46 | path = fs.getPath( directory ); | |
| 47 | } | |
| 48 | else { | |
| 49 | path = Paths.get( uri ); | |
| 50 | } | |
| 51 | ||
| 52 | try { | |
| 53 | FileWalker.walk( path, glob, c ); | |
| 54 | } finally { | |
| 55 | if( fs != null ) { fs.close(); } | |
| 56 | } | |
| 57 | } | |
| 58 | } | |
| 59 | } | |
| 1 | 60 |
| 1 | # R Scripts | |
| 2 | ||
| 3 | These R scripts illustrate how R can be used within an application to perform calculations using variables. Authors are free to write their own scripts, of course. These scripts serve as an example of how to automate certain tasks while writing. | |
| 4 | ||
| 5 | ## Configuration | |
| 6 | ||
| 7 | Configure the editor to use the R scripts as follows: | |
| 8 | ||
| 9 | 1. Copy the R scripts into same directory as your Markdown files. | |
| 10 | 1. Start the editor. | |
| 11 | 1. Click **Tools → R Script**. | |
| 12 | 1. Copy and paste the following: | |
| 13 | ||
| 14 | assign( 'anchor', as.Date( '$date.anchor$', format='%Y-%m-%d' ), envir = .GlobalEnv ); | |
| 15 | setwd( '$application.r.working.directory$' ); | |
| 16 | source( 'pluralize.R' ); | |
| 17 | source( 'csv.R' ); | |
| 18 | source( 'conversion.R' ); | |
| 19 | ||
| 20 | 1. Click **File → New** to create a new file. | |
| 21 | 1. Click **File → Save As** to set a filename. | |
| 22 | 1. Set **Name** to: `variables.yaml` | |
| 23 | 1. Click **OK**. | |
| 24 | 1. Paste the following definitions: | |
| 25 | ||
| 26 | date: | |
| 27 | anchor: 2017-01-01 | |
| 28 | editor: | |
| 29 | examples: | |
| 30 | season: 2017-09-02 | |
| 31 | math: | |
| 32 | x: 1 | |
| 33 | y: $editor.examples.math.x$ + 1 | |
| 34 | z: $editor.examples.math.y$ + 1 | |
| 35 | name: | |
| 36 | given: Josephene | |
| 37 | ||
| 38 | 1. Save and close the file. | |
| 39 | 1. Click **File → Open** | |
| 40 | 1. Change **Markdown Files** to **Definition Files**. | |
| 41 | 1. Select `variables.yaml`. | |
| 42 | 1. Click **Open**. | |
| 43 | ||
| 44 | R functionality is configured. | |
| 45 | ||
| 46 | ## Definitions | |
| 47 | ||
| 48 | The variables definitions within `variables.yaml` are available to R using the R syntax. An additional variable, `application.r.working.directory` is added to the list of variables. The value is set to the working directory of the file being edited. Hover the mouse cursor over the file tab in the editor to see the full path to the file. | |
| 49 | ||
| 50 | ## Examples | |
| 51 | ||
| 52 | This section demonstrates how to use the R functions when editing. Complete the following steps to begin: | |
| 53 | ||
| 54 | 1. Click **File → New** to create a new file. | |
| 55 | 1. Click **File → Save As** to set a filename. | |
| 56 | 1. Set **Name** to: `example.Rmd` | |
| 57 | 1. Click **OK**. | |
| 58 | ||
| 59 | The examples are ready for use within the editor. | |
| 60 | ||
| 61 | ### Arithmetic | |
| 62 | ||
| 63 | Type the following to perform a simple calculation: | |
| 64 | ||
| 65 | `r# 1+1` | |
| 66 | ||
| 67 | The preview pane shows `2.0`. | |
| 68 | ||
| 69 | ### Functions | |
| 70 | ||
| 71 | Call the [format](https://stat.ethz.ch/R-manual/R-devel/library/base/html/format.html) function to truncate unwanted decimal places as follows: | |
| 72 | ||
| 73 | `r# format(1+1,digits=1)` | |
| 74 | ||
| 75 | The preview pane shows `2`. | |
| 76 | ||
| 77 | ### Pluralize | |
| 78 | ||
| 79 | Many English words can be pluralized as follows: | |
| 80 | ||
| 81 | `r# pl('wolf',2)` | |
| 82 | ||
| 83 | The preview pane shows `wolves`. The `pluralize.R` file contains a partial implementation of Damian Conway's algorithmic approach to English pluralization. | |
| 84 | ||
| 85 | ### Chicago Manual of Style | |
| 86 | ||
| 87 | Apply the Chicago Manual of Style for words less than one-hundred as follows: | |
| 88 | ||
| 89 | `r# cms(1)` `r# cms(99)` `r# cms(101)` | |
| 90 | ||
| 91 | The preview pane shows numbers written out as `one` and `ninety-nine`, followed by the digits 101. | |
| 92 | ||
| 93 | ### Data Import | |
| 94 | ||
| 95 | Import and display information from a CSV file as follows: | |
| 96 | ||
| 97 | 1. Click **File → New** to create a new file. | |
| 98 | 1. Click **File → Save As** to rename the file. | |
| 99 | 1. Set the filename to: `data.csv` | |
| 100 | 1. Paste the following into `data.csv`: | |
| 101 | ||
| 102 | Animal,Quantity,Country | |
| 103 | Aardwolf,1,Africa | |
| 104 | Keel-billed toucan,1,Belize | |
| 105 | Beaver,2,Canada | |
| 106 | Mute swan,3,Denmark | |
| 107 | Lion,5,Ethiopia | |
| 108 | Brown bear,8,Finland | |
| 109 | Dolphin,13,Greece | |
| 110 | Turul,21,Hungary | |
| 111 | Gyrfalcon,34,Iceland | |
| 112 | Red-billed streamertail,55,Jamaica | |
| 113 | ||
| 114 | 1. Click the `example.Rmd` tab. | |
| 115 | 1. Type the following: | |
| 116 | ||
| 117 | `r# csv2md('data.csv',total=F)` | |
| 118 | ||
| 119 | 1. Type the following to calculate a total for all numeric columns: | |
| 120 | ||
| 121 | `r# csv2md('data.csv')` | |
| 122 | ||
| 123 | This imports the data from an external file and formats the information into a table, automatically. Update the data as follows: | |
| 124 | ||
| 125 | 1. Click the `data.csv` tab to edit the data. | |
| 126 | 1. Change the data by adding a new row. | |
| 127 | 1. Save the file. | |
| 128 | 1. Click the `example.Rmd` tab. | |
| 129 | ||
| 130 | The preview pane shows the revised contents. | |
| 131 | ||
| 132 | ### Elapsed Time | |
| 133 | ||
| 134 | The duration of a timeline, given in numbers of days, can be computed into English as follows: | |
| 135 | ||
| 136 | `r# elapsed(1,1)` | |
| 137 | ||
| 138 | The preview pane shows `same day`. Change the expression to: | |
| 139 | ||
| 140 | `r# elapsed(1,2)` | |
| 141 | ||
| 142 | The preview pane shows `one day`. Change the expression to: | |
| 143 | ||
| 144 | `r# elapsed(1,112358)` | |
| 145 | ||
| 146 | The preview pane shows `307 years, seven months, and sixteen days`, combined using the Chicago Manual of Style, the pluralization function, and a [serial comma](https://www.behance.net/gallery/19417363/The-Oxford-Comma). | |
| 147 | ||
| 148 | ### Variable Syntax | |
| 149 | ||
| 150 | The syntax for a variable changes when using an R Markdown file (denoted by the `.Rmd` filename extension), as opposed to a regular Markdown file (`.md`). Return to the example file and type the following: | |
| 151 | ||
| 152 | `r# v$date$anchor` | |
| 153 | ||
| 154 | The preview pane shows the date. | |
| 155 | ||
| 156 | ### Autocomplete | |
| 157 | ||
| 158 | Automatically insert a variable reference into the text as follows: | |
| 159 | ||
| 160 | 1. Type: `Jos` | |
| 161 | * Note the capital letter, matches are case sensitive. | |
| 162 | 1. Hold down the `Control` key. | |
| 163 | 1. Tap the `Spacebar` | |
| 164 | ||
| 165 | The editor shows: | |
| 166 | ||
| 167 | `r#x( v$editor$examples$name$given )` | |
| 168 | ||
| 169 | The preview pane shows: | |
| 170 | ||
| 171 | Josephine | |
| 172 | ||
| 173 | Here, the `x` function evaluates its parameter as an expression. This allows variables to include expressions in their definition. | |
| 174 | ||
| 175 | ### Variable Definition Expressions | |
| 176 | ||
| 177 | Definition file variables are have the ability to reference other definitions. Try the following: | |
| 178 | ||
| 179 | x = `r#x( v$editor$examples$math$x )`; | |
| 180 | y = `r#x( v$editor$examples$math$y )`; | |
| 181 | z = `r#x( v$editor$examples$math$z )` | |
| 182 | ||
| 183 | The preview pane shows: | |
| 184 | ||
| 185 | x = 1.0; y = 2.0; z = 3.0 | |
| 186 | ||
| 187 | ### Case | |
| 188 | ||
| 189 | Ensure words begin with a lowercase letter as follows: | |
| 190 | ||
| 191 | `r#lc( v$editor$examples$name$given )` | |
| 192 | ||
| 193 | The preview pane shows: | |
| 194 | ||
| 195 | josephine | |
| 196 | ||
| 197 | Similarly, ensure an uppercase letter as follows: | |
| 198 | ||
| 199 | `r#uc( 'hello, world!' )` | |
| 200 | ||
| 201 | The preview pane shows: | |
| 202 | ||
| 203 | Hello, world! | |
| 204 | ||
| 205 | ### Month | |
| 206 | ||
| 207 | Display the month name given a month number as follows: | |
| 208 | ||
| 209 | `r# month( 1 )` | |
| 210 | ||
| 211 | The preview pane shows: | |
| 212 | ||
| 213 | January | |
| 214 | ||
| 215 | ## Summary | |
| 216 | ||
| 217 | Authors can inline R statements into documents, directly, so long as those statements generate text. Plots, graphs, and images must be referenced as external image files or URLs. | |
| 1 | 218 |
| 1 | # ######################################################################## | |
| 2 | # | |
| 3 | # Substitute R expressions in a document with their evaluated value. The | |
| 4 | # anchor variable must be set for functions that use relative dates. | |
| 5 | # | |
| 6 | # ######################################################################## | |
| 7 | ||
| 8 | # Evaluates an expression; writes s if there is no expression. | |
| 9 | x <- function( s ) { | |
| 10 | return( | |
| 11 | tryCatch({ | |
| 12 | r = eval( parse( text=s ) ) | |
| 13 | ||
| 14 | # If the result isn't primitive, then it was probably parsed into | |
| 15 | # an unprintable object (e.g., "gray" becomes a colour). In those | |
| 16 | # cases, return the original text string. Otherwise, an atomic | |
| 17 | # value means a primitive type (string, integer, etc.) that can be | |
| 18 | # written directly into the document. | |
| 19 | # | |
| 20 | # See: http://stackoverflow.com/a/19501276/59087 | |
| 21 | if( is.atomic( r ) ) { | |
| 22 | r | |
| 23 | } | |
| 24 | else { | |
| 25 | s | |
| 26 | } | |
| 27 | }, | |
| 28 | warning = function( w ) { | |
| 29 | s | |
| 30 | }, | |
| 31 | error = function( e ) { | |
| 32 | s | |
| 33 | }) | |
| 34 | ) | |
| 35 | } | |
| 36 | ||
| 37 | # Returns a date offset by a given number of days, relative to the given | |
| 38 | # date (d). This does not use the anchor, but is used to get the anchor's | |
| 39 | # value as a date. | |
| 40 | when <- function( d, n = 0, format = "%Y-%m-%d" ) { | |
| 41 | as.Date( d, format = format ) + x( n ) | |
| 42 | } | |
| 43 | ||
| 44 | # Full date (s) offset by an optional number of days before or after. | |
| 45 | # This will remove leading zeros (applying leading spaces instead, which | |
| 46 | # are ignored by any worthwhile typesetting engine). | |
| 47 | annal <- function( days = 0, format = "%Y-%m-%d", oformat = "%B %d, %Y" ) { | |
| 48 | format( when( anchor, days ), format = oformat ) | |
| 49 | } | |
| 50 | ||
| 51 | # Extracts the year from a date string. | |
| 52 | year <- function( days = 0, format = "%Y-%m-%d" ) { | |
| 53 | annal( days, format, "%Y" ) | |
| 54 | } | |
| 55 | ||
| 56 | # Day of the week (in days since the anchor date). | |
| 57 | weekday <- function( n ) { | |
| 58 | weekdays( when( anchor, n ) ) | |
| 59 | } | |
| 60 | ||
| 61 | # String concatenate function alias because paste0 is a terrible name. | |
| 62 | concat <- paste0 | |
| 63 | ||
| 64 | # Translates a number from digits to words using Chicago Manual of Style. | |
| 65 | # This does not translate numbers greater than one hundred. If ordinal | |
| 66 | # is TRUE, this will return the ordinal name. This will not produce ordinals | |
| 67 | # for numbers greater than 100 | |
| 68 | cms <- function( n, ordinal = FALSE ) { | |
| 69 | n <- x( n ) | |
| 70 | ||
| 71 | # We're done here. | |
| 72 | if( n == 0 ) { | |
| 73 | if( ordinal ) { | |
| 74 | return( "zeroth" ) | |
| 75 | } | |
| 76 | ||
| 77 | return( "zero" ) | |
| 78 | } | |
| 79 | ||
| 80 | # Concatenate this a little later. | |
| 81 | if( n < 0 ) { | |
| 82 | result = "negative " | |
| 83 | n = abs( n ) | |
| 84 | } | |
| 85 | ||
| 86 | # Do not spell out numbers greater than one hundred. | |
| 87 | if( n > 100 ) { | |
| 88 | # Comma-separated numbers. | |
| 89 | return( format( n, big.mark=",", trim=TRUE, scientific=FALSE ) ) | |
| 90 | } | |
| 91 | ||
| 92 | # Don't go beyond 100. | |
| 93 | if( n == 100 ) { | |
| 94 | if( ordinal ) { | |
| 95 | return( "one hundredth" ) | |
| 96 | } | |
| 97 | ||
| 98 | return( "one hundred" ) | |
| 99 | } | |
| 100 | ||
| 101 | # Samuel Langhorne Clemens noted English has too many exceptions. | |
| 102 | small = c( | |
| 103 | "one", "two", "three", "four", "five", | |
| 104 | "six", "seven", "eight", "nine", "ten", | |
| 105 | "eleven", "twelve", "thirteen", "fourteen", "fifteen", | |
| 106 | "sixteen", "seventeen", "eighteen", "nineteen" | |
| 107 | ) | |
| 108 | ||
| 109 | ord_small = c( | |
| 110 | "first", "second", "third", "fourth", "fifth", | |
| 111 | "sixth", "seventh", "eighth", "ninth", "tenth", | |
| 112 | "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", | |
| 113 | "sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth" | |
| 114 | ) | |
| 115 | ||
| 116 | # After this, the number (n) is between 20 and 99. | |
| 117 | if( n < 20 ) { | |
| 118 | if( ordinal ) { | |
| 119 | return( .subset( ord_small, n %% 100 ) ) | |
| 120 | } | |
| 121 | ||
| 122 | return( .subset( small, n %% 100 ) ) | |
| 123 | } | |
| 124 | ||
| 125 | tens = c( "", | |
| 126 | "twenty", "thirty", "forty", "fifty", | |
| 127 | "sixty", "seventy", "eighty", "ninety" | |
| 128 | ) | |
| 129 | ||
| 130 | ord_tens = c( "", | |
| 131 | "twentieth", "thirtieth", "fortieth", "fiftieth", | |
| 132 | "sixtieth", "seventieth", "eightieth", "ninetieth" | |
| 133 | ) | |
| 134 | ||
| 135 | ones_index = n %% 10 | |
| 136 | n = n %/% 10 | |
| 137 | ||
| 138 | # No number in the ones column, so the number must be a multiple of ten. | |
| 139 | if( ones_index == 0 ) { | |
| 140 | if( ordinal ) { | |
| 141 | return( .subset( ord_tens, n ) ) | |
| 142 | } | |
| 143 | ||
| 144 | return( .subset( tens, n ) ) | |
| 145 | } | |
| 146 | ||
| 147 | # Find the value from the ones column. | |
| 148 | if( ordinal ) { | |
| 149 | unit_1 = .subset( ord_small, ones_index ) | |
| 150 | } | |
| 151 | else { | |
| 152 | unit_1 = .subset( small, ones_index ) | |
| 153 | } | |
| 154 | ||
| 155 | # Find the tens column. | |
| 156 | unit_10 = .subset( tens, n ) | |
| 157 | ||
| 158 | # Hyphenate the tens and the ones together. | |
| 159 | concat( unit_10, concat( "-", unit_1 ) ) | |
| 160 | } | |
| 161 | ||
| 162 | # Returns a human-readable string that provides the elapsed time between | |
| 163 | # two numbers in terms of years, months, and days. If any unit value is zero, | |
| 164 | # the unit is not included. The words (year, month, day) are pluralized | |
| 165 | # according to English grammar. The numbers are written out according to | |
| 166 | # Chicago Manual of Style. This applies the serial comma. | |
| 167 | # | |
| 168 | # Both numbers are offsets relative to the anchor date. | |
| 169 | # | |
| 170 | # If all unit values are zero, this returns s ("same day" by default). | |
| 171 | # | |
| 172 | # If the start date (began) is greater than end date (ended), the dates are | |
| 173 | # swapped before calculations are performed. This allows any two dates | |
| 174 | # to be compared and positive unit values are always returned. | |
| 175 | # | |
| 176 | elapsed <- function( began, ended, s = "same day" ) { | |
| 177 | began = when( anchor, began ) | |
| 178 | ended = when( anchor, ended ) | |
| 179 | ||
| 180 | # Swap the dates if the end date comes before the start date. | |
| 181 | if( as.integer( ended - began ) < 0 ) { | |
| 182 | tempd = began | |
| 183 | began = ended | |
| 184 | ended = tempd | |
| 185 | } | |
| 186 | ||
| 187 | # Calculate number of elapsed years. | |
| 188 | years = length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 189 | ||
| 190 | # Move the start date up by the number of elapsed years. | |
| 191 | if( years > 0 ) { | |
| 192 | began = seq( began, length = 2, by = concat( years, " years" ) )[2] | |
| 193 | years = pl.numeric( "year", years ) | |
| 194 | } | |
| 195 | else { | |
| 196 | # Zero years. | |
| 197 | years = "" | |
| 198 | } | |
| 199 | ||
| 200 | # Calculate number of elapsed months, excluding years. | |
| 201 | months = length( seq( from = began, to = ended, by = 'month' ) ) - 1 | |
| 202 | ||
| 203 | # Move the start date up by the number of elapsed months | |
| 204 | if( months > 0 ) { | |
| 205 | began = seq( began, length = 2, by = concat( months, " months" ) )[2] | |
| 206 | months = pl.numeric( "month", months ) | |
| 207 | } | |
| 208 | else { | |
| 209 | # Zero months | |
| 210 | months = "" | |
| 211 | } | |
| 212 | ||
| 213 | # Calculate number of elapsed days, excluding months and years. | |
| 214 | days = length( seq( from = began, to = ended, by = 'day' ) ) - 1 | |
| 215 | ||
| 216 | if( days > 0 ) { | |
| 217 | days = pl.numeric( "day", days ) | |
| 218 | } | |
| 219 | else { | |
| 220 | # Zero days | |
| 221 | days = "" | |
| 222 | } | |
| 223 | ||
| 224 | if( years <= 0 && months <= 0 && days <= 0 ) { | |
| 225 | return( s ) | |
| 226 | } | |
| 227 | ||
| 228 | # Put them all in a vector, then remove the empty values. | |
| 229 | s <- c( years, months, days ) | |
| 230 | s <- s[ s != "" ] | |
| 231 | ||
| 232 | r <- paste( s, collapse = ", " ) | |
| 233 | ||
| 234 | # If all three items are present, replace the last comma with ", and". | |
| 235 | if( length( s ) > 2 ) { | |
| 236 | return( gsub( "(.*),", "\\1, and", r ) ) | |
| 237 | } | |
| 238 | ||
| 239 | # Does nothing if no commas are present. | |
| 240 | gsub( "(.*),", "\\1 and", r ) | |
| 241 | } | |
| 242 | ||
| 243 | # Returns the number (n) in English followed by the plural or singular | |
| 244 | # form of the given string (s; resumably a noun), if applicable, according | |
| 245 | # to English grammar. That is, pl.numeric( "wolf", 5 ) will return | |
| 246 | # "five wolves". | |
| 247 | pl.numeric <- function( s, n ) { | |
| 248 | concat( cms( n ), concat( " ", pluralize( s, n ) ) ) | |
| 249 | } | |
| 250 | ||
| 251 | # Name of the season, starting with an capital letter. | |
| 252 | season <- function( n, format = "%Y-%m-%d" ) { | |
| 253 | WS <- as.Date("2016-12-15", "%Y-%m-%d") # Winter Solstice | |
| 254 | SE <- as.Date("2016-03-15", "%Y-%m-%d") # Spring Equinox | |
| 255 | SS <- as.Date("2016-06-15", "%Y-%m-%d") # Summer Solstice | |
| 256 | AE <- as.Date("2016-09-15", "%Y-%m-%d") # Autumn Equinox | |
| 257 | ||
| 258 | d <- when( anchor, n ) | |
| 259 | d <- as.Date( strftime( d, format="2016-%m-%d" ) ) | |
| 260 | ||
| 261 | ifelse( d >= WS | d < SE, "Winter", | |
| 262 | ifelse( d >= SE & d < SS, "Spring", | |
| 263 | ifelse( d >= SS & d < AE, "Summer", "Autumn" ) | |
| 264 | ) | |
| 265 | ) | |
| 266 | } | |
| 267 | ||
| 268 | # Converts the first letter in a string to lowercase | |
| 269 | lc <- function( s ) { | |
| 270 | concat( tolower( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 271 | } | |
| 272 | ||
| 273 | # Converts the first letter in a string to uppercase | |
| 274 | uc <- function( s ) { | |
| 275 | concat( toupper( substr( s, 1, 1 ) ), substr( s, 2, nchar( s ) ) ) | |
| 276 | } | |
| 277 | ||
| 278 | # Returns the number of days between the given dates. | |
| 279 | days <- function( d1, d2, format = "%Y-%m-%d" ) { | |
| 280 | dates = c( d1, d2 ) | |
| 281 | dt = strptime( dates, format = format ) | |
| 282 | as.integer( difftime( dates[2], dates[1], units = "days" ) ) | |
| 283 | } | |
| 284 | ||
| 285 | # Returns the number of years elapsed. | |
| 286 | years <- function( began, ended ) { | |
| 287 | began = when( anchor, began ) | |
| 288 | ended = when( anchor, ended ) | |
| 289 | ||
| 290 | # Swap the dates if the end date comes before the start date. | |
| 291 | if( as.integer( ended - began ) < 0 ) { | |
| 292 | tempd = began | |
| 293 | began = ended | |
| 294 | ended = tempd | |
| 295 | } | |
| 296 | ||
| 297 | # Calculate number of elapsed years. | |
| 298 | length( seq( from = began, to = ended, by = 'year' ) ) - 1 | |
| 299 | } | |
| 300 | ||
| 301 | # Full name of the month, starting with a capital letter. | |
| 302 | month <- function( n ) { | |
| 303 | # Faster than month.name[ x( n ) ] | |
| 304 | .subset( month.name, x( n ) ) | |
| 305 | } | |
| 306 | ||
| 307 | money <- function( n ) { | |
| 308 | formatC( x( n ), format="d" ) | |
| 309 | } | |
| 1 | 310 |
| 1 | # ###################################################################### | |
| 2 | # | |
| 3 | # Copyright 2016, White Magic Software, Ltd. | |
| 4 | # | |
| 5 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 6 | # a copy of this software and associated documentation files (the | |
| 7 | # "Software"), to deal in the Software without restriction, including | |
| 8 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 9 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 10 | # permit persons to whom the Software is furnished to do so, subject to | |
| 11 | # the following conditions: | |
| 12 | # | |
| 13 | # The above copyright notice and this permission notice shall be | |
| 14 | # included in all copies or substantial portions of the Software. | |
| 15 | # | |
| 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 23 | # | |
| 24 | # ###################################################################### | |
| 25 | ||
| 26 | # ###################################################################### | |
| 27 | # | |
| 28 | # Converts CSV to Markdown. | |
| 29 | # | |
| 30 | # ###################################################################### | |
| 31 | ||
| 32 | # Reads a CSV file and converts the contents to a Markdown table. The | |
| 33 | # file must be in the working directory as specified by setwd. | |
| 34 | # | |
| 35 | # @param f The filename to convert. | |
| 36 | # @param decimals Rounded decimal places (default 1). | |
| 37 | # @param totals Include total sums (default TRUE). | |
| 38 | # @param align Right-align numbers (default TRUE). | |
| 39 | csv2md <- function( f, decimals = 1, totals = T, align = T ) { | |
| 40 | # Read the CVS data from the file; ensure strings become characters. | |
| 41 | df <- read.table( f, sep=',', header=T, stringsAsFactors=F ) | |
| 42 | ||
| 43 | if( totals ) { | |
| 44 | # Determine what columns can be summed. | |
| 45 | number <- which( unlist( lapply( df, is.numeric ) ) ) | |
| 46 | ||
| 47 | # Use colSums when more than one summable column exists. | |
| 48 | if( length( number ) > 1 ) { | |
| 49 | f.sum <- colSums | |
| 50 | } | |
| 51 | else { | |
| 52 | f.sum <- sum | |
| 53 | } | |
| 54 | ||
| 55 | # Calculate the sum of all the summable columns and insert the | |
| 56 | # results back into the data frame. | |
| 57 | df[ (nrow( df ) + 1), number ] <- f.sum( df[, number], na.rm=TRUE ) | |
| 58 | ||
| 59 | # pluralize would be heavyweight here. | |
| 60 | if( length( number ) > 1 ) { | |
| 61 | t <- "**Totals**" | |
| 62 | } | |
| 63 | else { | |
| 64 | t <- "**Total**" | |
| 65 | } | |
| 66 | ||
| 67 | # Change the first column of the last line to "Total(s)". | |
| 68 | df[ nrow( df ), 1 ] <- t | |
| 69 | ||
| 70 | # Don't clutter the output with "NA" text. | |
| 71 | df[ is.na( df ) ] <- "" | |
| 72 | } | |
| 73 | ||
| 74 | if( align ) { | |
| 75 | is.char <- vapply( df, is.character, logical( 1 ) ) | |
| 76 | dashes <- paste( ifelse( is.char, ':---', '---:' ), collapse='|' ) | |
| 77 | } | |
| 78 | else { | |
| 79 | dashes <- paste( rep( '---', length( df ) ), collapse = '|') | |
| 80 | } | |
| 81 | ||
| 82 | # Create a Markdown version of the data frame. | |
| 83 | paste( | |
| 84 | paste( names( df ), collapse = '|'), '\n', | |
| 85 | dashes, '\n', | |
| 86 | paste( | |
| 87 | Reduce( function( x, y ) { | |
| 88 | paste( x, format( y, digits = decimals ), sep = '|' ) | |
| 89 | }, df | |
| 90 | ), | |
| 91 | collapse = '|\n', sep='' | |
| 92 | ) | |
| 93 | ) | |
| 94 | } | |
| 95 | ||
| 1 | 96 |
| 1 | # ###################################################################### | |
| 2 | # | |
| 3 | # Copyright 2016, White Magic Software, Ltd. | |
| 4 | # | |
| 5 | # Permission is hereby granted, free of charge, to any person obtaining | |
| 6 | # a copy of this software and associated documentation files (the | |
| 7 | # "Software"), to deal in the Software without restriction, including | |
| 8 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 9 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 10 | # permit persons to whom the Software is furnished to do so, subject to | |
| 11 | # the following conditions: | |
| 12 | # | |
| 13 | # The above copyright notice and this permission notice shall be | |
| 14 | # included in all copies or substantial portions of the Software. | |
| 15 | # | |
| 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |
| 20 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |
| 21 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
| 22 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 23 | # | |
| 24 | # ###################################################################### | |
| 25 | ||
| 26 | # ###################################################################### | |
| 27 | # | |
| 28 | # See Damian Conway's "An Algorithmic Approach to English Pluralization": | |
| 29 | # http://goo.gl/oRL4MP | |
| 30 | # See Oliver Glerke's Evo Inflector: https://github.com/atteo/evo-inflector/ | |
| 31 | # See Shevek's Pluralizer: https://github.com/shevek/linguistics/ | |
| 32 | # See also: http://www.freevectors.net/assets/files/plural.txt | |
| 33 | # | |
| 34 | # ###################################################################### | |
| 35 | ||
| 36 | pluralize <- function( s, n ) { | |
| 37 | result <- s | |
| 38 | ||
| 39 | # Partial implementation of Conway's algorithm for nouns. | |
| 40 | if( n != 1 ) { | |
| 41 | if( pl.noninflective( s ) || | |
| 42 | pl.suffix( "fish", s ) || | |
| 43 | pl.suffix( "ois", s ) || | |
| 44 | pl.suffix( "sheep", s ) || | |
| 45 | pl.suffix( "deer", s ) || | |
| 46 | pl.suffix( "pox", s ) || | |
| 47 | pl.suffix( "[A-Z].*ese", s ) || | |
| 48 | pl.suffix( "itis", s ) ) { | |
| 49 | # 1. Retain non-inflective user-mapped noun as is. | |
| 50 | # 2. Retain non-inflective plural as is. | |
| 51 | result <- s | |
| 52 | } | |
| 53 | else if( pl.is.irregular.pl( s ) ) { | |
| 54 | # 4. Change irregular plurals based on mapping. | |
| 55 | result <- pl.irregular.pl( s ) | |
| 56 | } | |
| 57 | else if( pl.is.irregular.es( s ) ) { | |
| 58 | # x. From Shevek's Pluralizer | |
| 59 | result <- pl.inflect( s, "", "es" ) | |
| 60 | } | |
| 61 | else if( pl.suffix( "man", s ) ) { | |
| 62 | # 5. For -man, change -an to -en | |
| 63 | result <- pl.inflect( s, "an", "en" ) | |
| 64 | } | |
| 65 | else if( pl.suffix( "[lm]ouse", s ) ) { | |
| 66 | # 5. For [lm]ouse, change -ouse to -ice | |
| 67 | result <- pl.inflect( s, "ouse", "ice" ) | |
| 68 | } | |
| 69 | else if( pl.suffix( "tooth", s ) ) { | |
| 70 | # 5. For -tooth, change -ooth to -eeth | |
| 71 | result <- pl.inflect( s, "ooth", "eeth" ) | |
| 72 | } | |
| 73 | else if( pl.suffix( "goose", s ) ) { | |
| 74 | # 5. For -goose, change -oose to -eese | |
| 75 | result <- pl.inflect( s, "oose", "eese" ) | |
| 76 | } | |
| 77 | else if( pl.suffix( "foot", s ) ) { | |
| 78 | # 5. For -foot, change -oot to -eet | |
| 79 | result <- pl.inflect( s, "oot", "eet" ) | |
| 80 | } | |
| 81 | else if( pl.suffix( "zoon", s ) ) { | |
| 82 | # 5. For -zoon, change -on to -a | |
| 83 | result <- pl.inflect( s, "on", "a" ) | |
| 84 | } | |
| 85 | else if( pl.suffix( "[csx]is", s ) ) { | |
| 86 | # 5. Change -cis, -sis, -xis to -es | |
| 87 | result <- pl.inflect( s, "is", "es" ) | |
| 88 | } | |
| 89 | else if( pl.suffix( "([cs]h|ss)", s ) ) { | |
| 90 | # 8. Change -ch, -sh, -ss to -es | |
| 91 | result <- pl.inflect( s, "", "es" ) | |
| 92 | } | |
| 93 | else if( pl.suffix( "([aeo]lf|[^d]eaf|arf)", s ) ) { | |
| 94 | # 9. Change -f to -ves | |
| 95 | result <- pl.inflect( s, "f", "ves" ) | |
| 96 | } | |
| 97 | else if( pl.suffix( "[nlw]ife", s ) ) { | |
| 98 | # 9. Change -fe to -ves | |
| 99 | result <- pl.inflect( s, "fe", "ves" ) | |
| 100 | } | |
| 101 | else if( pl.suffix( "([aeiou]y|[A-Z].*y)", s ) ) { | |
| 102 | # 10. Change -y to -ys. | |
| 103 | result <- pl.inflect( s, "", "s" ) | |
| 104 | } | |
| 105 | else if( pl.suffix( "y", s ) ) { | |
| 106 | # 10. Change -y to -ies. | |
| 107 | result <- pl.inflect( s, "y", "ies" ) | |
| 108 | } | |
| 109 | else { | |
| 110 | # 13. Default plural: add -s. | |
| 111 | result <- pl.inflect( s, "", "s" ) | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | result | |
| 116 | } | |
| 117 | ||
| 118 | # Pluralize s if n is not equal to 1. | |
| 119 | pl <- function( s, n ) { | |
| 120 | pluralize( s, x( n ) ) | |
| 121 | } | |
| 122 | ||
| 123 | # Returns the given string (s) with its suffix replaced by r. | |
| 124 | pl.inflect <- function( s, suffix, r ) { | |
| 125 | gsub( paste( suffix, "$", sep="" ), r, s ) | |
| 126 | } | |
| 127 | ||
| 128 | # Answers whether the given string (s) has the given ending. | |
| 129 | pl.suffix <- function( ending, s ) { | |
| 130 | grepl( paste( ending, "$", sep="" ), s ) | |
| 131 | } | |
| 132 | ||
| 133 | # Answers whether the given string (s) is a noninflective noun. | |
| 134 | pl.noninflective <- function( s ) { | |
| 135 | v <- c( | |
| 136 | "aircraft", "Bhutanese", "bison", "bream", "breeches", "britches", | |
| 137 | "Burmese", "carp", "chassis", "Chinese", "clippers", "cod", "contretemps", | |
| 138 | "corps", "debris", "diabetes", "djinn", "eland", "elk", "flounder", | |
| 139 | "fracas", "gallows", "graffiti", "headquarters", "herpes", "high-jinks", | |
| 140 | "homework", "hovercraft", "innings", "jackanapes", "Japanese", | |
| 141 | "Lebanese", "mackerel", "means", "measles", "mews", "mumps", "news", | |
| 142 | "pincers", "pliers", "Portuguese", "proceedings", "rabies", "salmon", | |
| 143 | "scissors", "sea-bass", "Senegalese", "series", "shears", "Siamese", | |
| 144 | "Sinhalese", "spacecraft", "species", "swine", "trout", "tuna", | |
| 145 | "Vietnamese", "watercraft", "whiting", "wildebeest" | |
| 146 | ) | |
| 147 | ||
| 148 | is.element( s, v ) | |
| 149 | } | |
| 150 | ||
| 151 | # Answers whether the given string (s) is an irregular plural. | |
| 152 | pl.is.irregular.pl <- function( s ) { | |
| 153 | # Could be refactored with pl.irregular.pl... | |
| 154 | v <- c( | |
| 155 | "beef", "brother", "child", "cow", "ephemeris", "genie", "money", | |
| 156 | "mongoose", "mythos", "octopus", "ox", "soliloquy", "trilby" | |
| 157 | ) | |
| 158 | ||
| 159 | is.element( s, v ) | |
| 160 | } | |
| 161 | ||
| 162 | # Call to pluralize an irregular noun. Only call after confirming | |
| 163 | # the noun is irregular via pl.is.irregular.pl. | |
| 164 | pl.irregular.pl <- function( s ) { | |
| 165 | v <- list( | |
| 166 | "beef" = "beefs", | |
| 167 | "brother" = "brothers", | |
| 168 | "child" = "children", | |
| 169 | "cow" = "cows", | |
| 170 | "ephemeris" = "ephemerides", | |
| 171 | "genie" = "genies", | |
| 172 | "money" = "moneys", | |
| 173 | "mongoose" = "mongooses", | |
| 174 | "mythos" = "mythoi", | |
| 175 | "octopus" = "octopuses", | |
| 176 | "ox" = "oxen", | |
| 177 | "soliloquy" = "soliloquies", | |
| 178 | "trilby" = "trilbys" | |
| 179 | ) | |
| 180 | ||
| 181 | # Faster version of v[[ s ]] | |
| 182 | .subset2( v, s ) | |
| 183 | } | |
| 184 | ||
| 185 | # Answers whether the given string (s) pluralizes with -es. | |
| 186 | pl.is.irregular.es <- function( s ) { | |
| 187 | v <- c( | |
| 188 | "acropolis", "aegis", "alias", "asbestos", "bathos", "bias", "bronchitis", | |
| 189 | "bursitis", "caddis", "cannabis", "canvas", "chaos", "cosmos", "dais", | |
| 190 | "digitalis", "epidermis", "ethos", "eyas", "gas", "glottis", "hubris", | |
| 191 | "ibis", "lens", "mantis", "marquis", "metropolis", "pathos", "pelvis", | |
| 192 | "polis", "rhinoceros", "sassafrass", "trellis" | |
| 193 | ) | |
| 194 | ||
| 195 | is.element( s, v ) | |
| 196 | } | |
| 197 | ||
| 1 | 198 |
| 1 | ||
| 1 | com.keenwrite.service.impl.DefaultSettings |
| 1 | ||
| 1 | com.keenwrite.service.events.impl.DefaultNotifier |
| 1 | # Used by the Gradle build script and the application. | |
| 2 | application.title=KeenWrite | |
| 3 | ||
| 1 | 4 |
| 1 | app.properties | |
| 1 | 2 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 4 | xmlns:cc="http://creativecommons.org/ns#" | |
| 5 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 6 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 7 | xmlns="http://www.w3.org/2000/svg" | |
| 8 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 9 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 10 | version="1.1" | |
| 11 | width="971.53119" | |
| 12 | height="498.39355" | |
| 13 | viewBox="0 0 971.53119 498.39354" | |
| 14 | xml:space="preserve" | |
| 15 | id="svg52" | |
| 16 | sodipodi:docname="app-title.svg" | |
| 17 | inkscape:version="1.0 (4035a4fb49, 2020-05-01)" | |
| 18 | inkscape:export-filename="/home/jarvisd/dev/java/scrivenvar/docs/images/app-title.png" | |
| 19 | inkscape:export-xdpi="24.66" | |
| 20 | inkscape:export-ydpi="24.66"><metadata | |
| 21 | id="metadata56"><rdf:RDF><cc:Work | |
| 22 | rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type | |
| 23 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><sodipodi:namedview | |
| 24 | inkscape:document-rotation="0" | |
| 25 | pagecolor="#ffffff" | |
| 26 | bordercolor="#666666" | |
| 27 | borderopacity="1" | |
| 28 | objecttolerance="10" | |
| 29 | gridtolerance="10" | |
| 30 | guidetolerance="10" | |
| 31 | inkscape:pageopacity="0" | |
| 32 | inkscape:pageshadow="2" | |
| 33 | inkscape:window-width="640" | |
| 34 | inkscape:window-height="480" | |
| 35 | id="namedview54" | |
| 36 | showgrid="false" | |
| 37 | inkscape:zoom="0.78417969" | |
| 38 | inkscape:cx="455.5775" | |
| 39 | inkscape:cy="347.59625" | |
| 40 | inkscape:current-layer="svg52" | |
| 41 | fit-margin-top="0" | |
| 42 | fit-margin-left="0" | |
| 43 | fit-margin-right="0" | |
| 44 | fit-margin-bottom="0" /> | |
| 45 | <desc | |
| 46 | id="desc2">Created with Fabric.js 3.6.3</desc> | |
| 47 | <defs | |
| 48 | id="defs4"><rect | |
| 49 | x="114.92139" | |
| 50 | y="132.06313" | |
| 51 | width="470.12033" | |
| 52 | height="175.55823" | |
| 53 | id="rect933" /> | |
| 54 | ||
| 55 | ||
| 56 | ||
| 57 | ||
| 58 | ||
| 59 | ||
| 60 | ||
| 61 | ||
| 62 | ||
| 63 | ||
| 64 | ||
| 65 | ||
| 66 | <linearGradient | |
| 67 | y2="-0.049471263" | |
| 68 | x2="0.96880889" | |
| 69 | y1="-0.044911571" | |
| 70 | x1="0.15235768" | |
| 71 | gradientTransform="matrix(-121.64666,137.28602,-137.28602,-121.64666,522.68198,525.78258)" | |
| 72 | gradientUnits="userSpaceOnUse" | |
| 73 | id="SVGID_1_302284"> | |
| 74 | <stop | |
| 75 | id="stop9" | |
| 76 | style="stop-color:#ec706a;stop-opacity:1" | |
| 77 | offset="0%" /> | |
| 78 | <stop | |
| 79 | id="stop11" | |
| 80 | style="stop-color:#ecd980;stop-opacity:1" | |
| 81 | offset="100%" /> | |
| 82 | </linearGradient> | |
| 83 | ||
| 84 | ||
| 85 | ||
| 86 | ||
| 87 | ||
| 88 | ||
| 89 | ||
| 90 | ||
| 91 | ||
| 92 | ||
| 93 | ||
| 94 | ||
| 95 | ||
| 96 | ||
| 97 | ||
| 98 | </defs> | |
| 99 | ||
| 100 | <g | |
| 101 | id="g853" | |
| 102 | transform="translate(-394.35834,-171.20491)"><path | |
| 103 | style="fill:url(#SVGID_1_302284);fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 104 | paint-order="stroke" | |
| 105 | d="m 425.11895,550.88213 c -46.93797,72.14807 -26.19433,103.38343 -26.19433,103.38343 v 0 c 0,0 31.07048,-45.59403 48.81648,-27.97293 v 0 c 15.24298,15.10308 -12.06548,43.30583 -12.06548,43.30583 v 0 c 0,0 166.06898,-68.436 89.90407,-144.24619 v 0 c 0,0 -16.00237,-18.40049 -39.62873,-18.40548 v 0 c -17.28637,0 -38.64951,9.84223 -60.83201,43.93534" | |
| 106 | stroke-linecap="round" | |
| 107 | id="path14" /><path | |
| 108 | style="fill:#126d95;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 109 | paint-order="stroke" | |
| 110 | d="m 575.11882,568.48329 -4.34657,-84.38342 19.95925,-19.85434 30.59087,30.75573 z" | |
| 111 | stroke-linecap="round" | |
| 112 | id="path22" /><path | |
| 113 | style="fill:#126d95;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 114 | paint-order="stroke" | |
| 115 | d="m 638.20224,478.0873 -10.3968,10.33684 -30.52591,-30.69078 10.39679,-10.33685 z" | |
| 116 | stroke-linecap="round" | |
| 117 | id="path26" /><path | |
| 118 | style="fill:#51a9cf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 119 | paint-order="stroke" | |
| 120 | d="m 791.45508,258.2912 c -6.12517,-3.44728 -14.03892,-2.61294 -19.29478,2.61793 -6.36997,6.33501 -6.39495,16.63688 -0.0649,23.00186 L 613.81523,441.29182 583.28931,410.60103 c 96.04423,-96.4489 126.74501,-177.76974 126.74501,-177.76974 79.22249,-11.81068 139.14522,-43.08601 168.97169,-61.62638 z" | |
| 121 | stroke-linecap="round" | |
| 122 | id="path30" /><path | |
| 123 | style="fill:#51a9cf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 124 | paint-order="stroke" | |
| 125 | d="m 607.67733,447.39871 -10.3968,10.33684 -30.64582,-30.87064 10.36183,-10.31186 z" | |
| 126 | stroke-linecap="round" | |
| 127 | id="path34" /><path | |
| 128 | style="fill:#51a9cf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 129 | paint-order="stroke" | |
| 130 | d="m 590.73628,464.25235 -19.95925,19.85434 -84.29849,-4.79622 73.70185,-45.84383 z" | |
| 131 | stroke-linecap="round" | |
| 132 | id="path38" /><path | |
| 133 | style="fill:#126d95;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 134 | paint-order="stroke" | |
| 135 | d="m 798.0649,265.0575 87.61088,-87.14624 c -18.72523,29.76151 -50.29032,89.4844 -62.52567,168.64194 0,0 -77.5688,34.88248 -178.68403,125.55095 L 613.81527,441.28846 772.09539,283.91262 c 6.35998,6.39496 16.63687,6.38996 23.00185,0.06 5.14095,-5.10597 6.11018,-12.8049 2.96766,-18.91508" | |
| 136 | stroke-linecap="round" | |
| 137 | id="path42" /></g> | |
| 138 | ||
| 139 | <text | |
| 140 | xml:space="preserve" | |
| 141 | id="text931" | |
| 142 | style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect933);fill:#000000;fill-opacity:1;stroke:none;" | |
| 143 | transform="translate(-394.35834,-171.20491)" /><text | |
| 144 | xml:space="preserve" | |
| 145 | style="font-style:italic;font-variant:normal;font-weight:800;font-stretch:normal;font-size:133.333px;line-height:1.25;font-family:'Merriweather Sans';-inkscape-font-specification:'Merriweather Sans, Ultra-Bold Italic';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#51a9cf;fill-opacity:1;stroke:none" | |
| 146 | x="311.66693" | |
| 147 | y="402.20627" | |
| 148 | id="text939"><tspan | |
| 149 | sodipodi:role="line" | |
| 150 | id="tspan937" | |
| 151 | x="311.66693" | |
| 152 | y="402.20627">KeenWrite</tspan></text></svg> | |
| 1 | 153 |
| 1 | #!/bin/bash | |
| 2 | ||
| 3 | INKSCAPE="/usr/bin/inkscape" | |
| 4 | PNG_COMPRESS="optipng" | |
| 5 | PNG_COMPRESS_OPTS="-o9 *png" | |
| 6 | ICO_TOOL="icotool" | |
| 7 | ICO_TOOL_OPTS="-c -o ../../../../../icons/logo.ico logo64.png" | |
| 8 | ||
| 9 | declare -a SIZES=("16" "32" "64" "128" "256" "512") | |
| 10 | ||
| 11 | for i in "${SIZES[@]}"; do | |
| 12 | # -y: export background opacity 0 | |
| 13 | $INKSCAPE -y 0 -w "${i}" --export-overwrite --export-type=png -o "logo${i}.png" "logo.svg" | |
| 14 | done | |
| 15 | ||
| 16 | # Compess the PNG images. | |
| 17 | which $PNG_COMPRESS && $PNG_COMPRESS $PNG_COMPRESS_OPTS | |
| 18 | ||
| 19 | # Generate an ICO file. | |
| 20 | which $ICO_TOOL && $ICO_TOOL $ICO_TOOL_OPTS | |
| 21 | ||
| 1 | 22 |
| 1 | /* Copyright 2020 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | ||
| 3 | .markdown { | |
| 4 | -fx-font-family: 'Noto Sans'; | |
| 5 | -fx-font-size: 11pt; | |
| 6 | -fx-padding: .25em; | |
| 7 | } | |
| 8 | ||
| 9 | /* Editor background color */ | |
| 10 | .styled-text-area { | |
| 11 | -fx-background-color: -fx-control-inner-background; | |
| 12 | } | |
| 13 | ||
| 14 | /* Text foreground colour */ | |
| 15 | .styled-text-area .text { | |
| 16 | -fx-fill: -fx-text-foreground; | |
| 17 | } | |
| 18 | ||
| 19 | /* Subtly highlight the current paragraph. */ | |
| 20 | .markdown .paragraph-box:has-caret { | |
| 21 | -fx-background-color: -fx-text-background; | |
| 22 | } | |
| 23 | ||
| 24 | /* Light colour for selection highlight. */ | |
| 25 | .markdown .selection { | |
| 26 | -fx-fill: -fx-text-selection; | |
| 27 | } | |
| 28 | ||
| 29 | /* Decoration for words not found in the lexicon. */ | |
| 30 | .markdown .spelling { | |
| 31 | -rtfx-underline-color: rgba( 255, 131, 67, .7 ); | |
| 32 | -rtfx-underline-dash-array: 4, 2; | |
| 33 | -rtfx-underline-width: 2; | |
| 34 | -rtfx-underline-cap: round; | |
| 35 | } | |
| 36 | ||
| 37 | .markdown .search { | |
| 38 | -rtfx-background-color: #ffe959; | |
| 39 | } | |
| 1 | 40 |
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
| 1 | .markdown { | |
| 2 | -fx-font-family: 'Noto Sans CJK JP'; | |
| 3 | } | |
| 1 | 4 |
| 1 | .markdown { | |
| 2 | -fx-font-family: 'Noto Sans CJK KR'; | |
| 3 | } | |
| 1 | 4 |
| 1 | .markdown { | |
| 2 | -fx-font-family: 'Noto Sans CJK SC'; | |
| 3 | } | |
| 1 | 4 |
| 1 | .markdown { | |
| 2 | -fx-font-family: 'Noto Sans CJK SC'; | |
| 3 | } | |
| 1 | 4 |
| 1 | .markdown { | |
| 2 | -fx-font-family: 'Noto Sans CJK HK'; | |
| 3 | } | |
| 1 | 4 |
| 1 | .markdown { | |
| 2 | -fx-font-family: 'Noto Sans CJK TC'; | |
| 3 | } | |
| 1 | 4 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | xmlns:dc="http://purl.org/dc/elements/1.1/" | |
| 4 | xmlns:cc="http://creativecommons.org/ns#" | |
| 5 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
| 6 | xmlns:svg="http://www.w3.org/2000/svg" | |
| 7 | xmlns="http://www.w3.org/2000/svg" | |
| 8 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |
| 9 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |
| 10 | inkscape:version="1.0 (4035a4fb49, 2020-05-01)" | |
| 11 | sodipodi:docname="icon.svg" | |
| 12 | id="svg52" | |
| 13 | xml:space="preserve" | |
| 14 | viewBox="0 0 512 512" | |
| 15 | height="512" | |
| 16 | width="512" | |
| 17 | version="1.1"><metadata | |
| 18 | id="metadata56"><rdf:RDF><cc:Work | |
| 19 | rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type | |
| 20 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><sodipodi:namedview | |
| 21 | inkscape:current-layer="svg52" | |
| 22 | inkscape:cy="369.17559" | |
| 23 | inkscape:cx="343.24925" | |
| 24 | inkscape:zoom="0.78417969" | |
| 25 | showgrid="false" | |
| 26 | id="namedview54" | |
| 27 | inkscape:window-height="480" | |
| 28 | inkscape:window-width="640" | |
| 29 | inkscape:pageshadow="2" | |
| 30 | inkscape:pageopacity="0" | |
| 31 | guidetolerance="10" | |
| 32 | gridtolerance="10" | |
| 33 | objecttolerance="10" | |
| 34 | borderopacity="1" | |
| 35 | bordercolor="#666666" | |
| 36 | pagecolor="#ffffff" | |
| 37 | inkscape:document-rotation="0" /> | |
| 38 | <desc | |
| 39 | id="desc2">Created with Fabric.js 3.6.3</desc> | |
| 40 | <defs | |
| 41 | id="defs4"><rect | |
| 42 | id="rect933" | |
| 43 | height="175.55823" | |
| 44 | width="470.12033" | |
| 45 | y="132.06313" | |
| 46 | x="114.92139" /> | |
| 47 | ||
| 48 | ||
| 49 | ||
| 50 | ||
| 51 | ||
| 52 | ||
| 53 | ||
| 54 | ||
| 55 | ||
| 56 | ||
| 57 | ||
| 58 | ||
| 59 | <linearGradient | |
| 60 | id="SVGID_1_302284" | |
| 61 | gradientUnits="userSpaceOnUse" | |
| 62 | gradientTransform="matrix(-121.64666,137.28602,-137.28602,-121.64666,522.68198,525.78258)" | |
| 63 | x1="0.15235768" | |
| 64 | y1="-0.044911571" | |
| 65 | x2="0.96880889" | |
| 66 | y2="-0.049471263"> | |
| 67 | <stop | |
| 68 | offset="0%" | |
| 69 | style="stop-color:#ec706a;stop-opacity:1" | |
| 70 | id="stop9" /> | |
| 71 | <stop | |
| 72 | offset="100%" | |
| 73 | style="stop-color:#ecd980;stop-opacity:1" | |
| 74 | id="stop11" /> | |
| 75 | </linearGradient> | |
| 76 | ||
| 77 | ||
| 78 | ||
| 79 | ||
| 80 | ||
| 81 | ||
| 82 | ||
| 83 | ||
| 84 | ||
| 85 | ||
| 86 | ||
| 87 | ||
| 88 | ||
| 89 | ||
| 90 | ||
| 91 | </defs> | |
| 92 | ||
| 93 | <g | |
| 94 | transform="translate(-384.01706,-164.40168)" | |
| 95 | id="g853"><path | |
| 96 | id="path14" | |
| 97 | stroke-linecap="round" | |
| 98 | d="m 425.11895,550.88213 c -46.93797,72.14807 -26.19433,103.38343 -26.19433,103.38343 v 0 c 0,0 31.07048,-45.59403 48.81648,-27.97293 v 0 c 15.24298,15.10308 -12.06548,43.30583 -12.06548,43.30583 v 0 c 0,0 166.06898,-68.436 89.90407,-144.24619 v 0 c 0,0 -16.00237,-18.40049 -39.62873,-18.40548 v 0 c -17.28637,0 -38.64951,9.84223 -60.83201,43.93534" | |
| 99 | paint-order="stroke" | |
| 100 | style="fill:url(#SVGID_1_302284);fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" /><path | |
| 101 | id="path22" | |
| 102 | stroke-linecap="round" | |
| 103 | d="m 575.11882,568.48329 -4.34657,-84.38342 19.95925,-19.85434 30.59087,30.75573 z" | |
| 104 | paint-order="stroke" | |
| 105 | style="fill:#126d95;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" /><path | |
| 106 | id="path26" | |
| 107 | stroke-linecap="round" | |
| 108 | d="m 638.20224,478.0873 -10.3968,10.33684 -30.52591,-30.69078 10.39679,-10.33685 z" | |
| 109 | paint-order="stroke" | |
| 110 | style="fill:#126d95;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" /><path | |
| 111 | id="path30" | |
| 112 | stroke-linecap="round" | |
| 113 | d="m 791.45508,258.2912 c -6.12517,-3.44728 -14.03892,-2.61294 -19.29478,2.61793 -6.36997,6.33501 -6.39495,16.63688 -0.0649,23.00186 L 613.81523,441.29182 583.28931,410.60103 c 96.04423,-96.4489 126.74501,-177.76974 126.74501,-177.76974 79.22249,-11.81068 139.14522,-43.08601 168.97169,-61.62638 z" | |
| 114 | paint-order="stroke" | |
| 115 | style="fill:#51a9cf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" /><path | |
| 116 | id="path34" | |
| 117 | stroke-linecap="round" | |
| 118 | d="m 607.67733,447.39871 -10.3968,10.33684 -30.64582,-30.87064 10.36183,-10.31186 z" | |
| 119 | paint-order="stroke" | |
| 120 | style="fill:#51a9cf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" /><path | |
| 121 | id="path38" | |
| 122 | stroke-linecap="round" | |
| 123 | d="m 590.73628,464.25235 -19.95925,19.85434 -84.29849,-4.79622 73.70185,-45.84383 z" | |
| 124 | paint-order="stroke" | |
| 125 | style="fill:#51a9cf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" /><path | |
| 126 | id="path42" | |
| 127 | stroke-linecap="round" | |
| 128 | d="m 798.0649,265.0575 87.61088,-87.14624 c -18.72523,29.76151 -50.29032,89.4844 -62.52567,168.64194 0,0 -77.5688,34.88248 -178.68403,125.55095 L 613.81527,441.28846 772.09539,283.91262 c 6.35998,6.39496 16.63687,6.38996 23.00185,0.06 5.14095,-5.10597 6.11018,-12.8049 2.96766,-18.91508" | |
| 129 | paint-order="stroke" | |
| 130 | style="fill:#126d95;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.99606;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" /></g> | |
| 131 | ||
| 132 | <text | |
| 133 | style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect933);fill:#000000;fill-opacity:1;stroke:none;" | |
| 134 | id="text931" | |
| 135 | xml:space="preserve" /></svg> | |
| 1 | 136 |
| 1 | # ######################################################################## | |
| 2 | # Main Application Window | |
| 3 | # ######################################################################## | |
| 4 | ||
| 5 | # suppress inspection "UnusedProperty" for whole file | |
| 6 | ||
| 7 | # ######################################################################## | |
| 8 | # Workspace preferences | |
| 9 | # ######################################################################## | |
| 10 | ||
| 11 | workspace.document=Document | |
| 12 | workspace.document.title=Title Name | |
| 13 | workspace.document.title.desc=Full document title, or variable reference (e.g., '{{'book.title'}}'). | |
| 14 | workspace.document.title.title=Title | |
| 15 | workspace.document.author=Author Name | |
| 16 | workspace.document.author.desc=Full name of primary author, or variable reference (e.g., '{{'book.author'}}'). | |
| 17 | workspace.document.author.title=Name | |
| 18 | workspace.document.byline=Byline | |
| 19 | workspace.document.byline.desc=Author name, pen name, byline, pseudonym, or variable reference. | |
| 20 | workspace.document.byline.title=Name | |
| 21 | workspace.document.address=Address | |
| 22 | workspace.document.address.desc=Author mailing address, or variable reference. | |
| 23 | workspace.document.address.title=Address | |
| 24 | workspace.document.phone=Phone | |
| 25 | workspace.document.phone.desc=Author phone number, or variable reference. | |
| 26 | workspace.document.phone.title=Number | |
| 27 | workspace.document.email=Email | |
| 28 | workspace.document.email.desc=Author email address, or variable reference. | |
| 29 | workspace.document.email.title=Email | |
| 30 | workspace.document.keywords=Keywords | |
| 31 | workspace.document.keywords.desc=Comma-separated words relating to subject matter, or variable reference. | |
| 32 | workspace.document.keywords.title=Words | |
| 33 | workspace.document.copyright=Copyright | |
| 34 | workspace.document.copyright.desc=Continuous years of publication, or variable reference. | |
| 35 | workspace.document.copyright.title=Year(s) | |
| 36 | workspace.document.date=Publish Date | |
| 37 | workspace.document.date.desc=Date and time document was published, or variable reference. | |
| 38 | workspace.document.date.title=Timestamp | |
| 39 | ||
| 40 | workspace.typeset=Typesetting | |
| 41 | workspace.typeset.context=ConTeXt | |
| 42 | workspace.typeset.context.themes.path=Paths | |
| 43 | workspace.typeset.context.themes.path.desc=Directory containing theme subdirectories. | |
| 44 | workspace.typeset.context.themes.path.title=Themes | |
| 45 | workspace.typeset.context.clean=Clean | |
| 46 | workspace.typeset.context.clean.desc=Delete ancillary files after an unsuccessful export. | |
| 47 | workspace.typeset.context.clean.title=Purge | |
| 48 | workspace.typeset.typography=Typography | |
| 49 | workspace.typeset.typography.quotes=Quotation Marks | |
| 50 | workspace.typeset.typography.quotes.desc=Convert straight quotes into curly quotes and primes. | |
| 51 | workspace.typeset.typography.quotes.title=Curl | |
| 52 | ||
| 53 | workspace.r=R | |
| 54 | workspace.r.script=Startup Script | |
| 55 | workspace.r.script.desc=Script runs prior to executing R statements within the document. | |
| 56 | workspace.r.dir=Working Directory | |
| 57 | workspace.r.dir.desc=Value assigned to {0}application.r.working.directory{1} and usable in the startup script. | |
| 58 | workspace.r.dir.title=Directory | |
| 59 | workspace.r.delimiter.began=Delimiter Prefix | |
| 60 | workspace.r.delimiter.began.desc=Prefix of expression that wraps inserted variables. | |
| 61 | workspace.r.delimiter.began.title=Opening | |
| 62 | workspace.r.delimiter.ended=Delimiter Suffix | |
| 63 | workspace.r.delimiter.ended.desc=Suffix of expression that wraps inserted variables. | |
| 64 | workspace.r.delimiter.ended.title=Closing | |
| 65 | ||
| 66 | workspace.images=Images | |
| 67 | workspace.images.dir=Absolute Directory | |
| 68 | workspace.images.dir.desc=Path to search for local file system images. | |
| 69 | workspace.images.dir.title=Directory | |
| 70 | workspace.images.order=Extensions | |
| 71 | workspace.images.order.desc=Preferred order of image file types to embed, separated by spaces. | |
| 72 | workspace.images.order.title=Extensions | |
| 73 | workspace.images.resize=Resize | |
| 74 | workspace.images.resize.desc=Scale images to fit the preview panel when resizing, automatically. | |
| 75 | workspace.images.resize.title=Resize | |
| 76 | workspace.images.server=Diagram Server | |
| 77 | workspace.images.server.desc=Server used to generate diagrams (e.g., kroki.io). | |
| 78 | workspace.images.server.title=Name | |
| 79 | ||
| 80 | workspace.definition=Variable | |
| 81 | workspace.definition.path=File name | |
| 82 | workspace.definition.path.desc=Absolute path to interpolated string variables. | |
| 83 | workspace.definition.path.title=Path | |
| 84 | workspace.definition.delimiter.began=Delimiter Prefix | |
| 85 | workspace.definition.delimiter.began.desc=Indicates when a variable name is starting. | |
| 86 | workspace.definition.delimiter.began.title=Opening | |
| 87 | workspace.definition.delimiter.ended=Delimiter Suffix | |
| 88 | workspace.definition.delimiter.ended.desc=Indicates when a variable name is ending. | |
| 89 | workspace.definition.delimiter.ended.title=Closing | |
| 90 | ||
| 91 | workspace.ui.skin=Skins | |
| 92 | workspace.ui.skin.selection=Bundled | |
| 93 | workspace.ui.skin.selection.desc=Pre-packaged application style (default: Modena Light). | |
| 94 | workspace.ui.skin.selection.title=Name | |
| 95 | workspace.ui.skin.custom=Custom | |
| 96 | workspace.ui.skin.custom.desc=User-defined JavaFX cascading stylesheet file. | |
| 97 | workspace.ui.skin.custom.title=Path | |
| 98 | ||
| 99 | workspace.ui.font=Fonts | |
| 100 | workspace.ui.font.editor=Editor Font | |
| 101 | workspace.ui.font.editor.name=Name | |
| 102 | workspace.ui.font.editor.name.desc=Text editor font name (sans-serif font recommended). | |
| 103 | workspace.ui.font.editor.name.title=Family | |
| 104 | workspace.ui.font.editor.size=Size | |
| 105 | workspace.ui.font.editor.size.desc=Font size. | |
| 106 | workspace.ui.font.editor.size.title=Points | |
| 107 | workspace.ui.font.preview=Preview Font | |
| 108 | workspace.ui.font.preview.name=Name | |
| 109 | workspace.ui.font.preview.name.desc=Preview pane font name (must support ligatures, serif font recommended). | |
| 110 | workspace.ui.font.preview.name.title=Family | |
| 111 | workspace.ui.font.preview.size=Size | |
| 112 | workspace.ui.font.preview.size.desc=Font size. | |
| 113 | workspace.ui.font.preview.size.title=Points | |
| 114 | workspace.ui.font.preview.mono.name=Name | |
| 115 | workspace.ui.font.preview.mono.name.desc=Monospace font name. | |
| 116 | workspace.ui.font.preview.mono.name.title=Family | |
| 117 | workspace.ui.font.preview.mono.size=Size | |
| 118 | workspace.ui.font.preview.mono.size.desc=Monospace font size. | |
| 119 | workspace.ui.font.preview.mono.size.title=Points | |
| 120 | ||
| 121 | workspace.language=Language | |
| 122 | workspace.language.locale=Internationalization | |
| 123 | workspace.language.locale.desc=Language for application and HTML export. | |
| 124 | workspace.language.locale.title=Locale | |
| 125 | ||
| 126 | # ######################################################################## | |
| 127 | # Menu Bar | |
| 128 | # ######################################################################## | |
| 129 | ||
| 130 | Main.menu.file=_File | |
| 131 | Main.menu.edit=_Edit | |
| 132 | Main.menu.insert=_Insert | |
| 133 | Main.menu.format=Forma_t | |
| 134 | Main.menu.definition=_Variable | |
| 135 | Main.menu.view=Vie_w | |
| 136 | Main.menu.help=_Help | |
| 137 | ||
| 138 | # ######################################################################## | |
| 139 | # Detachable Tabs | |
| 140 | # ######################################################################## | |
| 141 | ||
| 142 | # {0} is the application title; {1} is a unique window ID. | |
| 143 | Detach.tab.title={0} - {1} | |
| 144 | ||
| 145 | # ######################################################################## | |
| 146 | # Status Bar | |
| 147 | # ######################################################################## | |
| 148 | ||
| 149 | Main.status.text.offset=offset | |
| 150 | Main.status.line=Line {0} of {1}, ${Main.status.text.offset} {2} | |
| 151 | Main.status.state.default=OK | |
| 152 | Main.status.export.success=Saved as ''{0}'' | |
| 153 | ||
| 154 | Main.status.error.bootstrap.eval=Note: Bootstrap variable of ''{0}'' not found | |
| 155 | ||
| 156 | Main.status.error.parse={0} (near ${Main.status.text.offset} {1}) | |
| 157 | Main.status.error.def.blank=Move the caret to a word before inserting a variable | |
| 158 | Main.status.error.def.empty=Create a variable before inserting one | |
| 159 | Main.status.error.def.missing=No variable value found for ''{0}'' | |
| 160 | Main.status.error.r=Error with [{0}...]: {1} | |
| 161 | Main.status.error.file.missing=Not found: ''{0}'' | |
| 162 | Main.status.error.file.missing.near=Not found: ''{0}'' near line {1} | |
| 163 | ||
| 164 | Main.status.error.messages.recursion=Lookup depth exceeded, check for loops in ''{0}'' | |
| 165 | Main.status.error.messages.syntax=Missing ''}'' in ''{0}'' | |
| 166 | ||
| 167 | Main.status.error.undo=Cannot undo; beginning of undo history reached | |
| 168 | Main.status.error.redo=Cannot redo; end of redo history reached | |
| 169 | ||
| 170 | Main.status.error.theme.missing=Install themes before exporting (no themes found at ''{0}'') | |
| 171 | Main.status.error.theme.name=Cannot find theme name for ''{0}'' | |
| 172 | ||
| 173 | Main.status.image.request.init=Initializing HTTP request | |
| 174 | Main.status.image.request.fetch=Requesting content type from ''{0}'' | |
| 175 | Main.status.image.request.success=Determined content type ''{0}'' | |
| 176 | Main.status.image.request.error.media=No media type for ''{0}'' | |
| 177 | Main.status.image.request.error.cert=Could not accept certificate for ''{0}'' | |
| 178 | ||
| 179 | Main.status.font.search.missing=No font name starting with ''{0}'' was found | |
| 180 | ||
| 181 | Main.status.export.concat=Concatenating ''{0}'' | |
| 182 | Main.status.export.concat.parent=No parent directory found for ''{0}'' | |
| 183 | Main.status.export.concat.extension=File name must have an extension ''{0}'' | |
| 184 | Main.status.export.concat.io=Could not read from ''{0}'' | |
| 185 | ||
| 186 | Main.status.typeset.create=Creating typesetter | |
| 187 | Main.status.typeset.xhtml=Export document as XHTML | |
| 188 | Main.status.typeset.began=Started typesetting ''{0}'' | |
| 189 | Main.status.typeset.failed=Could not generate PDF file | |
| 190 | Main.status.typeset.page=Typesetting page {0} of {1} (pass {2}) | |
| 191 | Main.status.typeset.ended.success=Finished typesetting ''{0}'' ({1} elapsed) | |
| 192 | Main.status.typeset.ended.failure=Failed to typeset ''{0}'' ({1} elapsed) | |
| 193 | ||
| 194 | # ######################################################################## | |
| 195 | # Search Bar | |
| 196 | # ######################################################################## | |
| 197 | ||
| 198 | Main.search.stop.tooltip=Close search bar | |
| 199 | Main.search.stop.icon=CLOSE | |
| 200 | Main.search.next.tooltip=Find next match | |
| 201 | Main.search.next.icon=CHEVRON_DOWN | |
| 202 | Main.search.prev.tooltip=Find previous match | |
| 203 | Main.search.prev.icon=CHEVRON_UP | |
| 204 | Main.search.find.tooltip=Search document for text | |
| 205 | Main.search.find.icon=SEARCH | |
| 206 | Main.search.match.none=No matches | |
| 207 | Main.search.match.some={0} of {1} matches | |
| 208 | ||
| 209 | # ######################################################################## | |
| 210 | # Definition Pane and its Tree View | |
| 211 | # ######################################################################## | |
| 212 | ||
| 213 | Definition.menu.add.default=Undefined | |
| 214 | ||
| 215 | # ######################################################################## | |
| 216 | # Variable Definitions Pane | |
| 217 | # ######################################################################## | |
| 218 | ||
| 219 | Pane.definition.node.root.title=Variables | |
| 220 | ||
| 221 | # ######################################################################## | |
| 222 | # HTML Preview Pane | |
| 223 | # ######################################################################## | |
| 224 | ||
| 225 | Pane.preview.title=Preview | |
| 226 | ||
| 227 | # ######################################################################## | |
| 228 | # Document Outline Pane | |
| 229 | # ######################################################################## | |
| 230 | ||
| 231 | Pane.outline.title=Outline | |
| 232 | ||
| 233 | # ######################################################################## | |
| 234 | # File Manager Pane | |
| 235 | # ######################################################################## | |
| 236 | ||
| 237 | Pane.files.title=Files | |
| 238 | ||
| 239 | # ######################################################################## | |
| 240 | # Document Outline Pane | |
| 241 | # ######################################################################## | |
| 242 | ||
| 243 | Pane.statistics.title=Statistics | |
| 244 | ||
| 245 | # ######################################################################## | |
| 246 | # Failure messages with respect to YAML files. | |
| 247 | # ######################################################################## | |
| 248 | ||
| 249 | yaml.error.open=Could not open YAML file (ensure non-empty file). | |
| 250 | yaml.error.unresolvable=Too much indirection for: ''{0}'' = ''{1}''. | |
| 251 | yaml.error.missing=Empty variable value for key ''{0}''. | |
| 252 | yaml.error.tree.form=Unassigned variable near ''{0}''. | |
| 253 | ||
| 254 | # ######################################################################## | |
| 255 | # Text Resource | |
| 256 | # ######################################################################## | |
| 257 | ||
| 258 | TextResource.load.error.unsaved=The file ''{0}'' is unsaved or does not exist. | |
| 259 | TextResource.load.error.permissions=The file ''{0}'' must be readable and writable. | |
| 260 | ||
| 261 | # ######################################################################## | |
| 262 | # Text Resources | |
| 263 | # ######################################################################## | |
| 264 | ||
| 265 | TextResource.saveFailed.message=Failed to save ''{0}''.\n\nReason: {1} | |
| 266 | TextResource.saveFailed.title=Save | |
| 267 | ||
| 268 | # ######################################################################## | |
| 269 | # File Open | |
| 270 | # ######################################################################## | |
| 271 | ||
| 272 | Dialog.file.choose.open.title=Open File | |
| 273 | Dialog.file.choose.save.title=Save File | |
| 274 | Dialog.file.choose.export.title=Export File | |
| 275 | ||
| 276 | Dialog.file.choose.filter.title.source=Source Files | |
| 277 | Dialog.file.choose.filter.title.definition=Variable Files | |
| 278 | Dialog.file.choose.filter.title.xml=XML Files | |
| 279 | Dialog.file.choose.filter.title.all=All Files | |
| 280 | ||
| 281 | # ######################################################################## | |
| 282 | # Browse File | |
| 283 | # ######################################################################## | |
| 284 | ||
| 285 | BrowseFileButton.chooser.title=Open local file | |
| 286 | BrowseFileButton.chooser.allFilesFilter=All Files | |
| 287 | BrowseFileButton.tooltip=${BrowseFileButton.chooser.title} | |
| 288 | ||
| 289 | # ######################################################################## | |
| 290 | # Browse Directory | |
| 291 | # ######################################################################## | |
| 292 | ||
| 293 | BrowseDirectoryButton.chooser.title=Open local directory | |
| 294 | BrowseDirectoryButton.tooltip=${BrowseDirectoryButton.chooser.title} | |
| 295 | ||
| 296 | # ######################################################################## | |
| 297 | # Alert Dialog | |
| 298 | # ######################################################################## | |
| 299 | ||
| 300 | Alert.file.close.title=Close | |
| 301 | Alert.file.close.text=Save changes to {0}? | |
| 302 | ||
| 303 | # ######################################################################## | |
| 304 | # Typesetting Alert Dialog | |
| 305 | # ######################################################################## | |
| 306 | ||
| 307 | Alert.typesetter.missing.title=Missing Typesetter | |
| 308 | Alert.typesetter.missing.header=Install typesetter | |
| 309 | Alert.typesetter.missing.version=for {0} {1} {2}-bit | |
| 310 | Alert.typesetter.missing.installer.text=Download and install ConTeXt | |
| 311 | Alert.typesetter.missing.installer.url=https://wiki.contextgarden.net/Installation | |
| 312 | ||
| 313 | # ######################################################################## | |
| 314 | # Image Dialog | |
| 315 | # ######################################################################## | |
| 316 | ||
| 317 | Dialog.image.title=Image | |
| 318 | Dialog.image.chooser.imagesFilter=Images | |
| 319 | Dialog.image.previewLabel.text=Markdown Preview\: | |
| 320 | Dialog.image.textLabel.text=Alternate Text\: | |
| 321 | Dialog.image.titleLabel.text=Title (tooltip)\: | |
| 322 | Dialog.image.urlLabel.text=Image URL\: | |
| 323 | ||
| 324 | # ######################################################################## | |
| 325 | # Hyperlink Dialog | |
| 326 | # ######################################################################## | |
| 327 | ||
| 328 | Dialog.link.title=Link | |
| 329 | Dialog.link.previewLabel.text=Markdown Preview\: | |
| 330 | Dialog.link.textLabel.text=Link Text\: | |
| 331 | Dialog.link.titleLabel.text=Title (tooltip)\: | |
| 332 | Dialog.link.urlLabel.text=Link URL\: | |
| 333 | ||
| 334 | # ######################################################################## | |
| 335 | # Themes Dialog | |
| 336 | # ######################################################################## | |
| 337 | ||
| 338 | Dialog.theme.title=Typesetting theme | |
| 339 | Dialog.theme.header=Choose a typesetting theme | |
| 340 | ||
| 341 | # ######################################################################## | |
| 342 | # About Dialog | |
| 343 | # ######################################################################## | |
| 344 | ||
| 345 | Dialog.about.title=About {0} | |
| 346 | Dialog.about.header={0} | |
| 347 | Dialog.about.content=Copyright 2016-{0} White Magic Software, Ltd.\n\nVersion: {1} | |
| 348 | ||
| 349 | # ######################################################################## | |
| 350 | # Application Actions | |
| 351 | # ######################################################################## | |
| 352 | ||
| 353 | Action.file.new.description=Create a new file | |
| 354 | Action.file.new.accelerator=Shortcut+N | |
| 355 | Action.file.new.icon=FILE_ALT | |
| 356 | Action.file.new.text=_New | |
| 357 | ||
| 358 | Action.file.open.description=Open a new file | |
| 359 | Action.file.open.accelerator=Shortcut+O | |
| 360 | Action.file.open.text=_Open... | |
| 361 | Action.file.open.icon=FOLDER_OPEN_ALT | |
| 362 | ||
| 363 | Action.file.close.description=Close the current document | |
| 364 | Action.file.close.accelerator=Shortcut+W | |
| 365 | Action.file.close.text=_Close | |
| 366 | ||
| 367 | Action.file.close_all.description=Close all open documents | |
| 368 | Action.file.close_all.accelerator=Ctrl+F4 | |
| 369 | Action.file.close_all.text=Close All | |
| 370 | ||
| 371 | Action.file.save.description=Save the document | |
| 372 | Action.file.save.accelerator=Shortcut+S | |
| 373 | Action.file.save.text=_Save | |
| 374 | Action.file.save.icon=FLOPPY_ALT | |
| 375 | ||
| 376 | Action.file.save_as.description=Rename the current document | |
| 377 | Action.file.save_as.text=Save _As | |
| 378 | ||
| 379 | Action.file.save_all.description=Save all open documents | |
| 380 | Action.file.save_all.accelerator=Shortcut+Shift+S | |
| 381 | Action.file.save_all.text=Save A_ll | |
| 382 | ||
| 383 | Action.file.export.pdf.description=Typeset the document | |
| 384 | Action.file.export.pdf.accelerator=Shortcut+P | |
| 385 | Action.file.export.pdf.text=_PDF | |
| 386 | Action.file.export.pdf.icon=FILE_PDF_ALT | |
| 387 | ||
| 388 | Action.file.export.pdf.dir.description=Typeset files in document directory | |
| 389 | Action.file.export.pdf.dir.accelerator=Shortcut+Shift+P | |
| 390 | Action.file.export.pdf.dir.text=_Joined PDF | |
| 391 | Action.file.export.pdf.dir.icon=FILE_PDF_ALT | |
| 392 | ||
| 393 | Action.file.export.html_svg.description=Export the current document as HTML + SVG | |
| 394 | Action.file.export.text=_Export As | |
| 395 | Action.file.export.html_svg.text=HTML and S_VG | |
| 396 | ||
| 397 | Action.file.export.html_tex.description=Export the current document as HTML + TeX | |
| 398 | Action.file.export.html_tex.text=HTML and _TeX | |
| 399 | ||
| 400 | Action.file.export.xhtml_tex.description=Export as XHTML + TeX | |
| 401 | Action.file.export.xhtml_tex.text=_XHTML and TeX | |
| 402 | ||
| 403 | Action.file.export.markdown.description=Export the current document as Markdown | |
| 404 | Action.file.export.markdown.text=Markdown | |
| 405 | ||
| 406 | Action.file.exit.description=Quit the application | |
| 407 | Action.file.exit.text=E_xit | |
| 408 | ||
| 409 | ||
| 410 | Action.edit.undo.description=Undo the previous edit | |
| 411 | Action.edit.undo.accelerator=Shortcut+Z | |
| 412 | Action.edit.undo.text=_Undo | |
| 413 | Action.edit.undo.icon=UNDO | |
| 414 | ||
| 415 | Action.edit.redo.description=Redo the previous edit | |
| 416 | Action.edit.redo.accelerator=Shortcut+Y | |
| 417 | Action.edit.redo.text=_Redo | |
| 418 | Action.edit.redo.icon=REPEAT | |
| 419 | ||
| 420 | Action.edit.cut.description=Delete the selected text or line | |
| 421 | Action.edit.cut.accelerator=Shortcut+X | |
| 422 | Action.edit.cut.text=Cu_t | |
| 423 | Action.edit.cut.icon=CUT | |
| 424 | ||
| 425 | Action.edit.copy.description=Copy the selected text | |
| 426 | Action.edit.copy.accelerator=Shortcut+C | |
| 427 | Action.edit.copy.text=_Copy | |
| 428 | Action.edit.copy.icon=COPY | |
| 429 | ||
| 430 | Action.edit.paste.description=Paste from the clipboard | |
| 431 | Action.edit.paste.accelerator=Shortcut+V | |
| 432 | Action.edit.paste.text=_Paste | |
| 433 | Action.edit.paste.icon=PASTE | |
| 434 | ||
| 435 | Action.edit.select_all.description=Highlight the current document text | |
| 436 | Action.edit.select_all.accelerator=Shortcut+A | |
| 437 | Action.edit.select_all.text=Select _All | |
| 438 | ||
| 439 | Action.edit.find.description=Search for text in the document | |
| 440 | Action.edit.find.accelerator=Shortcut+F | |
| 441 | Action.edit.find.text=_Find | |
| 442 | Action.edit.find.icon=SEARCH | |
| 443 | ||
| 444 | Action.edit.find_next.description=Find next occurrence | |
| 445 | Action.edit.find_next.accelerator=F3 | |
| 446 | Action.edit.find_next.text=Find _Next | |
| 447 | ||
| 448 | Action.edit.find_prev.description=Find previous occurrence | |
| 449 | Action.edit.find_prev.accelerator=Shift+F3 | |
| 450 | Action.edit.find_prev.text=Find _Prev | |
| 451 | ||
| 452 | Action.edit.preferences.description=Edit user preferences | |
| 453 | Action.edit.preferences.accelerator=Ctrl+Alt+S | |
| 454 | Action.edit.preferences.text=_Preferences | |
| 455 | ||
| 456 | ||
| 457 | Action.format.bold.description=Insert strong text | |
| 458 | Action.format.bold.accelerator=Shortcut+B | |
| 459 | Action.format.bold.text=_Bold | |
| 460 | Action.format.bold.icon=BOLD | |
| 461 | ||
| 462 | Action.format.italic.description=Insert text emphasis | |
| 463 | Action.format.italic.accelerator=Shortcut+I | |
| 464 | Action.format.italic.text=_Italic | |
| 465 | Action.format.italic.icon=ITALIC | |
| 466 | ||
| 467 | Action.format.monospace.description=Insert monospace text | |
| 468 | Action.format.monospace.accelerator=Shortcut+` | |
| 469 | Action.format.monospace.text=_Monospace | |
| 470 | ||
| 471 | Action.format.superscript.description=Insert superscript text | |
| 472 | Action.format.superscript.accelerator=Shortcut+[ | |
| 473 | Action.format.superscript.text=Su_perscript | |
| 474 | Action.format.superscript.icon=SUPERSCRIPT | |
| 475 | ||
| 476 | Action.format.subscript.description=Insert subscript text | |
| 477 | Action.format.subscript.accelerator=Shortcut+] | |
| 478 | Action.format.subscript.text=Su_bscript | |
| 479 | Action.format.subscript.icon=SUBSCRIPT | |
| 480 | ||
| 481 | Action.format.strikethrough.description=Insert struck text | |
| 482 | Action.format.strikethrough.accelerator=Shortcut+T | |
| 483 | Action.format.strikethrough.text=Stri_kethrough | |
| 484 | Action.format.strikethrough.icon=STRIKETHROUGH | |
| 485 | ||
| 486 | ||
| 487 | Action.insert.blockquote.description=Insert blockquote | |
| 488 | Action.insert.blockquote.accelerator=Ctrl+Q | |
| 489 | Action.insert.blockquote.text=_Blockquote | |
| 490 | Action.insert.blockquote.icon=QUOTE_LEFT | |
| 491 | ||
| 492 | Action.insert.code.description=Insert inline code | |
| 493 | Action.insert.code.accelerator=Shortcut+K | |
| 494 | Action.insert.code.text=Inline _Code | |
| 495 | Action.insert.code.icon=CODE | |
| 496 | ||
| 497 | Action.insert.fenced_code_block.description=Insert code block | |
| 498 | Action.insert.fenced_code_block.accelerator=Shortcut+Shift+K | |
| 499 | Action.insert.fenced_code_block.text=_Fenced Code Block | |
| 500 | Action.insert.fenced_code_block.prompt.text=Enter code here | |
| 501 | Action.insert.fenced_code_block.icon=FILE_CODE_ALT | |
| 502 | ||
| 503 | Action.insert.link.description=Insert hyperlink | |
| 504 | Action.insert.link.accelerator=Shortcut+L | |
| 505 | Action.insert.link.text=_Link... | |
| 506 | Action.insert.link.icon=LINK | |
| 507 | ||
| 508 | Action.insert.image.description=Insert image | |
| 509 | Action.insert.image.accelerator=Shortcut+G | |
| 510 | Action.insert.image.text=_Image... | |
| 511 | Action.insert.image.icon=PICTURE_ALT | |
| 512 | ||
| 513 | Action.insert.heading.description=Insert heading level | |
| 514 | Action.insert.heading.accelerator=Shortcut+ | |
| 515 | Action.insert.heading.icon=HEADER | |
| 516 | ||
| 517 | Action.insert.heading_1.description=${Action.insert.heading.description} 1 | |
| 518 | Action.insert.heading_1.accelerator=${Action.insert.heading.accelerator}1 | |
| 519 | Action.insert.heading_1.text=Heading _1 | |
| 520 | Action.insert.heading_1.icon=${Action.insert.heading.icon} | |
| 521 | ||
| 522 | Action.insert.heading_2.description=${Action.insert.heading.description} 2 | |
| 523 | Action.insert.heading_2.accelerator=${Action.insert.heading.accelerator}2 | |
| 524 | Action.insert.heading_2.text=Heading _2 | |
| 525 | Action.insert.heading_2.icon=${Action.insert.heading.icon} | |
| 526 | ||
| 527 | Action.insert.heading_3.description=${Action.insert.heading.description} 3 | |
| 528 | Action.insert.heading_3.accelerator=${Action.insert.heading.accelerator}3 | |
| 529 | Action.insert.heading_3.text=Heading _3 | |
| 530 | Action.insert.heading_3.icon=${Action.insert.heading.icon} | |
| 531 | ||
| 532 | Action.insert.unordered_list.description=Insert bulleted list | |
| 533 | Action.insert.unordered_list.accelerator=Shortcut+U | |
| 534 | Action.insert.unordered_list.text=_Unordered List | |
| 535 | Action.insert.unordered_list.icon=LIST_UL | |
| 536 | ||
| 537 | Action.insert.ordered_list.description=Insert enumerated list | |
| 538 | Action.insert.ordered_list.accelerator=Shortcut+Shift+O | |
| 539 | Action.insert.ordered_list.text=_Ordered List | |
| 540 | Action.insert.ordered_list.icon=LIST_OL | |
| 541 | ||
| 542 | Action.insert.horizontal_rule.description=Insert horizontal rule | |
| 543 | Action.insert.horizontal_rule.accelerator=Shortcut+H | |
| 544 | Action.insert.horizontal_rule.text=_Horizontal Rule | |
| 545 | Action.insert.horizontal_rule.icon=LIST_OL | |
| 546 | ||
| 547 | ||
| 548 | Action.definition.create.description=Create a new variable | |
| 549 | Action.definition.create.text=_Create | |
| 550 | Action.definition.create.icon=TREE | |
| 551 | Action.definition.create.tooltip=Add new item (Insert) | |
| 552 | ||
| 553 | Action.definition.rename.description=Rename the selected variable | |
| 554 | Action.definition.rename.text=_Rename | |
| 555 | Action.definition.rename.icon=EDIT | |
| 556 | Action.definition.rename.tooltip=Rename selected item (F2) | |
| 557 | ||
| 558 | Action.definition.delete.description=Delete the selected variables | |
| 559 | Action.definition.delete.text=De_lete | |
| 560 | Action.definition.delete.icon=TRASH | |
| 561 | Action.definition.delete.tooltip=Delete selected items (Delete) | |
| 562 | ||
| 563 | Action.definition.insert.description=Insert a variable | |
| 564 | Action.definition.insert.accelerator=Ctrl+Space | |
| 565 | Action.definition.insert.text=_Insert | |
| 566 | Action.definition.insert.icon=STAR | |
| 567 | ||
| 568 | ||
| 569 | Action.view.refresh.description=Clear all caches | |
| 570 | Action.view.refresh.accelerator=F5 | |
| 571 | Action.view.refresh.text=Refresh | |
| 572 | ||
| 573 | Action.view.preview.description=Open document preview | |
| 574 | Action.view.preview.accelerator=F6 | |
| 575 | Action.view.preview.text=Preview | |
| 576 | ||
| 577 | Action.view.outline.description=Open document outline | |
| 578 | Action.view.outline.accelerator=F7 | |
| 579 | Action.view.outline.text=Outline | |
| 580 | ||
| 581 | Action.view.statistics.description=Open document word counts | |
| 582 | Action.view.statistics.accelerator=F8 | |
| 583 | Action.view.statistics.text=Statistics | |
| 584 | ||
| 585 | Action.view.files.description=Open file manager | |
| 586 | Action.view.files.accelerator=Ctrl+F8 | |
| 587 | Action.view.files.text=Files | |
| 588 | ||
| 589 | Action.view.menubar.description=Toggle menu bar | |
| 590 | Action.view.menubar.accelerator=Ctrl+F9 | |
| 591 | Action.view.menubar.text=Menu bar | |
| 592 | ||
| 593 | Action.view.toolbar.description=Toggle tool bar | |
| 594 | Action.view.toolbar.accelerator=Ctrl+Shift+F9 | |
| 595 | Action.view.toolbar.text=Tool bar | |
| 596 | ||
| 597 | Action.view.statusbar.description=Toggle status bar | |
| 598 | Action.view.statusbar.accelerator=Ctrl+Shift+Alt+F9 | |
| 599 | Action.view.statusbar.text=Status bar | |
| 600 | ||
| 601 | Action.view.log.description=Open document issues | |
| 602 | Action.view.log.accelerator=F12 | |
| 603 | Action.view.log.text=Log | |
| 604 | ||
| 605 | ||
| 606 | Action.help.about.description=Show help dialog | |
| 607 | Action.help.about.accelerator=F1 | |
| 608 | Action.help.about.text=About | |
| 609 | Action.help.about.icon=INFO | |
| 1 | 610 |
| 1 | body,h1,h2,h3,h4,h5,h6,ol,p,ul{margin:0;padding:0}img{max-width:100%;height:auto}table{table-collapse:collapse;table-spacing:0;border-spacing:0} | |
| 2 | ||
| 3 | /* Do not use points (pt): FlyingSaucer on Debian fails to render. */ | |
| 4 | body { | |
| 5 | background-color: #fff; | |
| 6 | margin: 0 auto; | |
| 7 | line-height: 1.6; | |
| 8 | color: #454545; | |
| 9 | padding: .5em; | |
| 10 | font-feature-settings: 'liga' 1; | |
| 11 | font-variant-ligatures: normal; | |
| 12 | } | |
| 13 | ||
| 14 | body>*:first-child { | |
| 15 | margin-top: 0 !important; | |
| 16 | } | |
| 17 | ||
| 18 | body>*:last-child { | |
| 19 | margin-bottom: 0 !important; | |
| 20 | } | |
| 21 | ||
| 22 | #caret { | |
| 23 | background: #fcfeff; | |
| 24 | } | |
| 25 | ||
| 26 | p, blockquote, ul, ol, dl, table, pre { | |
| 27 | margin: 1em 0; | |
| 28 | } | |
| 29 | ||
| 30 | /* HEADERS ***/ | |
| 31 | h1, h2, h3, h4, h5, h6 { | |
| 32 | font-weight: bold; | |
| 33 | margin: 1em 0 .5em; | |
| 34 | } | |
| 35 | ||
| 36 | h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, | |
| 37 | h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code { | |
| 38 | font-size: inherit; | |
| 39 | } | |
| 40 | ||
| 41 | h1 { | |
| 42 | font-size: 28px; | |
| 43 | } | |
| 44 | ||
| 45 | h2 { | |
| 46 | font-size: 24px; | |
| 47 | border-bottom: 1px solid #ccc; | |
| 48 | } | |
| 49 | ||
| 50 | h3 { | |
| 51 | font-size: 20px; | |
| 52 | } | |
| 53 | ||
| 54 | h4 { | |
| 55 | font-size: 18px; | |
| 56 | } | |
| 57 | ||
| 58 | h5 { | |
| 59 | font-size: 16px; | |
| 60 | } | |
| 61 | ||
| 62 | h6 { | |
| 63 | font-size: 14px; | |
| 64 | } | |
| 65 | ||
| 66 | h1+p, h2+p, h3+p, h4+p, h5+p, h6+p { | |
| 67 | margin-top: .5em; | |
| 68 | } | |
| 69 | ||
| 70 | /* LINKS ***/ | |
| 71 | a { | |
| 72 | color: #0077aa; | |
| 73 | text-decoration: none; | |
| 74 | } | |
| 75 | ||
| 76 | a:hover { | |
| 77 | text-decoration: underline; | |
| 78 | } | |
| 79 | ||
| 80 | /* ITEMIZED LISTS ***/ | |
| 81 | ol, ul { | |
| 82 | margin: 0 0 0 2em; | |
| 83 | } | |
| 84 | ||
| 85 | ol { list-style-type: decimal; } | |
| 86 | ol ol { list-style-type: lower-alpha; } | |
| 87 | ol ol ol { list-style-type: lower-roman; } | |
| 88 | ol ol ol ol { list-style-type: upper-alpha; } | |
| 89 | ol ol ol ol ol { list-style-type: upper-roman; } | |
| 90 | ol ol ol ol ol ol { list-style-type: lower-greek; } | |
| 91 | ||
| 92 | ul { list-style-type: disc; } | |
| 93 | ul ul { list-style-type: circle; } | |
| 94 | ul ul ul { list-style-type: square; } | |
| 95 | ul ul ul ul { list-style-type: disc; } | |
| 96 | ul ul ul ul ul { list-style-type: circle; } | |
| 97 | ul ul ul ul ul ul { list-style-type: square; } | |
| 98 | ||
| 99 | /* DEFINITION LISTS ***/ | |
| 100 | dl { | |
| 101 | /** Horizontal scroll bar will appear if set to 100%. */ | |
| 102 | width: 99%; | |
| 103 | overflow: hidden; | |
| 104 | padding-left: 1em; | |
| 105 | } | |
| 106 | ||
| 107 | dl dt { | |
| 108 | font-weight: bold; | |
| 109 | float: left; | |
| 110 | width: 20%; | |
| 111 | clear: both; | |
| 112 | position: relative; | |
| 113 | } | |
| 114 | ||
| 115 | dl dd { | |
| 116 | float: right; | |
| 117 | width: 79%; | |
| 118 | padding-bottom: .5em; | |
| 119 | margin-left: 0; | |
| 120 | } | |
| 121 | ||
| 122 | /* PREFORMATTED CODE ***/ | |
| 123 | pre, code, tt { | |
| 124 | /* Must be bundled in JAR file. */ | |
| 125 | font-family: 'Source Code Pro'; | |
| 126 | font-size: 10pt; | |
| 127 | background-color: #f8f8f8; | |
| 128 | text-decoration: none; | |
| 129 | white-space: pre-wrap; | |
| 130 | word-wrap: break-word; | |
| 131 | overflow-wrap: anywhere; | |
| 132 | border-radius: .125em; | |
| 133 | } | |
| 134 | ||
| 135 | code, tt { | |
| 136 | padding: .25em; | |
| 137 | } | |
| 138 | ||
| 139 | pre > code { | |
| 140 | padding: 0; | |
| 141 | border: none; | |
| 142 | background: transparent; | |
| 143 | } | |
| 144 | ||
| 145 | pre { | |
| 146 | border: .125em solid #ccc; | |
| 147 | overflow: auto; | |
| 148 | padding: .25em .5em; | |
| 149 | } | |
| 150 | ||
| 151 | pre code, pre tt { | |
| 152 | background-color: transparent; | |
| 153 | border: none; | |
| 154 | } | |
| 155 | ||
| 156 | /* BLOCKQUOTES ***/ | |
| 157 | blockquote { | |
| 158 | border-left: .25em solid #ccc; | |
| 159 | padding: 0 1em; | |
| 160 | color: #777; | |
| 161 | } | |
| 162 | ||
| 163 | blockquote>:first-child { | |
| 164 | margin-top: 0; | |
| 165 | } | |
| 166 | ||
| 167 | blockquote>:last-child { | |
| 168 | margin-bottom: 0; | |
| 169 | } | |
| 170 | ||
| 171 | /* TABLES ***/ | |
| 172 | table { | |
| 173 | width: 100%; | |
| 174 | } | |
| 175 | ||
| 176 | tr:nth-child(odd) { | |
| 177 | background-color: #eee; | |
| 178 | } | |
| 179 | ||
| 180 | th { | |
| 181 | background-color: #454545; | |
| 182 | color: #fff; | |
| 183 | } | |
| 184 | ||
| 185 | th, td { | |
| 186 | text-align: left; | |
| 187 | padding: 0 1em; | |
| 188 | } | |
| 189 | ||
| 190 | /* IMAGES ***/ | |
| 191 | img { | |
| 192 | max-width: 100%; | |
| 193 | ||
| 194 | /* Tell FlyingSaucer to treat images as block elements. | |
| 195 | * See SvgReplacedElementFactory. | |
| 196 | */ | |
| 197 | display: inline-block; | |
| 198 | } | |
| 199 | ||
| 200 | /* TEX ***/ | |
| 201 | ||
| 202 | /* Tell FlyingSaucer to treat tex elements as nodes. | |
| 203 | * See SvgReplacedElementFactory. | |
| 204 | */ | |
| 205 | tex { | |
| 206 | /* Ensure the formulas can be inlined with text. */ | |
| 207 | display: inline-block; | |
| 208 | } | |
| 209 | ||
| 210 | /* Without a robust typesetting engine, there's no | |
| 211 | * nice-looking way to automatically typeset equations. | |
| 212 | * Sometimes baseline is appropriate, sometimes the | |
| 213 | * descender must be considered, and sometimes vertical | |
| 214 | * alignment to the middle looks best. | |
| 215 | */ | |
| 216 | p tex { | |
| 217 | vertical-align: baseline; | |
| 218 | } | |
| 219 | ||
| 220 | /* RULES ***/ | |
| 221 | hr { | |
| 222 | clear: both; | |
| 223 | margin: 1.5em 0 1.5em; | |
| 224 | height: 0; | |
| 225 | overflow: hidden; | |
| 226 | border: none; | |
| 227 | background: transparent; | |
| 228 | border-bottom: .125em solid #ccc; | |
| 229 | } | |
| 230 | ||
| 231 | /* EMAIL ***/ | |
| 232 | div.email { | |
| 233 | padding: 0 1.5em; | |
| 234 | text-align: left; | |
| 235 | text-indent: 0; | |
| 236 | border-style: solid; | |
| 237 | border-width: 0.05em; | |
| 238 | border-radius: .25em; | |
| 239 | background-color: #f8f8f8; | |
| 240 | } | |
| 1 | 241 |
| 1 | body { | |
| 2 | font-family: 'Noto Serif CJK JP'; | |
| 3 | } | |
| 4 | ||
| 5 | pre, code, tt { | |
| 6 | font-family: 'Noto Sans Mono CJK JP'; | |
| 7 | } | |
| 1 | 8 |
| 1 | body { | |
| 2 | font-family: 'Noto Serif CJK KR'; | |
| 3 | } | |
| 4 | ||
| 5 | pre, code, tt { | |
| 6 | font-family: 'Noto Sans Mono CJK KR'; | |
| 7 | } | |
| 1 | 8 |
| 1 | body { | |
| 2 | font-family: 'Noto Serif CJK SC'; | |
| 3 | } | |
| 4 | ||
| 5 | pre, code, tt { | |
| 6 | font-family: 'Noto Sans Mono CJK SC'; | |
| 7 | } | |
| 1 | 8 |
| 1 | body { | |
| 2 | font-family: 'Noto Serif CJK SC'; | |
| 3 | } | |
| 4 | ||
| 5 | pre, code, tt { | |
| 6 | font-family: 'Noto Sans Mono CJK SC'; | |
| 7 | } | |
| 1 | 8 |
| 1 | body { | |
| 2 | font-family: 'Noto Serif CJK SC'; | |
| 3 | } | |
| 4 | ||
| 5 | pre, code, tt { | |
| 6 | font-family: 'Noto Sans Mono CJK HK'; | |
| 7 | } | |
| 1 | 8 |
| 1 | body { | |
| 2 | font-family: 'Noto Serif CJK TC'; | |
| 3 | } | |
| 4 | ||
| 5 | pre, code, tt { | |
| 6 | font-family: 'Noto Sans Mono CJK TC'; | |
| 7 | } | |
| 1 | 8 |
| 1 | Listing English contractions helps converting straight apostrophes into curly apostrophes. The files include: | |
| 2 | ||
| 3 | * began.txt -- Contractions that begin with an apostrophe. | |
| 4 | * ended.txt -- Contractions that end with an apostrophe. | |
| 5 | * inner.txt -- Contractions that have internal apostrophes. | |
| 6 | * outer.txt -- Contractions that start and end with an apostrophe. | |
| 7 | * verbs.txt -- Contractions that form suffixes for a variety of words. | |
| 8 | ||
| 9 | The contractions for verbs must be detected dynamically, all other contractions can be hard-coded into either regular expressions or EBNF grammars. | |
| 10 | ||
| 1 | 11 |
| 1 | 'aporth | |
| 2 | 'bout | |
| 3 | 'boutcha | |
| 4 | 'boutchu | |
| 5 | 'choo | |
| 6 | 'dillo | |
| 7 | 'e'll | |
| 8 | 'ere | |
| 9 | 'e | |
| 10 | 'e's | |
| 11 | 'fraid | |
| 12 | 'fro | |
| 13 | 'ho | |
| 14 | 'kay | |
| 15 | 'lo | |
| 16 | 'n | |
| 17 | 'neath | |
| 18 | 'nother | |
| 19 | 'onna | |
| 20 | 'pon | |
| 21 | 'sblood | |
| 22 | 'scuse | |
| 23 | 'sfar | |
| 24 | 'sfoot | |
| 25 | 'sup | |
| 26 | 't | |
| 27 | 'taint | |
| 28 | 'tain't | |
| 29 | 'tis | |
| 30 | 'tisn't | |
| 31 | 'tshall | |
| 32 | 'twas | |
| 33 | 'twasn't | |
| 34 | 'tween | |
| 35 | 'twere | |
| 36 | 'tweren't | |
| 37 | 'twill | |
| 38 | 'twixt | |
| 39 | 'twon't | |
| 40 | 'twou'd | |
| 41 | 'twou'dn't | |
| 42 | 'twould | |
| 43 | 'twouldn't | |
| 44 | 'um | |
| 45 | 've | |
| 46 | 'zat | |
| 47 | ||
| 1 | 48 |
| 1 | ain' | |
| 2 | an' | |
| 3 | burlin' | |
| 4 | cas' | |
| 5 | didn' | |
| 6 | doan' | |
| 7 | doin' | |
| 8 | fo' | |
| 9 | gerrin' | |
| 10 | gon' | |
| 11 | i' | |
| 12 | Ima' | |
| 13 | mo' | |
| 14 | namsayin' | |
| 15 | o' | |
| 16 | ol' | |
| 17 | o'th' | |
| 18 | po' | |
| 19 | t' | |
| 20 | th' | |
| 21 | ||
| 1 | 22 |
| 1 | aboves'd | |
| 2 | after't | |
| 3 | a'ight | |
| 4 | ain't | |
| 5 | ain'tcha | |
| 6 | all's | |
| 7 | and's | |
| 8 | a'n't | |
| 9 | an't | |
| 10 | anybody'll | |
| 11 | anybody's | |
| 12 | aren'chu | |
| 13 | aren't | |
| 14 | a'right | |
| 15 | as't | |
| 16 | at's | |
| 17 | bain't | |
| 18 | bean't | |
| 19 | before't | |
| 20 | ben't | |
| 21 | better'n | |
| 22 | bettern't | |
| 23 | bisn't | |
| 24 | b'long | |
| 25 | bo's'n | |
| 26 | br'er | |
| 27 | but's | |
| 28 | by'r | |
| 29 | by't | |
| 30 | cain't | |
| 31 | call't | |
| 32 | cam'st | |
| 33 | cann't | |
| 34 | ca'n't | |
| 35 | can't | |
| 36 | can'tcha | |
| 37 | can't've | |
| 38 | can've | |
| 39 | cap'n | |
| 40 | casn't | |
| 41 | ch'ill | |
| 42 | c'mere | |
| 43 | c'min | |
| 44 | c'mon | |
| 45 | col's | |
| 46 | couldn't | |
| 47 | couldn't've | |
| 48 | couldn've | |
| 49 | could've | |
| 50 | cudn't | |
| 51 | damfidon't | |
| 52 | damnfidon't | |
| 53 | daredn't | |
| 54 | daren't | |
| 55 | dasn't | |
| 56 | dassn't | |
| 57 | dat's | |
| 58 | dere's | |
| 59 | der's | |
| 60 | didn't | |
| 61 | didn'tcha | |
| 62 | didn'tchya | |
| 63 | di'n't | |
| 64 | din't | |
| 65 | doesn't | |
| 66 | does't | |
| 67 | don't | |
| 68 | don'tcha | |
| 69 | do't | |
| 70 | dothn't | |
| 71 | dudn't | |
| 72 | dun't | |
| 73 | dursen't | |
| 74 | dursn't | |
| 75 | durstn't | |
| 76 | d'ya | |
| 77 | d'y'all | |
| 78 | d'ye | |
| 79 | d'yer | |
| 80 | d'you | |
| 81 | e'en | |
| 82 | e'er | |
| 83 | everybody's | |
| 84 | everyone's | |
| 85 | ev'ry | |
| 86 | far's | |
| 87 | fo'c's'le | |
| 88 | fo'c'sle | |
| 89 | fo'c'stle | |
| 90 | for't | |
| 91 | f'rever | |
| 92 | f'rexample | |
| 93 | g'bye | |
| 94 | g'day | |
| 95 | g'head | |
| 96 | gi's | |
| 97 | giv'n | |
| 98 | g'night | |
| 99 | g'wan | |
| 100 | hadn't | |
| 101 | hadn't've | |
| 102 | had've | |
| 103 | hain't | |
| 104 | ha'n't | |
| 105 | han't | |
| 106 | ha'pence | |
| 107 | ha'pennies | |
| 108 | ha'penny | |
| 109 | ha'p'orth | |
| 110 | ha'porth | |
| 111 | ha'p'orths | |
| 112 | hasn't | |
| 113 | has't | |
| 114 | haven't | |
| 115 | have't | |
| 116 | havn't | |
| 117 | heav'n | |
| 118 | he'd | |
| 119 | he'd've | |
| 120 | he'l | |
| 121 | he'll | |
| 122 | he'll've | |
| 123 | here'll | |
| 124 | here're | |
| 125 | here's | |
| 126 | her's | |
| 127 | he's | |
| 128 | he'sn't | |
| 129 | he've | |
| 130 | how'd | |
| 131 | how'll | |
| 132 | how'm | |
| 133 | how're | |
| 134 | how's | |
| 135 | how't | |
| 136 | how've | |
| 137 | I'd | |
| 138 | I'd-a | |
| 139 | I'da | |
| 140 | idn't | |
| 141 | I'dn't've | |
| 142 | I'd've | |
| 143 | i'faith | |
| 144 | if'n | |
| 145 | if't | |
| 146 | I'l | |
| 147 | I'll | |
| 148 | I'll've | |
| 149 | I'm | |
| 150 | I'm'a | |
| 151 | I'm-a | |
| 152 | I'ma | |
| 153 | i'm'a | |
| 154 | i'ma | |
| 155 | I'mma | |
| 156 | i'n | |
| 157 | in's | |
| 158 | i'n't | |
| 159 | in't | |
| 160 | into't | |
| 161 | I's | |
| 162 | i's | |
| 163 | I'se | |
| 164 | isn't | |
| 165 | is't | |
| 166 | it'd | |
| 167 | it'd've | |
| 168 | it'll | |
| 169 | it's | |
| 170 | it'sn't | |
| 171 | I've | |
| 172 | I'ven't | |
| 173 | let's | |
| 174 | li'l | |
| 175 | littl'un | |
| 176 | ma'am | |
| 177 | mayn't | |
| 178 | may't | |
| 179 | may've | |
| 180 | m'dear | |
| 181 | mightn't | |
| 182 | mightn't've | |
| 183 | might've | |
| 184 | m'lad | |
| 185 | m'ladies | |
| 186 | m'lady | |
| 187 | m'lord | |
| 188 | m'lords | |
| 189 | mng't | |
| 190 | more'n | |
| 191 | mus'n't | |
| 192 | musn't | |
| 193 | mustn't | |
| 194 | mustn't've | |
| 195 | must've | |
| 196 | needn't | |
| 197 | nee'n't | |
| 198 | ne'er | |
| 199 | ne'er-do-well | |
| 200 | never've | |
| 201 | nobody'd | |
| 202 | nobody's | |
| 203 | nobody've | |
| 204 | nor'easter | |
| 205 | not've | |
| 206 | n't | |
| 207 | o'clock | |
| 208 | o'er | |
| 209 | o'erhead | |
| 210 | o'erload | |
| 211 | o'erloads | |
| 212 | o'erlook | |
| 213 | o'erlooks | |
| 214 | Oi'll | |
| 215 | Oi've | |
| 216 | o'lantern | |
| 217 | o'lanterns | |
| 218 | one's | |
| 219 | on't | |
| 220 | other'n | |
| 221 | oughtn't | |
| 222 | oughtn't've | |
| 223 | p'aps | |
| 224 | penn'orth | |
| 225 | pen'orth | |
| 226 | people'd | |
| 227 | po'boy | |
| 228 | pow'r | |
| 229 | p'r'aps | |
| 230 | p'raps | |
| 231 | pray'r | |
| 232 | p'rhaps | |
| 233 | pudd'n'head | |
| 234 | r'coon | |
| 235 | run-o'-the-mill | |
| 236 | same's | |
| 237 | see't | |
| 238 | se'nnight | |
| 239 | sev'n | |
| 240 | shalln't | |
| 241 | shall's | |
| 242 | shall've | |
| 243 | sha'n't | |
| 244 | shan't | |
| 245 | sh'd | |
| 246 | she'd | |
| 247 | she'd've | |
| 248 | she'l | |
| 249 | she'll | |
| 250 | she'll've | |
| 251 | she's | |
| 252 | she've | |
| 253 | shouldn't | |
| 254 | shouldn't've | |
| 255 | should've | |
| 256 | s'long | |
| 257 | s'matter | |
| 258 | s'more | |
| 259 | s'mores | |
| 260 | somebody'd | |
| 261 | somebody's | |
| 262 | someone's | |
| 263 | something's | |
| 264 | sort've | |
| 265 | so's | |
| 266 | th'are | |
| 267 | th'art | |
| 268 | that'd | |
| 269 | that'd've | |
| 270 | that'll | |
| 271 | that'll've | |
| 272 | that're | |
| 273 | that's | |
| 274 | that've | |
| 275 | them's | |
| 276 | there'd | |
| 277 | there'll | |
| 278 | there're | |
| 279 | there's | |
| 280 | there've | |
| 281 | these're | |
| 282 | these've | |
| 283 | they'd | |
| 284 | they'da | |
| 285 | they'd've | |
| 286 | they'l | |
| 287 | they'll | |
| 288 | they'll've | |
| 289 | they're | |
| 290 | they's | |
| 291 | they've | |
| 292 | th'immortall | |
| 293 | this'd | |
| 294 | this'll | |
| 295 | this's | |
| 296 | this've | |
| 297 | those're | |
| 298 | those've | |
| 299 | tho't | |
| 300 | thou'dst | |
| 301 | thou'lt | |
| 302 | thou'rt | |
| 303 | thou'st | |
| 304 | tops'l | |
| 305 | to't | |
| 306 | to've | |
| 307 | twasn't | |
| 308 | twopenn'orths | |
| 309 | t'ye | |
| 310 | unto't | |
| 311 | upon't | |
| 312 | usedn't | |
| 313 | usen't | |
| 314 | us's | |
| 315 | view't | |
| 316 | wadn't | |
| 317 | wait'll | |
| 318 | wa'n't | |
| 319 | wan't | |
| 320 | warn't | |
| 321 | wasn't | |
| 322 | was't | |
| 323 | wazn't | |
| 324 | we'd | |
| 325 | we'd've | |
| 326 | we'l | |
| 327 | we'll | |
| 328 | we'll've | |
| 329 | we're | |
| 330 | weren't | |
| 331 | we's | |
| 332 | we've | |
| 333 | we'ven't | |
| 334 | what'd | |
| 335 | whate'er | |
| 336 | whatever's | |
| 337 | what'll | |
| 338 | what'm | |
| 339 | what're | |
| 340 | what's | |
| 341 | what've | |
| 342 | when'd | |
| 343 | whene'er | |
| 344 | when'll | |
| 345 | when's | |
| 346 | where'd | |
| 347 | where'er | |
| 348 | where'm | |
| 349 | where're | |
| 350 | where's | |
| 351 | where've | |
| 352 | which'd | |
| 353 | which'll | |
| 354 | which're | |
| 355 | which's | |
| 356 | which've | |
| 357 | who'd | |
| 358 | who'da | |
| 359 | who'd've | |
| 360 | whoe'er | |
| 361 | who'll | |
| 362 | who'm | |
| 363 | whom're | |
| 364 | who're | |
| 365 | who's | |
| 366 | who've | |
| 367 | why'd | |
| 368 | why'm | |
| 369 | whyn't | |
| 370 | why're | |
| 371 | why's | |
| 372 | willn't | |
| 373 | will've | |
| 374 | with't | |
| 375 | wolln't | |
| 376 | wo'n't | |
| 377 | won't | |
| 378 | won't've | |
| 379 | woo't | |
| 380 | worn't | |
| 381 | wou'd | |
| 382 | wouldn't | |
| 383 | wouldn'ta | |
| 384 | wouldn't've | |
| 385 | would've | |
| 386 | wudn't | |
| 387 | y'ad | |
| 388 | y'ain't | |
| 389 | y'all | |
| 390 | ya'll | |
| 391 | y'all'd | |
| 392 | y'all'd've | |
| 393 | y'all'll | |
| 394 | y'all're | |
| 395 | y'allself | |
| 396 | y'allselves | |
| 397 | y'all've | |
| 398 | y'are | |
| 399 | y'ave | |
| 400 | ye'd | |
| 401 | ye'll | |
| 402 | y'ere | |
| 403 | ye're | |
| 404 | yestere'en | |
| 405 | yet's | |
| 406 | ye've | |
| 407 | y'ever | |
| 408 | y'knew | |
| 409 | y'know | |
| 410 | you'd | |
| 411 | you'dn't've | |
| 412 | you'd've | |
| 413 | you'l | |
| 414 | you'll | |
| 415 | you'll've | |
| 416 | you're | |
| 417 | you'ren't | |
| 418 | yours'd | |
| 419 | yours'll | |
| 420 | yours've | |
| 421 | you's | |
| 422 | you'se | |
| 423 | you've | |
| 424 | you'ven't | |
| 425 | yo've | |
| 426 | y'see | |
| 427 | ||
| 1 | 428 |
| 1 | 'n' | |
| 2 | ||
| 1 | 3 |
| 1 | 'd | |
| 2 | 'll | |
| 3 | 'm | |
| 4 | 're | |
| 5 | 's | |
| 6 | 've | |
| 7 | ||
| 1 | 8 |
| 1 | # ######################################################################## | |
| 2 | # Application | |
| 3 | # ######################################################################## | |
| 4 | ||
| 5 | application.title=keenwrite | |
| 6 | application.package=com/${application.title} | |
| 7 | application.messages= com.${application.title}.messages | |
| 8 | ||
| 9 | # Suppress multiple file modified notifications for one logical modification. | |
| 10 | # Given in milliseconds. | |
| 11 | application.watchdog.timeout=50 | |
| 12 | ||
| 13 | # ######################################################################## | |
| 14 | # Preferences | |
| 15 | # ######################################################################## | |
| 16 | ||
| 17 | preferences.root=com.${application.title} | |
| 18 | preferences.root.state=state | |
| 19 | preferences.root.options=options | |
| 20 | preferences.root.definition.source=definition.source | |
| 21 | ||
| 22 | # ######################################################################## | |
| 23 | # File and Path References | |
| 24 | # ######################################################################## | |
| 25 | ||
| 26 | file.stylesheet.application.dir=${application.package}/skins | |
| 27 | file.stylesheet.application.base=${file.stylesheet.application.dir}/scene.css | |
| 28 | file.stylesheet.application.skin=${file.stylesheet.application.dir}/{0}.css | |
| 29 | file.stylesheet.markdown=${application.package}/editor/markdown.css | |
| 30 | # {0} language code, {1} script code, {2} country code | |
| 31 | file.stylesheet.markdown.locale=${application.package}/editor/markdown_{0}-{1}-{2}.css | |
| 32 | file.stylesheet.xml=${application.package}/xml.css | |
| 33 | ||
| 34 | # Preview styles are loaded statically through a class's classloader. | |
| 35 | file.stylesheet.preview=webview.css | |
| 36 | # {0} language code, {1} script code, {2} country code | |
| 37 | file.stylesheet.preview.locale=webview_{0}-{1}-{2}.css | |
| 38 | ||
| 39 | file.logo.16=${application.package}/logo16.png | |
| 40 | file.logo.32=${application.package}/logo32.png | |
| 41 | file.logo.128=${application.package}/logo128.png | |
| 42 | file.logo.256=${application.package}/logo256.png | |
| 43 | file.logo.512=${application.package}/logo512.png | |
| 44 | ||
| 45 | # Default file name when a new file is created. | |
| 46 | # This ensures that the file type can always be | |
| 47 | # discerned so that the correct type of variable | |
| 48 | # reference can be inserted. | |
| 49 | file.default.document=untitled.md | |
| 50 | file.default.definition=variables.yaml | |
| 51 | ||
| 52 | # ######################################################################## | |
| 53 | # File name Extensions | |
| 54 | # ######################################################################## | |
| 55 | ||
| 56 | # Comma-separated list of definition file name extensions. | |
| 57 | definition.file.ext.json=*.json | |
| 58 | definition.file.ext.toml=*.toml | |
| 59 | definition.file.ext.yaml=*.yml,*.yaml | |
| 60 | definition.file.ext.properties=*.properties,*.props | |
| 61 | ||
| 62 | # Comma-separated list of file name extensions. | |
| 63 | file.ext.rmarkdown=*.Rmd | |
| 64 | file.ext.rxml=*.Rxml | |
| 65 | file.ext.source=*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt,${file.ext.rmarkdown},${file.ext.rxml} | |
| 66 | file.ext.definition=${definition.file.ext.yaml} | |
| 67 | file.ext.xml=*.xml,${file.ext.rxml} | |
| 68 | file.ext.all=*.* | |
| 69 | ||
| 70 | # File name extension search order for images. | |
| 71 | file.ext.image.order=svg pdf png jpg tiff | |
| 72 | ||
| 73 | # ######################################################################## | |
| 74 | # Variable Name Editor | |
| 75 | # ######################################################################## | |
| 76 | ||
| 77 | # Maximum number of characters for a variable name. A variable is defined | |
| 78 | # as one or more non-whitespace characters up to this maximum length. | |
| 79 | editor.variable.maxLength=256 | |
| 80 | ||
| 81 | # ######################################################################## | |
| 82 | # Dialog Preferences | |
| 83 | # ######################################################################## | |
| 84 | ||
| 85 | dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R | |
| 86 | dialog.alert.button.order.linux=L_HE+UNYACBXIO_R | |
| 87 | dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R | |
| 88 | ||
| 89 | # Ensures a consistent button order for alert dialogs across platforms (because | |
| 90 | # the default button order on Linux defies all logic). | |
| 91 | dialog.alert.button.order=${dialog.alert.button.order.windows} | |
| 1 | 92 |
| 1 | .root { | |
| 2 | -fx-base: rgb( 43, 43, 43 ); | |
| 3 | -fx-background: -fx-base; | |
| 4 | -fx-control-inner-background: -fx-base; | |
| 5 | ||
| 6 | -fx-light-text-color: rgb( 187, 187, 187 ); | |
| 7 | -fx-mid-text-color: derive( -fx-base, 100% ); | |
| 8 | -fx-dark-text-color: derive( -fx-base, 25% ); | |
| 9 | -fx-text-foreground: -fx-light-text-color; | |
| 10 | -fx-text-background: derive( -fx-control-inner-background, 7.5% ); | |
| 11 | -fx-text-selection: derive( -fx-control-inner-background, 45% ); | |
| 12 | ||
| 13 | /* Make controls ( buttons, thumb, etc. ) slightly lighter */ | |
| 14 | -fx-color: derive( -fx-base, 20% ); | |
| 15 | } | |
| 16 | ||
| 17 | .caret { | |
| 18 | -fx-stroke: -fx-accent; | |
| 19 | } | |
| 20 | ||
| 21 | .glyph-icon { | |
| 22 | -fx-text-fill: -fx-light-text-color; | |
| 23 | -fx-fill: -fx-light-text-color; | |
| 24 | } | |
| 25 | ||
| 26 | .glyph-icon:hover { | |
| 27 | -fx-effect: dropshadow( three-pass-box, rgba( 0, 0, 0, 0.2 ), 4, 0, 0, 0 ); | |
| 28 | } | |
| 29 | ||
| 30 | /* Fix derived prompt color for text fields */ | |
| 31 | .text-input { | |
| 32 | -fx-prompt-text-fill: derive( -fx-control-inner-background, +50% ); | |
| 33 | } | |
| 34 | ||
| 35 | /* Keep prompt invisible when focused ( above color fix overrides it ) */ | |
| 36 | .text-input:focused { | |
| 37 | -fx-prompt-text-fill: transparent; | |
| 38 | } | |
| 39 | ||
| 40 | /* Fix scroll bar buttons arrows colors */ | |
| 41 | .scroll-bar > .increment-button > .increment-arrow, | |
| 42 | .scroll-bar > .decrement-button > .decrement-arrow { | |
| 43 | -fx-background-color: -fx-mark-highlight-color, -fx-light-text-color; | |
| 44 | } | |
| 45 | ||
| 46 | .scroll-bar > .increment-button:hover > .increment-arrow, | |
| 47 | .scroll-bar > .decrement-button:hover > .decrement-arrow { | |
| 48 | -fx-background-color: -fx-mark-highlight-color, rgb( 240, 240, 240 ); | |
| 49 | } | |
| 50 | ||
| 51 | .scroll-bar > .increment-button:pressed > .increment-arrow, | |
| 52 | .scroll-bar > .decrement-button:pressed > .decrement-arrow { | |
| 53 | -fx-background-color: -fx-mark-highlight-color, rgb( 255, 255, 255 ); | |
| 54 | } | |
| 1 | 55 |
| 1 | /* https://stackoverflow.com/a/58441758/59087 | |
| 2 | */ | |
| 3 | .root { | |
| 4 | -fx-accent: #1e74c6; | |
| 5 | -fx-focus-color: -fx-accent; | |
| 6 | -fx-base: #373e43; | |
| 7 | -fx-control-inner-background: derive( -fx-base, 35% ); | |
| 8 | -fx-control-inner-background-alt: -fx-control-inner-background; | |
| 9 | ||
| 10 | -fx-light-text-color: derive( -fx-base, 150% ); | |
| 11 | -fx-mid-text-color: derive( -fx-base, 100% ); | |
| 12 | -fx-dark-text-color: derive( -fx-base, 25% ); | |
| 13 | -fx-text-foreground: -fx-light-text-color; | |
| 14 | -fx-text-background: derive( -fx-control-inner-background, 7.5% ); | |
| 15 | -fx-text-selection: derive( -fx-control-inner-background, 45% ); | |
| 16 | } | |
| 17 | ||
| 18 | .glyph-icon { | |
| 19 | -fx-text-fill: -fx-light-text-color; | |
| 20 | -fx-fill: -fx-light-text-color; | |
| 21 | } | |
| 22 | ||
| 23 | .glyph-icon:hover { | |
| 24 | -fx-effect: dropshadow( three-pass-box, rgba( 0, 0, 0, 0.2 ), 4, 0, 0, 0 ); | |
| 25 | } | |
| 26 | ||
| 27 | .label { | |
| 28 | -fx-text-fill: -fx-light-text-color; | |
| 29 | } | |
| 30 | ||
| 31 | .text-field { | |
| 32 | -fx-prompt-text-fill: gray; | |
| 33 | } | |
| 34 | ||
| 35 | .button { | |
| 36 | -fx-focus-traversable: false; | |
| 37 | } | |
| 38 | ||
| 39 | .button:hover { | |
| 40 | -fx-text-fill: white; | |
| 41 | } | |
| 42 | ||
| 43 | .separator *.line { | |
| 44 | -fx-background-color: #3C3C3C; | |
| 45 | -fx-border-style: solid; | |
| 46 | -fx-border-width: 1px; | |
| 47 | } | |
| 48 | ||
| 49 | .scroll-bar { | |
| 50 | -fx-background-color: derive( -fx-base, 45% ); | |
| 51 | } | |
| 52 | ||
| 53 | .button:default { | |
| 54 | -fx-base: -fx-accent; | |
| 55 | } | |
| 56 | ||
| 57 | .table-view { | |
| 58 | -fx-selection-bar-non-focused: derive( -fx-base, 50% ); | |
| 59 | } | |
| 60 | ||
| 61 | .table-view .column-header .label { | |
| 62 | -fx-alignment: CENTER_LEFT; | |
| 63 | -fx-font-weight: none; | |
| 64 | } | |
| 65 | ||
| 66 | .list-cell:even, | |
| 67 | .list-cell:odd, | |
| 68 | .table-row-cell:even, | |
| 69 | .table-row-cell:odd { | |
| 70 | -fx-control-inner-background: derive( -fx-base, 15% ); | |
| 71 | } | |
| 72 | ||
| 73 | .list-cell:empty, | |
| 74 | .table-row-cell:empty { | |
| 75 | -fx-background-color: transparent; | |
| 76 | } | |
| 77 | ||
| 78 | .list-cell, | |
| 79 | .table-row-cell { | |
| 80 | -fx-border-color: transparent; | |
| 81 | -fx-table-cell-border-color: transparent; | |
| 82 | } | |
| 83 | ||
| 84 | /* Avoid clipping text descenders in statistics table row. */ | |
| 85 | .table-row-cell { | |
| 86 | -fx-cell-size: 30px; | |
| 87 | } | |
| 1 | 88 |
| 1 | /* https://github.com/joffrey-bion/javafx-themes/blob/master/css/modena_dark.css | |
| 2 | */ | |
| 3 | .root { | |
| 4 | -fx-base: rgb( 50, 50, 50 ); | |
| 5 | -fx-background: -fx-base; | |
| 6 | ||
| 7 | /* Make controls ( buttons, thumb, etc. ) slightly lighter */ | |
| 8 | -fx-color: derive( -fx-base, 10% ); | |
| 9 | ||
| 10 | /* Text fields and table rows background */ | |
| 11 | -fx-control-inner-background: rgb( 20, 20, 20 ); | |
| 12 | /* Version of -fx-control-inner-background for alternative rows */ | |
| 13 | -fx-control-inner-background-alt: derive( -fx-control-inner-background, 2.5% ); | |
| 14 | ||
| 15 | /* Text colors depending on background's brightness */ | |
| 16 | -fx-light-text-color: rgb( 220, 220, 220 ); | |
| 17 | -fx-mid-text-color: rgb( 100, 100, 100 ); | |
| 18 | -fx-dark-text-color: rgb( 20, 20, 20 ); | |
| 19 | -fx-text-foreground: -fx-light-text-color; | |
| 20 | -fx-text-background: derive( -fx-control-inner-background, 7.5% ); | |
| 21 | -fx-text-selection: derive( -fx-control-inner-background, 45% ); | |
| 22 | ||
| 23 | /* A bright blue for highlighting/accenting objects. For example: selected | |
| 24 | * text; selected items in menus, lists, trees, and tables; progress bars */ | |
| 25 | -fx-accent: rgb( 0, 80, 100 ); | |
| 26 | ||
| 27 | /* Color of non-focused yet selected elements */ | |
| 28 | -fx-selection-bar-non-focused: rgb( 50, 50, 50 ); | |
| 29 | } | |
| 30 | ||
| 31 | .glyph-icon { | |
| 32 | -fx-text-fill: -fx-light-text-color; | |
| 33 | -fx-fill: -fx-light-text-color; | |
| 34 | } | |
| 35 | ||
| 36 | .glyph-icon:hover { | |
| 37 | -fx-effect: dropshadow( three-pass-box, rgba( 0, 0, 0, 0.2 ), 4, 0, 0, 0 ); | |
| 38 | } | |
| 39 | ||
| 40 | /* Fix derived prompt color for text fields */ | |
| 41 | .text-input { | |
| 42 | -fx-prompt-text-fill: derive( -fx-control-inner-background, +50% ); | |
| 43 | } | |
| 44 | ||
| 45 | /* Keep prompt invisible when focused ( above color fix overrides it ) */ | |
| 46 | .text-input:focused { | |
| 47 | -fx-prompt-text-fill: transparent; | |
| 48 | } | |
| 49 | ||
| 50 | /* Fix scroll bar buttons arrows colors */ | |
| 51 | .scroll-bar > .increment-button > .increment-arrow, | |
| 52 | .scroll-bar > .decrement-button > .decrement-arrow { | |
| 53 | -fx-background-color: -fx-mark-highlight-color, rgb( 220, 220, 220 ); | |
| 54 | } | |
| 55 | ||
| 56 | .scroll-bar > .increment-button:hover > .increment-arrow, | |
| 57 | .scroll-bar > .decrement-button:hover > .decrement-arrow { | |
| 58 | -fx-background-color: -fx-mark-highlight-color, rgb( 240, 240, 240 ); | |
| 59 | } | |
| 60 | ||
| 61 | .scroll-bar > .increment-button:pressed > .increment-arrow, | |
| 62 | .scroll-bar > .decrement-button:pressed > .decrement-arrow { | |
| 63 | -fx-background-color: -fx-mark-highlight-color, rgb( 255, 255, 255 ); | |
| 64 | } | |
| 1 | 65 |
| 1 | .root { | |
| 2 | -fx-text-foreground: -fx-dark-text-color; | |
| 3 | -fx-text-background: derive( -fx-accent, 124% ); | |
| 4 | -fx-text-selection: #a6d2ff; | |
| 5 | } | |
| 1 | 6 |
| 1 | /* | |
| 2 | * Copyright (c) 2015 Karl Tauber <karl at jformdesigner dot com> | |
| 3 | * All rights reserved. | |
| 4 | * | |
| 5 | * Redistribution and use in source and binary forms, with or without | |
| 6 | * modification, are permitted provided that the following conditions are met: | |
| 7 | * | |
| 8 | * o Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * | |
| 11 | * o Redistributions in binary form must reproduce the above copyright | |
| 12 | * notice, this list of conditions and the following disclaimer in the | |
| 13 | * documentation and/or other materials provided with the distribution. | |
| 14 | * | |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 16 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 17 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 18 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 19 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 20 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 21 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 26 | */ | |
| 27 | ||
| 28 | .tool-bar { | |
| 29 | -fx-spacing: 0; | |
| 30 | } | |
| 31 | ||
| 32 | .tool-bar .button { | |
| 33 | -fx-background-color: transparent; | |
| 34 | } | |
| 35 | ||
| 36 | .tool-bar .button:hover { | |
| 37 | -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color; | |
| 38 | -fx-color: -fx-hover-base; | |
| 39 | } | |
| 40 | ||
| 41 | .tool-bar .button:armed { | |
| 42 | -fx-color: -fx-pressed-base; | |
| 43 | } | |
| 44 | ||
| 45 | /* Definition editor drag and drop target. | |
| 46 | */ | |
| 47 | .drop-target { | |
| 48 | -fx-border-color: #eea82f; | |
| 49 | -fx-border-width: 0 0 2 0; | |
| 50 | -fx-padding: 3 3 1 3 | |
| 51 | } | |
| 1 | 52 |
| 1 | /* https://toedter.com/2011/10/26/java-fx-2-0-css-styling/ | |
| 2 | */ | |
| 3 | .root { | |
| 4 | -fx-base: rgb( 50, 50, 50 ); | |
| 5 | -fx-background: -fx-base; | |
| 6 | -fx-control-inner-background: -fx-base; | |
| 7 | ||
| 8 | -fx-light-text-color: derive( -fx-base, 150% ); | |
| 9 | -fx-mid-text-color: derive( -fx-base, 100% ); | |
| 10 | -fx-dark-text-color: derive( -fx-base, 25% ); | |
| 11 | -fx-text-foreground: -fx-light-text-color; | |
| 12 | -fx-text-background: derive( -fx-control-inner-background, 7.5% ); | |
| 13 | -fx-text-selection: derive( -fx-control-inner-background, 45% ); | |
| 14 | } | |
| 15 | ||
| 16 | .glyph-icon { | |
| 17 | -fx-text-fill: -fx-light-text-color; | |
| 18 | -fx-fill: -fx-light-text-color; | |
| 19 | } | |
| 20 | ||
| 21 | .glyph-icon:hover { | |
| 22 | -fx-effect: dropshadow( three-pass-box, rgba( 0, 0, 0, 0.2 ), 4, 0, 0, 0 ); | |
| 23 | } | |
| 24 | ||
| 25 | .tab { | |
| 26 | -fx-background-color: linear-gradient( to top, -fx-base, derive( -fx-base, 30% ) ); | |
| 27 | } | |
| 28 | ||
| 29 | .menu-bar { | |
| 30 | -fx-background-color: linear-gradient( to bottom, -fx-base, derive( -fx-base, 30% ) ); | |
| 31 | } | |
| 32 | ||
| 33 | .tool-bar:horizontal { | |
| 34 | -fx-background-color: linear-gradient( to bottom, derive( -fx-base, +50% ), derive( -fx-base, -40% ), derive( -fx-base, -20% ) ); | |
| 35 | } | |
| 36 | ||
| 37 | .button { | |
| 38 | -fx-background-color: transparent; | |
| 39 | } | |
| 40 | ||
| 41 | .button:hover { | |
| 42 | -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color; | |
| 43 | -fx-color: -fx-hover-base; | |
| 44 | } | |
| 45 | ||
| 46 | .table-view { | |
| 47 | -fx-table-cell-border-color:derive( -fx-base, +10% ); | |
| 48 | -fx-table-header-border-color:derive( -fx-base, +20% ); | |
| 49 | } | |
| 50 | ||
| 51 | .split-pane:horizontal > * > .split-pane-divider { | |
| 52 | -fx-border-color: transparent -fx-base transparent -fx-base; | |
| 53 | -fx-background-color: transparent, derive( -fx-base, 20% ); | |
| 54 | -fx-background-insets: 0, 0 1 0 1; | |
| 55 | } | |
| 56 | ||
| 57 | .separator-label { | |
| 58 | -fx-text-fill: orange; | |
| 59 | } | |
| 1 | 60 |
| 1 | /* https://ethanschoonover.com/solarized | |
| 2 | */ | |
| 3 | .root { | |
| 4 | /* Solarized: base03 */ | |
| 5 | -fx-base: rgb( 0, 43, 54 ); | |
| 6 | -fx-background: -fx-base; | |
| 7 | ||
| 8 | /* Brighten controls */ | |
| 9 | -fx-color: derive( -fx-base, -40% ); | |
| 10 | ||
| 11 | -fx-control-inner-background: -fx-base; | |
| 12 | -fx-control-inner-background-alt: derive( -fx-control-inner-background, 2.5% ); | |
| 13 | ||
| 14 | /* Text colors */ | |
| 15 | /* Solarized: base0 */ | |
| 16 | -fx-light-text-color: rgb( 131, 148, 150 ); | |
| 17 | -fx-mid-text-color: derive( -fx-light-text-color, 50% ); | |
| 18 | -fx-dark-text-color: derive( -fx-light-text-color, 25% ); | |
| 19 | -fx-text-foreground: -fx-light-text-color; | |
| 20 | -fx-text-background: derive( -fx-control-inner-background, 7.5% ); | |
| 21 | -fx-text-selection: derive( -fx-control-inner-background, 45% ); | |
| 22 | ||
| 23 | -fx-mid-text-color: derive( -fx-base, 100% ); | |
| 24 | -fx-dark-text-color: derive( -fx-base, 25% ); | |
| 25 | -fx-text-foreground: -fx-light-text-color; | |
| 26 | -fx-text-background: derive( -fx-control-inner-background, 7.5% ); | |
| 27 | -fx-text-selection: derive( -fx-control-inner-background, 45% ); | |
| 28 | ||
| 29 | /* Accent colors */ | |
| 30 | -fx-accent: rgb( 38, 139, 210 ); | |
| 31 | -fx-focus-color: rgb( 253, 246, 227 ); | |
| 32 | ||
| 33 | /* Non-focused-selected elements */ | |
| 34 | -fx-selection-bar-non-focused: rgb( 0, 43, 54 ); | |
| 35 | } | |
| 36 | ||
| 37 | .glyph-icon { | |
| 38 | -fx-text-fill: -fx-light-text-color; | |
| 39 | -fx-fill: -fx-light-text-color; | |
| 40 | } | |
| 41 | ||
| 42 | .glyph-icon:hover { | |
| 43 | -fx-effect: dropshadow( three-pass-box, rgba( 0, 0, 0, 0.2 ), 4, 0, 0, 0 ); | |
| 44 | } | |
| 45 | ||
| 46 | .scroll-bar { | |
| 47 | -fx-background-color: derive( -fx-base, 45% ); | |
| 48 | } | |
| 49 | ||
| 50 | .caret { | |
| 51 | -fx-stroke: -fx-accent; | |
| 52 | } | |
| 53 | ||
| 1 | 54 |
| 1 | /* https://github.com/Col-E/Recaf/blob/master/src/main/resources/style/ui-dark.css | |
| 2 | */ | |
| 3 | .root { | |
| 4 | -fx-base: rgb( 45, 45, 46 ); | |
| 5 | -fx-background: -fx-base; | |
| 6 | ||
| 7 | /* Brighten controls */ | |
| 8 | -fx-color: derive( -fx-base, -40% ); | |
| 9 | ||
| 10 | /* Control background */ | |
| 11 | -fx-control-inner-background: rgb( 46, 46, 47 ); | |
| 12 | ||
| 13 | /* Alternative control background ( rows ) */ | |
| 14 | -fx-control-inner-background-alt: derive( -fx-control-inner-background, 2.5% ); | |
| 15 | ||
| 16 | /* Text colors */ | |
| 17 | -fx-light-text-color: rgb( 220, 220, 220 ); | |
| 18 | -fx-mid-text-color: rgb( 100, 100, 100 ); | |
| 19 | -fx-dark-text-color: rgb( 20, 20, 20 ); | |
| 20 | -fx-text-foreground: -fx-light-text-color; | |
| 21 | -fx-text-background: derive( -fx-control-inner-background, 7.5% ); | |
| 22 | -fx-text-selection: derive( -fx-control-inner-background, 45% ); | |
| 23 | ||
| 24 | /* Accent colors */ | |
| 25 | -fx-accent: rgb( 51, 51, 52 ); | |
| 26 | -fx-focus-color: rgb( 51, 51, 52 ); | |
| 27 | ||
| 28 | /* Non-focused-selected elements */ | |
| 29 | -fx-selection-bar-non-focused: rgb( 45, 45, 46 ); | |
| 30 | } | |
| 31 | ||
| 32 | .glyph-icon { | |
| 33 | -fx-text-fill: -fx-light-text-color; | |
| 34 | -fx-fill: -fx-light-text-color; | |
| 35 | } | |
| 36 | ||
| 37 | .glyph-icon:hover { | |
| 38 | -fx-effect: dropshadow( three-pass-box, rgba( 0, 0, 0, 0.2 ), 4, 0, 0, 0 ); | |
| 39 | } | |
| 40 | ||
| 41 | * { | |
| 42 | -fx-highlight-fill: rgba( 0, 180, 255, 0.4 ); | |
| 43 | } | |
| 44 | ||
| 45 | /* Scroll */ | |
| 46 | .scroll-bar { | |
| 47 | -fx-background-color: rgb( 61,61,62 ); | |
| 48 | } | |
| 49 | .scroll-bar .thumb { | |
| 50 | -fx-background-color: rgb( 91,91,92 ); | |
| 51 | -fx-background-radius: 0; | |
| 52 | } | |
| 53 | .scroll-bar .thumb:hover, | |
| 54 | .scroll-bar .thumb:pressed { | |
| 55 | -fx-background-color: rgb( 141,141,142 ); | |
| 56 | } | |
| 57 | .scroll-bar .increment-button .increment-arrow, | |
| 58 | .scroll-bar .decrement-button .decrement-arrow { | |
| 59 | -fx-background-color: rgb( 200,200,200 ); | |
| 60 | } | |
| 61 | .corner { | |
| 62 | -fx-background-color: rgb( 61,61,62 ); | |
| 63 | } | |
| 64 | ||
| 65 | /* Menu */ | |
| 66 | .menu-bar { | |
| 67 | -fx-background-color: rgb( 45, 45, 48 ); | |
| 68 | } | |
| 69 | .menu { | |
| 70 | -fx-padding: 6 14 6 14; | |
| 71 | -fx-background-insets: -1; | |
| 72 | } | |
| 73 | .menu-item { | |
| 74 | -fx-padding: 5 11 5 11; | |
| 75 | -fx-background-insets: -1; | |
| 76 | } | |
| 77 | .menu:hover { | |
| 78 | -fx-background-color: rgb( 61, 61, 62 ); | |
| 79 | } | |
| 80 | .context-menu, | |
| 81 | .menu:showing { | |
| 82 | -fx-background-color: rgb( 27, 27, 28 ); | |
| 83 | -fx-border-insets: -1; | |
| 84 | -fx-border-width: 1; | |
| 85 | -fx-border-color: black; | |
| 86 | } | |
| 87 | .context-menu { | |
| 88 | -fx-min-width: 80px; | |
| 89 | -fx-background-insets: -1; | |
| 90 | -fx-border-insets: -1; | |
| 91 | -fx-border-width: 1; | |
| 92 | -fx-border-color: black; | |
| 93 | } | |
| 94 | .context-menu .menu-item:focused { | |
| 95 | -fx-background-color: rgb( 61, 61, 62 ); | |
| 96 | } | |
| 97 | .context-menu-header { | |
| 98 | /* TODO: Find a way to disable hover coloring on the menu header */ | |
| 99 | -fx-opacity: 1.0; | |
| 100 | -fx-background-color: rgb( 24, 50, 95 ); | |
| 101 | } | |
| 102 | .context-menu-header .label { | |
| 103 | -fx-opacity: 1.0; | |
| 104 | } | |
| 105 | ||
| 106 | /* Tabs */ | |
| 107 | .tab-pane { | |
| 108 | -fx-tab-min-width: 100px; | |
| 109 | } | |
| 110 | .tab-pane *.tab-header-background { | |
| 111 | -fx-background-color: rgb( 29, 29, 31 ); | |
| 112 | -fx-border-width: 0 0 1 0; | |
| 113 | -fx-border-color: black; | |
| 114 | } | |
| 115 | .headers-region { | |
| 116 | -fx-background-color: rgb( 75, 75, 76 ); | |
| 117 | } | |
| 118 | .tab { | |
| 119 | -fx-background-color: rgb( 36,36,37 ); | |
| 120 | -fx-background-insets: 2 -1 -1 -1; | |
| 121 | -fx-background-radius: 0; | |
| 122 | -fx-padding: 2 2 1 2; | |
| 123 | -fx-border-insets: 0; | |
| 124 | -fx-border-width: 1 1 1 1; | |
| 125 | -fx-border-color: black; | |
| 126 | } | |
| 127 | .tab:selected { | |
| 128 | -fx-background-color: rgb( 45, 45, 46 ); | |
| 129 | -fx-background-insets: 2 -1 -1 -1; | |
| 130 | -fx-padding: 2; | |
| 131 | -fx-border-insets: 0; | |
| 132 | -fx-border-width: 1 1 0 1; | |
| 133 | -fx-border-color: black; | |
| 134 | } | |
| 135 | .tab:selected .focus-indicator { | |
| 136 | -fx-border-color: transparent; | |
| 137 | } | |
| 138 | ||
| 139 | /* Table */ | |
| 140 | .table-view { | |
| 141 | -fx-selection-bar: rgb( 50, 71, 77 ); | |
| 142 | -fx-selection-bar-non-focused: rgb( 46, 56, 59 ); | |
| 143 | -fx-background-color: rgb( 36,36,37 ); | |
| 144 | -fx-background-insets: 2 -1 -1 -1; | |
| 145 | -fx-background-radius: 0; | |
| 146 | -fx-padding: -1; | |
| 147 | -fx-border-width: 0 1 1 1; | |
| 148 | -fx-border-color: rgb( 22, 22, 23 ); | |
| 149 | } | |
| 150 | .table-view .filler, | |
| 151 | .table-view .show-hide-columns-button, | |
| 152 | .column-overlay { | |
| 153 | -fx-background-color: transparent; | |
| 154 | } | |
| 155 | .column-header-background { | |
| 156 | -fx-background-color: rgb( 36,36,37 ); | |
| 157 | -fx-background-insets: 2 -1 -1 -1; | |
| 158 | -fx-padding: -1; | |
| 159 | -fx-border-insets: 0; | |
| 160 | -fx-border-width: 0 1 0 1; | |
| 161 | -fx-border-color: rgb( 22, 22, 23 ); | |
| 162 | } | |
| 163 | .column-header { | |
| 164 | -fx-background-color: rgb( 45, 45, 46 ); | |
| 165 | -fx-background-insets: -1 -0 -1 0; | |
| 166 | -fx-padding: 2; | |
| 167 | -fx-border-insets: 1 -1 1 0; | |
| 168 | -fx-border-width: 1; | |
| 169 | -fx-border-color: rgb( 22, 22, 23 ); | |
| 170 | } | |
| 171 | ||
| 172 | /* Splitpane */ | |
| 173 | .split-pane-divider { | |
| 174 | -fx-background-color: black; | |
| 175 | -fx-padding: 0; | |
| 176 | -fx-background-insets: -5; | |
| 177 | } | |
| 178 | ||
| 179 | /* Tree */ | |
| 180 | .tree-table-view, | |
| 181 | .tree-view { | |
| 182 | -fx-background-color: rgb( 29, 29, 31 ); | |
| 183 | -fx-background-insets: 0; | |
| 184 | -fx-border-width: 0 1 0 0; | |
| 185 | -fx-border-color: black; | |
| 186 | } | |
| 187 | .tree-table-cell, | |
| 188 | .tree-cell { | |
| 189 | -fx-background-color: rgb( 29, 29, 31 ); | |
| 190 | } | |
| 191 | .tree-cell:selected { | |
| 192 | -fx-background-color: rgb( 44, 48, 55 ); | |
| 193 | } | |
| 194 | ||
| 195 | /* Buttons */ | |
| 196 | .box, | |
| 197 | .button, | |
| 198 | .combo-box, | |
| 199 | .slider .thumb { | |
| 200 | -fx-background-radius: 0; | |
| 201 | -fx-background-color: rgb( 63, 63, 70 ); | |
| 202 | -fx-background-insets: 0; | |
| 203 | -fx-border-width: 1; | |
| 204 | -fx-border-color: rgb( 85, 85, 85 ); | |
| 205 | } | |
| 206 | .check-box:hover .box, | |
| 207 | .button:hover, | |
| 208 | .combo-box:hover, | |
| 209 | .slider .thumb:hover { | |
| 210 | -fx-background-color: rgb( 80, 80, 85 ); | |
| 211 | -fx-border-color: rgb( 0, 122, 205 ); | |
| 212 | } | |
| 213 | .check-box:pressed .box, | |
| 214 | .button:pressed, | |
| 215 | .combo-box:pressed, | |
| 216 | .slider .thumb:pressed { | |
| 217 | -fx-background-color: rgb( 0, 122, 205 ); | |
| 218 | -fx-border-color: rgb( 0, 162, 245 ); | |
| 219 | } | |
| 220 | .combo-box:showing { | |
| 221 | -fx-background-color: rgb( 27, 27, 28 ); | |
| 222 | -fx-border-width: 1 1 0 1; | |
| 223 | -fx-border-color: black; | |
| 224 | } | |
| 225 | .combo-box .combo-box-popup .list-cell { | |
| 226 | -fx-background-color: rgb( 27, 27, 28 ); | |
| 227 | } | |
| 228 | .combo-box .combo-box-popup .list-cell:hover { | |
| 229 | -fx-background-color: rgb( 61, 61, 62 ); | |
| 230 | } | |
| 231 | .combo-box .combo-box-popup .list-view { | |
| 232 | -fx-background-color: rgb( 27, 27, 28 ); | |
| 233 | -fx-border-width: 0 1 1 1; | |
| 234 | -fx-border-color: black; | |
| 235 | } | |
| 236 | .hyperlink { | |
| 237 | -fx-text-fill: rgb( 30, 132, 250 ); | |
| 238 | } | |
| 239 | hyperlink:visited { | |
| 240 | -fx-text-fill: rgb( 98, 59, 217 ); | |
| 241 | } | |
| 242 | ||
| 243 | /* slider */ | |
| 244 | .slider .track { | |
| 245 | -fx-background-radius: 0; | |
| 246 | -fx-background-color: rgb( 29, 29, 31 ); | |
| 247 | -fx-background-insets: 0; | |
| 248 | -fx-border-width: 1; | |
| 249 | -fx-border-color: rgb( 65, 65, 65 ); | |
| 250 | } | |
| 251 | .slider .thumb { | |
| 252 | -fx-padding: 5; | |
| 253 | } | |
| 254 | .axis-tick-mark { | |
| 255 | -fx-stroke: rgb( 100, 100, 100 ); | |
| 256 | } | |
| 257 | ||
| 258 | /* Text */ | |
| 259 | .text-area .content, | |
| 260 | .text-field { | |
| 261 | -fx-background-radius: 0; | |
| 262 | -fx-background-color: rgb( 63, 63, 70 ); | |
| 263 | -fx-background-insets: 0; | |
| 264 | -fx-border-width: 1; | |
| 265 | -fx-border-color: rgb( 85, 85, 85 ); | |
| 266 | } | |
| 267 | .text-area { | |
| 268 | -fx-background-radius: 0; | |
| 269 | -fx-background-color: rgb( 63, 63, 70 ); | |
| 270 | -fx-background-insets: 0; | |
| 271 | -fx-border-width: 1; | |
| 272 | -fx-border-color: rgb( 85, 85, 85 ); | |
| 273 | } | |
| 274 | .text-area .content { | |
| 275 | -fx-border-width: 0; | |
| 276 | } | |
| 277 | ||
| 278 | /* Popup */ | |
| 279 | .tooltip { | |
| 280 | -fx-background-radius: 0; | |
| 281 | -fx-background-color: rgb( 40, 40, 42 ); | |
| 282 | -fx-background-insets: 0; | |
| 283 | -fx-border-width: 1; | |
| 284 | -fx-border-color: rgb( 70, 70, 72 ); | |
| 285 | } | |
| 1 | 286 |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M56.633 44.984V63.27c0 .363-.367.73-.735.73H1.102c-.368 0-.735-.367-.735-.73V61.62c0-.363.367-.73.735-.73h2.39l28.5-28.344L2.391 3.109c-.184-.183-.184-.367-.184-.55V.73c0-.363.367-.73.734-.73h52.957c.368 0 .735.367.735.73v18.106c0 .363-.367.73-.735.73h-2.023c-.367 0-.734-.367-.734-.73 0-7.684-4.598-14.082-12.688-14.082H19.121l24.09 24.137c.367.367.367.73 0 1.097L19.676 53.395h20.777c5.516 0 10.297-3.473 12.133-8.594.184-.367.367-.551.738-.551h2.758c.367 0 .55.367.55.734zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M66.824 40.465c-.176 1.59-2.097 2.824-5.945 3.707-3.852.879-8.75 1.41-14.52 1.41h-4.898v9.172c1.574.176 3.324.176 4.898.176 5.77 0 10.668-.528 14.52-1.586 3.848-1.059 5.945-2.293 6.121-3.707-.176-.352-.176-9.172-.176-9.172zm-20.64-6.7c-1.75 0-3.325 0-4.899-.18v9.352h4.899c5.773 0 10.671-.53 14.52-1.59s5.944-2.292 5.944-3.702v-8.997c-.171 1.586-2.097 2.825-6.12 3.704-3.673 1.058-8.571 1.59-14.344 1.414zm0-11.468c-1.75 0-3.325 0-4.899-.176v9.352c1.574.175 3.324.175 4.899.175 5.773 0 10.671-.53 14.695-1.59C64.727 29 66.824 27.767 67 26.356V17.36c-.176 1.59-2.098 2.825-6.121 3.704-4.024.707-8.922 1.234-14.695 1.234zm0-13.05c-1.75 0-3.325 0-4.899.175v10.406c1.574.176 3.324.176 4.899.176 5.773 0 10.671-.527 14.695-1.586 3.848-1.059 5.945-2.293 6.121-3.703-.176-1.59-2.098-2.824-6.121-3.883-4.024-1.055-8.922-1.41-14.695-1.586zM18.02 23.886c-.176.527-.528 2.293-1.227 5.293l-1.223 5.113h5.07l-1.222-5.113c-.7-3-1.227-4.766-1.227-5.293zM0 7.129v49.918l37.785 6.527V.426zm23.09 37.219-1.399-5.645-6.996-.176-1.398 5.29-4.375-.352 6.648-23.813 5.07-.351 7.348 25.222zm0 0" fill="#a03537"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M66.824 40.465c-.176 1.59-2.097 2.824-5.945 3.707-3.852.879-8.75 1.41-14.52 1.41h-4.898v9.172c1.574.176 3.324.176 4.898.176 5.77 0 10.668-.528 14.52-1.586 3.848-1.059 5.945-2.293 6.121-3.707-.176-.352-.176-9.172-.176-9.172zm-20.64-6.7c-1.75 0-3.325 0-4.899-.18v9.352h4.899c5.773 0 10.671-.53 14.52-1.59s5.944-2.292 5.944-3.702v-8.997c-.171 1.586-2.097 2.825-6.12 3.704-3.673 1.058-8.571 1.59-14.344 1.414zm0-11.468c-1.75 0-3.325 0-4.899-.176v9.352c1.574.175 3.324.175 4.899.175 5.773 0 10.671-.53 14.695-1.59C64.727 29 66.824 27.767 67 26.356V17.36c-.176 1.59-2.098 2.825-6.121 3.704-4.024.707-8.922 1.234-14.695 1.234zm0-13.05c-1.75 0-3.325 0-4.899.175v10.406c1.574.176 3.324.176 4.899.176 5.773 0 10.671-.527 14.695-1.586 3.848-1.059 5.945-2.293 6.121-3.703-.176-1.59-2.098-2.824-6.121-3.883-4.024-1.055-8.922-1.41-14.695-1.586zM18.02 23.886c-.176.527-.528 2.293-1.227 5.293l-1.223 5.113h5.07l-1.222-5.113c-.7-3-1.227-4.766-1.227-5.293zM0 7.129v49.918l37.785 6.527V.426zm23.09 37.219-1.399-5.645-6.996-.176-1.398 5.29-4.375-.352 6.648-23.813 5.07-.351 7.348 25.222zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M66.824 40.465c-.176 1.59-2.097 2.824-5.945 3.707-3.852.879-8.75 1.41-14.52 1.41h-4.898v9.172c1.574.176 3.324.176 4.898.176 5.77 0 10.668-.528 14.52-1.586 3.848-1.059 5.945-2.293 6.121-3.707-.176-.352-.176-9.172-.176-9.172zm-20.64-6.7c-1.75 0-3.325 0-4.899-.18v9.352h4.899c5.773 0 10.671-.53 14.52-1.59s5.944-2.292 5.944-3.702v-8.997c-.171 1.586-2.097 2.825-6.12 3.704-3.673 1.058-8.571 1.59-14.344 1.414zm0-11.468c-1.75 0-3.325 0-4.899-.176v9.352c1.574.175 3.324.175 4.899.175 5.773 0 10.671-.53 14.695-1.59C64.727 29 66.824 27.767 67 26.356V17.36c-.176 1.59-2.098 2.825-6.121 3.704-4.024.707-8.922 1.234-14.695 1.234zm0-13.05c-1.75 0-3.325 0-4.899.175v10.406c1.574.176 3.324.176 4.899.176 5.773 0 10.671-.527 14.695-1.586 3.848-1.059 5.945-2.293 6.121-3.703-.176-1.59-2.098-2.824-6.121-3.883-4.024-1.055-8.922-1.41-14.695-1.586zM18.02 23.886c-.176.527-.528 2.293-1.227 5.293l-1.223 5.113h5.07l-1.222-5.113c-.7-3-1.227-4.766-1.227-5.293zM0 7.129v49.918l37.785 6.527V.426zm23.09 37.219-1.399-5.645-6.996-.176-1.398 5.29-4.375-.352 6.648-23.813 5.07-.351 7.348 25.222zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="66" xmlns="http://www.w3.org/2000/svg"><path d="M40.266 62.762 34.922 45.55h-19.98l-5.34 17.21h-7.32L21.468 5.59h7.32l19.39 57.172zM26.813 19.438c-.594-2.176-1.387-5.145-1.583-7.32h-.199c-.394 1.98-.988 4.945-1.781 7.32L16.523 41H33.54zm33.039-9.891c-2.375 0-4.356-1.781-4.356-4.156s1.98-4.153 4.356-4.153c2.37 0 4.351 1.778 4.351 4.153 0 2.375-1.98 4.156-4.351 4.156zm-3.563 53.215V18.05h7.32v44.71zm0 0" fill="#fea500" stroke="#fea500" stroke-miterlimit="10" stroke-width="2.47295"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="66" xmlns="http://www.w3.org/2000/svg"><path d="M40.266 62.762 34.922 45.55h-19.98l-5.34 17.21h-7.32L21.468 5.59h7.32l19.39 57.172zM26.813 19.438c-.594-2.176-1.387-5.145-1.583-7.32h-.199c-.394 1.98-.988 4.945-1.781 7.32L16.523 41H33.54zm33.039-9.891c-2.375 0-4.356-1.781-4.356-4.156s1.98-4.153 4.356-4.153c2.37 0 4.351 1.778 4.351 4.153 0 2.375-1.98 4.156-4.351 4.156zm-3.563 53.215V18.05h7.32v44.71zm0 0" fill="#fea500" stroke="#fea500" stroke-miterlimit="10" stroke-width="2.47295"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="53" xmlns="http://www.w3.org/2000/svg"><path d="M49.023.375H3.977C1.703.375 0 2.07 0 4.328 0 6.59 1.703 8.281 3.977 8.281h.187v3.953c0 8.286 5.11 15.625 12.684 18.637.379.188.757.563.757.942v.375c0 .378-.378.753-.757.94C9.086 36.142 3.977 43.48 4.164 51.767v3.953h-.187C1.703 55.719 0 57.41 0 59.672c0 2.258 1.703 3.953 3.977 3.953h45.046c2.274 0 3.977-1.695 3.977-3.953 0-2.262-1.703-3.953-3.977-3.953h-.187v-3.953c0-8.286-5.11-15.625-12.684-18.637-.379-.188-.757-.563-.757-.941v-.376c0-.378.378-.753.757-.94 7.762-3.013 12.871-10.352 12.684-18.638V8.281h.187C51.297 8.281 53 6.59 53 4.328 53 2.07 51.297.375 49.023.375zm-5.488 11.86c0 6.023-3.785 11.484-9.465 13.742-2.46.941-4.164 3.199-4.164 5.836v.375c0 2.636 1.703 4.894 4.164 5.835 5.68 2.258 9.465 7.72 9.465 13.743v3.953H9.465v-3.953c0-6.024 3.785-11.485 9.465-13.743 2.46-.941 4.164-3.199 4.164-5.836v-.374c0-2.637-1.703-4.895-4.164-5.836-5.68-2.258-9.465-7.72-9.465-13.743V8.281h34.07zm-28.77 6.777c-.378-.567-.19-1.317.38-1.696.187-.187.375-.187.753-.187H37.29c.566 0 1.137.566 1.137 1.129 0 .187 0 .566-.192.754-1.324 1.883-3.027 3.199-5.109 3.953-2.27.754-3.977 2.445-5.11 4.515-.378.754-1.328 1.133-2.081.567-.192-.188-.57-.375-.57-.567-1.134-2.07-2.84-3.761-5.11-4.515-2.274-.942-4.164-2.258-5.488-3.953zM29.907 42.73a6.64 6.64 0 0 0 4.164 1.504c2.84 0 5.301 1.883 5.868 4.707v.188c.19.566.19 1.129.19 1.883s-.565 1.316-1.323 1.316H14.008c-.758 0-1.324-.562-1.324-1.316 0-.567.187-1.317.187-1.883v-.188c.758-2.636 3.219-4.52 6.059-4.52a6.64 6.64 0 0 0 4.164-1.503c.758-.754 1.511-1.508 1.89-2.45.38-.562.95-.937 1.703-.75.57.188.95.376 1.137.75.57.755 1.137 1.509 2.082 2.262zm0 0" fill="#8ed200"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M10.867 48.152c0 1.551 1.04 2.582 2.602 2.582h2.773v9.305c0 2.066 1.91 3.961 3.992 3.961s3.989-1.895 3.989-3.96v-9.306h5.379v9.305c0 2.066 1.91 3.961 3.992 3.961s3.988-1.895 3.988-3.96v-9.306h2.602c1.562 0 2.605-1.03 2.605-2.757V21.449H10.867zM4.097 21.45C2.017 21.45.11 23.344.11 25.41v18.606c0 2.066 1.907 3.96 3.989 3.96s3.992-1.894 3.992-3.96V25.41c0-2.066-1.735-3.96-3.992-3.96zm45.805 0c-2.082 0-3.992 1.895-3.992 3.961v18.606c0 2.066 1.91 3.96 3.992 3.96s3.989-1.894 3.989-3.96V25.41c0-2.066-1.907-3.96-3.989-3.96zM36.367 5.945l3.473-3.449c.52-.516.52-1.375 0-1.894a1.373 1.373 0 0 0-1.91 0L33.94 4.566c-1.91-1.379-4.34-1.894-6.941-1.894-2.777 0-5.031.515-7.285 1.55L15.898.259c-.523-.344-1.562-.344-2.082 0-.347.515-.347 1.55 0 2.066l3.47 3.446c-3.817 2.93-6.419 7.41-6.419 12.75h32.266c0-5.168-2.602-9.82-6.766-12.575zm-14.746 7.407h-2.773v-2.586h2.773zm13.531 0H32.38v-2.586h2.773zm0 0" fill="#a4ca39"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.887 38.266c-2.684-.84-4.532-3.36-4.532-6.215 0-2.852 1.848-5.371 4.532-6.211.84-.336 1.343-1.172 1.008-2.012-.84-3.023-1.848-5.707-3.524-8.394-.504-.84-1.344-1.008-2.184-.672-1.007.504-2.015.84-3.19.84-3.692 0-6.548-3.024-6.548-6.547 0-1.176.336-2.184.84-3.188.504-.84.168-1.68-.672-2.183a40.47 40.47 0 0 0-8.39-3.528c-.84-.168-1.68.168-2.016 1.008C37.37 3.852 34.855 5.7 32 5.7s-5.371-1.847-6.21-4.535C25.452.324 24.612-.18 23.772.156c-3.02.84-5.707 1.848-8.39 3.528-.84.503-1.008 1.343-.672 2.183.504 1.004.84 2.012.84 3.188 0 3.691-3.024 6.547-6.547 6.547-1.176 0-2.184-.336-3.191-.84-.84-.504-1.68-.168-2.184.672a40.699 40.699 0 0 0-3.524 8.394c-.167.84.168 1.676 1.008 2.012 2.684.84 4.532 3.36 4.532 6.21 0 2.856-1.848 5.376-4.532 6.216-.84.332-1.343 1.172-1.008 2.011.84 3.024 1.848 5.707 3.524 8.395.504.84 1.344 1.008 2.184.672 1.007-.504 2.015-.84 3.19-.84 3.692 0 6.548 3.02 6.548 6.547 0 1.176-.336 2.183-.84 3.187-.504.84-.168 1.68.672 2.184a40.47 40.47 0 0 0 8.39 3.527h.336c.672 0 1.344-.504 1.512-1.176.84-2.687 3.356-4.535 6.211-4.535s5.371 1.848 6.211 4.535c.336.84 1.176 1.344 2.016 1.008 3.02-.84 5.707-1.847 8.39-3.527.84-.504 1.008-1.344.672-2.184-.504-1.004-.84-2.011-.84-3.187 0-3.692 3.024-6.547 6.547-6.547 1.176 0 2.184.336 3.192.84.84.504 1.68.168 2.183-.672a40.698 40.698 0 0 0 3.524-8.395c.503-.672 0-1.511-.84-1.843zm-30.719 3.691c-5.371 0-9.902-4.363-9.902-9.906 0-5.371 4.363-9.903 9.902-9.903 5.371 0 9.902 4.364 9.902 9.903 0 5.375-4.53 9.906-9.902 9.906zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="68" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M51.6 33H28.3c-3.2 0-5.9 2.601-5.9 5.9v26.3h-5.9c0 3.2 2.599 5.9 5.9 5.9h23.4c3.2 0 5.9-2.601 5.9-5.9V41.7h5.9v-2.9a5.98 5.98 0 0 0-6-5.8zm-2.9 31.6c0 1.9-1.5 3.4-3.4 3.4H23.9c1.399-1 1.399-2.9 1.399-2.9V38.9c0-1.6 1.3-2.9 2.902-2.9 1.598 0 2.899 1.3 2.899 2.9v2.901h17.6zM34.1 38.9V36h17.6c2.7 0 2.9 1.7 2.9 2.9zm0 0" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.62485 0 0 1.6469 -26.2 -53.722)"/><path d="M28.719 46.082c-.489-.332-.973-.824-1.461-1.32-.488-.492-.813-1.153-1.137-1.645-.812-1.316-1.625-2.637-1.953-4.117-.484-1.648-.813-3.293-.813-4.941 0-1.813.329-3.293 1.141-4.61.484-.988 1.297-1.976 2.438-2.472a5.777 5.777 0 0 1 3.246-.989c.328 0 .812 0 1.3.164.325.164.653.164 1.137.496.653.164.977.329 1.14.493.325.164.65.164.973.164.165 0 .489 0 .653-.164.16 0 .484-.164.972-.328s.813-.329 1.137-.329c.488-.168.813-.168 1.137-.332.488 0 .812-.164 1.3 0 .813 0 1.466.164 2.278.496 1.137.493 2.11 1.153 2.762 2.305-.324.164-.489.328-.813.66-.488.492-.976 1.153-1.465 1.645-.484.824-.812 1.812-.648 2.965 0 1.316.324 2.304.977 3.293.484.66.972 1.32 1.785 1.812l.976.496c-.164.492-.324.82-.488 1.317-.324.988-.813 1.812-1.461 2.632-.488.66-.812 1.32-1.14 1.649l-1.297 1.316a3.095 3.095 0 0 1-1.625.496c-.329 0-.813 0-1.141-.164-.324-.164-.649-.164-.973-.332-.324-.164-.648-.328-.976-.328-.485-.164-.813-.164-1.297-.164-.488 0-.977 0-1.301.164-.324.164-.652.164-.977.328-.488.168-.812.332-.972.332-.328.164-.813.164-1.14.164-1.298-.66-1.786-.824-2.274-1.152zm7.636-21.246c-.812.328-1.46.492-2.273.492-.164-.656 0-1.48.324-2.305.324-.656.649-1.316 1.137-1.976a5.093 5.093 0 0 1 1.789-1.48c.813-.329 1.461-.66 2.11-.66.163.823 0 1.484-.325 2.304-.324.66-.648 1.484-1.137 1.977-.324.824-.812 1.32-1.625 1.648zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M0 61.766V2.234C0 1.004 1 0 2.223 0h53.332c.668 0 1.222.223 1.668.781l22.222 24.922c.332.445.555.89.555 1.45v34.613C80 62.996 79 64 77.777 64H2.223A2.234 2.234 0 0 1 0 61.766zm75.555-33.72-21-23.577H4.445V59.53h71.11zm0 0"/><path d="M53.332 29.055V4.469c0-1.227 1-2.235 2.223-2.235a2.236 2.236 0 0 1 2.222 2.235V26.82h17.778a2.234 2.234 0 0 1 0 4.47h-20a2.236 2.236 0 0 1-2.223-2.235zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#c33"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="68" xmlns="http://www.w3.org/2000/svg"><path d="M34 0c-7.133 0-13.672 1.98-18.625 5.547S7.051 14.266 7.051 20.21v8.719C3.09 31.902.316 38.242.316 45.574.316 55.68 5.664 64 12.203 64c1.586 0 3.371-.594 4.953-1.586V28.73c-.988-.593-2.18-1.187-3.367-1.386V20.21c0-3.566 1.785-6.738 5.352-9.113C22.707 8.52 28.055 6.738 34 6.738s11.098 1.782 14.86 4.36c3.566 2.574 5.35 5.746 5.35 9.113v6.934c-1.187.398-2.378.793-3.366 1.386v33.29c1.582.992 3.367 1.585 4.953 1.585 6.34 0 11.887-8.324 11.887-18.43 0-6.933-2.774-13.273-6.735-16.246v-8.52c0-5.944-3.37-11.292-8.324-14.663C47.672 2.18 41.133 0 34 0zm0 29.723-7.332 14.465-4.555-5.946-1.586 2.38v11.093l1.586-1.98 5.746 7.331L34 44.582l6.14 12.484 5.747-7.332 1.586 1.98V40.622l-1.586-2.379-4.356 6.14zm0 0" fill="#1493f6"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M34.625 63.98c14.355-12.652 1.95-28.816-1.773-30.574.53 1.934.355 5.27-1.594 7.203-.887-4.918-4.785-11.07-10.278-13.18.883 7.032-3.19 11.95-4.964 14.41-1.418 2.286-9.922 14.06-1.418 22.141-20.38-6.328-15.239-26.535-9.391-35.496C11.41 19.172 18.676 11.617 17.078.02c9.926 3.515 16.66 13.882 18.434 21.789 3.367-3.164 3.898-8.786 3.011-11.95 6.914 2.813 28.536 41.47-3.898 54.121zm0 0" fill="#ff9800"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M0 0v36.57h13.715V13.715H36.57V0zm0 0"/><path d="M18.285 18.285V64H64V18.285zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M64 0H0v64h64zM12.8 12.633H6.399V6.23h6.403zm44.802 0h-38.57V6.23h38.57zm0 44.797H6.23V19.2h51.372zm0 0"/><path d="m16.336 24.59-4.547 4.547 7.41 7.41-7.41 7.242 4.547 4.547 11.957-11.79zm10.613 21.558h12.797v6.399H26.95zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M17.8.5c-2.9 0-5.4 2.801-5.4 6.2 0 3.4 2.4 6.2 5.4 6.2 2.9 0 5.399-2.8 5.399-6.2C23.199 3.302 20.8.5 17.8.5zm0 10.1c-1.6 0-3-1.7-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9-1.3 3.9-3 3.9zM7 11.8V1.7C7 1 6.5.5 5.8.5S4.6 1 4.6 1.7v10.1c0 .7.5 1.2 1.2 1.2S7 12.4 7 11.8zm-1.1 6.9C3 18.7.5 21.5.5 24.9s2.4 6.2 5.4 6.2 5.401-2.8 5.401-6.2c-.102-3.3-2.5-6.2-5.4-6.2zm0 10.2c-1.6 0-3-1.699-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9c-.1 2.1-1.4 3.9-3 3.9zM19 30V19.9c0-.7-.5-1.2-1.2-1.2s-1.2.5-1.2 1.2V30c0 .7.5 1.2 1.2 1.2S19 30.7 19 30zM31.3 12.7V2.6c0-.7-.499-1.2-1.2-1.2-.7 0-1.1.5-1.1 1.2v10.099c0 .701.5 1.2 1.2 1.2s1.1-.6 1.1-1.2zm-1.2 6.9c-2.9 0-5.401 2.8-5.401 6.2 0 3.4 2.4 6.202 5.4 6.202 2.901 0 5.402-2.802 5.402-6.202S33.1 19.6 30.1 19.6zm0 10.102c-1.6 0-3-1.7-3-3.902 0-2.099 1.3-3.9 3-3.9s3 1.7 3 3.9c0 2.202-1.3 3.902-3 3.902zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" transform="matrix(1.91667 0 0 1.9394 0 .485)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M0 61.766V2.234C0 1.004 1 0 2.223 0h53.332c.668 0 1.222.223 1.668.781l22.222 24.922c.332.445.555.89.555 1.45v34.613C80 62.996 79 64 77.777 64H2.223A2.234 2.234 0 0 1 0 61.766zm75.555-33.72-21-23.577H4.445V59.53h71.11zm0 0"/><path d="M53.332 29.055V4.469c0-1.227 1-2.235 2.223-2.235a2.236 2.236 0 0 1 2.222 2.235V26.82h17.778a2.234 2.234 0 0 1 0 4.47h-20a2.236 2.236 0 0 1-2.223-2.235zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="c"><g filter="url(#a)"><path d="M0 0h72v64H0z" fill-opacity=".65"/></g></mask><clipPath id="b"><path d="M0 0h72v64H0z"/></clipPath><g clip-path="url(#b)" mask="url(#c)"><path d="M25.281.082h4.356l.168.168c1.34.34 2.843.508 4.183 1.18 4.524 1.855 7.203 5.39 8.875 9.937 0 .168.168.508.336.676 1.004.336 2.008.504 3.016.84l.164-.168c2.68-5.39 7.035-8.422 12.894-9.43 1.508-.34 3.016-.172 4.524.336-.168.168-.168.336-.336.336-2.012 2.191-3.516 4.547-3.852 7.41-.168 1.012-.336 1.852-.504 2.863-.5 2.528-1.34 5.055-3.347 6.907C53.246 23.497 50.062 24 46.883 24c-.168 0-.504-.168-.504-.504-.332-.508-.5-1.012-.668-1.687-2.176.507-2.848 1.011-3.016 2.863.672 0 1.34.172 2.012.172 5.691.672 11.387 1.515 16.91 2.691 2.68.676 5.192 1.348 7.703 2.528 1.34.675 2.012 1.683 2.344 3.199 0 .34.168.675.336.843v2.02c-.168.168-.168.508-.336.676-.332 1.011-.836 1.851-1.672 2.527-1.008.84-2.18 1.176-3.52 1.348.337 1.683-.667 2.691-1.84 3.367-.835.504-1.675.84-2.51 1.012-1.34.168-2.513 0-3.852 0-1.004 1.851-2.68 2.187-4.52 2.355-1.844.168-3.52-.336-5.023-1.347-.672.843-1.34 1.347-2.512 1.347-2.012.336-3.684-.168-5.36-.84l-.167-.168.167.168c1.172 2.188 2.344 4.211 3.348 6.399.336.676.336 1.683.336 2.36 0 1.515-.836 2.523-2.008 2.862-.168 1.18-.84 2.188-1.676 2.864-1.171.84-2.511 1.007-4.02 1.007-.167 0-.667-.167-.667 0-.672 1.012-1.844 1.348-2.848 1.856h-1.507c-1.004-.168-1.84-1.18-3.016-.34-.164.172-.5.172-.668.172-1.172 0-3.348-.676-3.852-2.36s-1.34-3.538-2.007-5.222l-.168.168c-1.508 3.035-4.856 4.21-8.04 2.695a14.392 14.392 0 0 1-4.351-3.367C1.67 46.402-1.508 35.621.668 23.496 2.676 12.043 11.551 3.286 22.77.926 23.44.422 24.445.25 25.28.082zm13.73 19.035c1.676.168 3.52.168 5.192-.676 1.508-.671 3.184-.671 4.688-.504 1.007.168 2.18.504 3.183.672-.504-.504-1.004-1.011-1.676-1.347-2.68-2.02-5.691-3.367-9.207-3.703-.336 0-.84 0-.84.168-1.003 1.515-1.671 3.03-1.671 4.882-.168 1.18.164 2.36.668 3.371 1.843-.843 3.851-1.687 5.691-2.527v-.508c-1.004.172-1.84.508-2.844.676-1.172.168-2.343.336-3.183-.504zM56.427 7.664c-2.176 2.356-3.684 5.05-5.024 7.746l3.684 3.2c1.176-1.516 1.508-3.2 2.012-4.883.668-3.032.836-6.235 3.18-8.59-4.352-.168-10.715 4.379-12.055 8.422l2.175 1.175c.168-.168.168-.504.336-.671.668-1.18 1.34-2.36 2.176-3.368 1.004-1.18 2.012-2.36 3.516-3.03zm-7.867 24.59c1.34.168 2.68.336 4.015.336l12.395 1.347c.668 0 1.172.168 1.84.168-.336-1.011-.836-1.851-1.676-2.19-.836-.337-1.84-.505-2.676-.84-3.684-.677-7.203-1.516-10.887-2.192-3.515-.504-7.199-.84-10.715-1.348-.167 0-.503 0-.671.172l-2.008 2.02c-3.684 2.695-7.703 3.367-12.055 3.367a25.933 25.933 0 0 0 3.348-1.18c3.515-1.344 6.531-3.2 8.707-6.23.336-.504.336-.84.168-1.348a12.356 12.356 0 0 1-1.508-5.895c0-2.527.836-4.714 2.68-6.566.336-.34.336-.676.168-1.012-1.172-2.695-2.848-4.715-5.36-6.234-3.015-1.852-6.363-2.02-9.543-1.516-7.535 1.18-13.23 5.055-17.25 11.285-3.683 5.895-4.855 12.297-4.015 19.204.332 2.187.836 4.21 1.84 6.062.167.504.335 1.012.671 1.516 4.52 8.254 11.551 12.968 20.93 13.64 5.527.34 10.383-2.695 12.055-7.914.336-1.18.84-2.36.168-3.539 1.172 1.012 2.68 1.516 4.355 1.688 1.672.168 3.012-.34 3.684-1.688.168 0 .332.172.5.172 1.34.672 2.68.84 4.187.672 1.844-.168 2.68-1.012 2.848-2.695.836.168 1.84.336 2.68.168 2.675 0 3.851-1.516 3.347-3.704 1.004 0 2.176 0 3.18-.167 1.008-.172 2.012-.676 2.68-1.856l-10.047-2.02-10.047-2.023c-.332.34 2.012.34 2.012.34zm-1.676-13.645c.336 1.012.504 1.852.668 2.696.168.504.336.504.84.504 1.672-.168 3.18-.504 4.52-1.176.167-.172.335-.172.839-.508l-4.02-1.008-.503.84h-.168v-1.012zm0 0" fill="#543828"/><path d="M5.02 40c-1.004-1.852-1.504-4.043-1.84-6.063-1.004-6.906.168-13.304 4.02-19.203 4.015-6.398 9.878-10.273 17.245-11.28 3.348-.509 6.532-.34 9.543 1.515 2.512 1.515 4.188 3.535 5.36 6.23.168.336.168.676-.168 1.012-1.672 1.851-2.512 4.043-2.68 6.566 0 2.024.504 4.043 1.508 5.895.336.508.168.844-.168 1.348-2.176 3.03-5.192 5.054-8.707 6.234-1.004.336-2.176.672-3.348 1.176 4.352 0 8.54-.672 12.055-3.368-.836 1.348-1.34 2.696-2.344 3.875-4.351 5.727-10.047 8.254-17.414 6.231-.668-.168-1.34-.336-2.008.336l-1.008.508c-3.18.84-6.695.504-10.046-1.012zm22.605-29.813c-3.516 0-6.195 2.696-6.195 6.403 0 3.367 2.847 6.23 6.363 6.23s6.195-2.863 6.195-6.398c0-3.54-2.843-6.402-6.363-6.235zm0 0" fill="#e95927"/><path d="M5.02 40c3.351 1.516 6.867 1.684 10.382.676.336-.172.836-.172 1.004-.508.672-.504 1.34-.504 2.012-.336 7.367 2.02 13.059-.504 17.414-6.23.836-1.18 1.508-2.528 2.344-3.875l2.008-2.02c.168-.172.504-.172.671-.172 3.516.508 7.2.844 10.715 1.348 3.684.676 7.203 1.347 10.887 2.191-.168 0-.336.168-.336.168-4.523.336-9.043.672-13.394 1.012h-2.348l10.047 2.02c-.164.335-.164 1.011-.332 1.18-.504.503-1.172 1.01-1.844 1.179-.836.168-1.676 0-2.512 0 .168.84 0 1.683-.668 2.355-.504.34-1.007.844-1.675 1.012-1.508.676-3.016.676-4.52-.168-.336 1.516-1.676 2.02-2.848 2.188-1.34.171-2.511 0-3.683-.84l-.168.168 1.004 2.359c.672 1.176.168 2.355-.168 3.535-1.672 5.223-6.528 8.422-12.055 7.918-9.379-.676-16.41-5.39-20.93-13.644-.672-.504-.84-1.012-1.007-1.516zm0 0" fill="#fbcd00"/><path d="M51.402 36.8c.84 0 1.844.169 2.512 0 .672-.167 1.34-.675 1.844-1.179.336-.168.336-.844.336-1.18l10.047 2.024c-.672 1.008-1.508 1.683-2.68 1.851-1.004.168-2.176.168-3.184.168.504 2.192-.668 3.707-3.347 3.707-.836 0-1.844-.171-2.68-.171-.168 1.687-1.004 2.527-2.848 2.695-1.504.168-2.843 0-4.183-.672 0-.336-.168-.336-.336-.336-.668 1.348-2.008 1.852-3.684 1.684s-3.015-.676-4.355-1.684l-1.004-2.36.168-.167c1.172.84 2.344 1.18 3.683.84 1.34-.168 2.512-.672 2.848-2.188 1.508.844 3.012.844 4.52.168.504-.168 1.171-.672 1.675-1.012.668-.504.836-1.347.668-2.187zm0 0" fill="#3daf00"/><path d="M56.426 7.664c-1.504.672-2.676 1.852-3.684 3.2-.836 1.011-1.504 2.187-2.176 3.366-.168.168-.168.336-.336.676l-2.175-1.18c1.34-4.21 7.703-8.59 12.054-8.421-2.343 2.527-2.343 5.726-3.18 8.59-.335 1.683-.835 3.367-2.007 4.882l-3.684-3.199c1.504-2.863 3.18-5.558 5.188-7.914.336-.172 0 0 0 0zm0 0" fill="#25a7f0"/><path d="M38.68 18.777c0-1.851.5-3.535 1.672-4.882.168-.168.671-.168.84-.168 3.515.168 6.359 1.683 9.206 3.703.504.34 1.172.843 1.676 1.347-1.004-.168-2.176-.504-3.183-.672-1.672-.335-3.18-.167-4.688.504-1.672.676-3.348.676-5.191.676zm0 0" fill="#cbcbca"/><path d="M48.559 32.254c4.52-.34 9.039-.676 13.394-1.012.168 0 .336 0 .336-.168.836.336 1.84.504 2.68.84 1.004.34 1.34 1.18 1.672 2.191-.668 0-1.34-.167-1.84-.167l-12.227-1.18zm0 0" fill="#3eae00"/><path d="m46.883 18.61 2.176.335v1.012h.168l.503-1.012 4.02 1.012c-.336.168-.504.336-.84.504-1.508.676-3.012 1.012-4.52 1.18-.335 0-.671 0-.84-.504-.163-.676-.5-1.516-.667-2.528zm0 0" fill="#25a5ec"/><path d="m38.68 18.777.164.34c1.008.84 2.18.84 3.183.672 1.004-.168 1.84-.504 2.848-.672v.504l-5.695 2.527c-.168-1.011-.5-2.191-.5-3.37zm0 0" fill="#c9c8c7"/><path d="M27.625 10.188c3.52 0 6.363 2.695 6.363 6.234 0 3.535-2.843 6.398-6.195 6.398-3.348 0-6.363-2.863-6.363-6.23 0-3.707 2.68-6.403 6.195-6.403zm3.684 6.234c0-2.192-1.672-3.875-3.684-3.875-2.008 0-3.684 1.683-3.852 3.875 0 2.02 1.676 3.871 3.688 3.871 2.344-.168 3.848-1.684 3.848-3.871zm0 0" fill="#fbcb00"/><path d="M56.426 7.664c.168.168 0 0 0 0zm0 0" fill="#543828"/><path d="M31.309 16.422c0 2.187-1.672 3.703-3.848 3.703-2.18 0-3.852-1.684-3.688-3.871 0-2.024 1.676-3.875 3.852-3.875 2.18.168 3.684 1.851 3.684 4.043zm-3.684-.168a8.37 8.37 0 0 0 1.844-.676c.668-.336.668-1.18 0-1.683-.836-.676-2.68-.676-3.516 0-.672.503-.672 1.347 0 1.683.5.336 1.004.504 1.672.676zm0 0" fill="#553928"/><path d="M27.625 16.254c-.668-.172-1.172-.34-1.672-.676-.672-.336-.672-1.18 0-1.683.836-.676 2.68-.676 3.516 0 .668.503.668 1.347 0 1.683a8.37 8.37 0 0 1-1.844.676zm0 0" fill="#fff"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M47.734 32c8.914 12.195 3.325 28-11.785 31.46C28.695 52.927 40.33 43.59 47.734 32zM8.45 60.3l.453.302a18.118 18.118 0 0 0 9.215 3.011c-2.867-5.87-2.265-12.945 1.363-18.367l10.575-15.355c5.742-8.578 3.476-20.32-5.137-26.043l-.605-.45A18.106 18.106 0 0 0 15.098.387c2.87 6.02 2.265 12.945-1.36 18.367L3.16 34.109c-5.742 8.73-3.472 20.473 5.29 26.192zm27.047-17.76s3.02-4.067 4.531-6.474l4.383-6.476c5.438-7.977-4.082-14.3-5.289-14.45 1.207 2.407 0 7.376-1.512 9.786l-4.382 6.472c-2.414 3.762-1.508 8.73 2.27 11.141zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#4d1b9b"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M5.102 59.219v-10.11h11.605v10.11zm14.066 0v-10.11h12.836v10.11zM5.102 46.805V35.457h11.605v11.348zm14.066 0V35.457h12.836v11.348zM5.102 33.152V23.047h11.605v10.105zM34.645 59.22v-10.11H47.48v10.11zM19.168 33.152V23.047h12.836v10.105zm30.95 26.067v-10.11h11.605v10.11zM34.644 46.805V35.457H47.48v11.348zm-14.07-30.496c0 .53-.528 1.062-1.231 1.062h-2.637c-.703 0-1.23-.531-1.23-1.062V6.203c0-.535.527-1.066 1.23-1.066h2.461c.703 0 1.23.531 1.23 1.066V16.31zm29.542 30.496V35.457h11.606v11.348zM34.645 33.152V23.047H47.48v10.105zm15.472 0V23.047h11.606v10.105zm1.406-16.843c0 .53-.527 1.062-1.23 1.062h-2.637c-.703 0-1.23-.531-1.23-1.062V6.203c0-.535.527-1.066 1.23-1.066h2.637c.703 0 1.23.531 1.23 1.066zM67 14.004c0-2.484-2.285-4.434-5.102-4.434h-5.097V6.203c0-3.016-2.813-5.676-6.508-5.676h-2.637c-3.515 0-6.508 2.48-6.508 5.676V9.57H25.676V6.203c0-3.016-2.817-5.676-6.508-5.676h-2.637c-3.52 0-6.508 2.48-6.508 5.676V9.57H5.102C2.285 9.57 0 11.7 0 14.004v45.035c0 2.484 2.285 4.434 5.102 4.434h56.62c2.817 0 5.102-2.13 5.102-4.434V14.004zm0 0" fill="#111"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M43.195 53.328v-24l13.93-2.308C55.008 15.11 44.605 6.043 32.09 6.043c-14.106 0-25.563 11.555-25.563 25.773 0 14.223 11.457 25.778 25.563 25.778 1.41 0 2.644-.18 4.055-.356 1.058-1.777 2.82-3.023 5.113-3.734.703-.176 1.41-.176 1.937-.176zM36.848 8.176l-2.82 12.09h-2.645l-1.938-12.09c2.82-1.422 7.403 0 7.403 0zM32.09 41.418c-5.29 0-9.52-4.266-9.52-9.602 0-5.332 4.23-9.597 9.52-9.597 5.289 0 9.52 4.265 9.52 9.597 0 5.336-4.231 9.602-9.52 9.602zm0-16.887c-4.055 0-7.227 3.2-7.227 7.285 0 4.09 3.172 7.29 7.227 7.29s7.226-3.2 7.226-7.29c0-4.086-3.171-7.285-7.226-7.285zm0 12.442c-2.82 0-5.113-2.309-5.113-5.157 0-2.843 2.293-5.152 5.113-5.152 2.82 0 5.113 2.309 5.113 5.152.176 2.848-2.293 5.157-5.113 5.157zm3.347 24.707c.18.71.356 1.246.708 1.957-1.41.175-2.645.355-4.055.355C14.637 63.992.355 49.594.355 31.996S14.637 0 32.09 0c15.512 0 28.558 11.375 31.203 26.129l-2.996.535C57.828 13.332 46.19 3.024 32.09 3.024 16.398 3.023 3.53 15.995 3.53 31.815c0 15.82 12.867 28.797 28.559 28.797 1.058 0 2.117 0 3.172-.175 0 .355 0 .886.175 1.242zm31.208-33.95v27.73c0 2.31-1.766 4.087-4.41 4.798-2.82.71-5.641-.531-6.344-2.664-.532-2.313 1.41-4.621 4.23-5.332 1.234-.356 2.645-.18 3.703.175V35.73l-14.808 2.665V59.19c0 1.957-1.766 3.91-4.235 4.621-2.82.711-5.816-.71-6.168-2.664-.531-2.312 1.41-4.62 4.23-5.332 1.235-.355 2.645-.18 3.704.176V31.105zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M11.427 34.125c-7.399 0-10.8-7.1-10.8-14.9-.099-7.7 3.3-14.9 10.8-14.9 2.3 0 4.2.6 5.7 1.6l-1 2.4c-1.1-.7-2.6-1.2-4.2-1.2-5.2 0-7.3 6-7.3 12.1 0 6 2.1 12 7.2 12 1.6 0 3.2-.5 4.2-1.2l1 2.5c-1.5 1-3.3 1.6-5.6 1.6zm14.901-20.8v20.3h-3.701v-20.3h-2.599v-2.3h2.599v-3.2c0-4.3 2.4-7.2 6.9-7.2h.8v2.4h-.3c-2 0-3.699 1-3.699 4.6v3.3h3.899v2.4zm0 0" fill="#679eb2" stroke="#679eb2" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(1.84155 0 0 1.8314 0 .18)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M11.427 34.125c-7.399 0-10.8-7.1-10.8-14.9-.099-7.7 3.3-14.9 10.8-14.9 2.3 0 4.2.6 5.7 1.6l-1 2.4c-1.1-.7-2.6-1.2-4.2-1.2-5.2 0-7.3 6-7.3 12.1 0 6 2.1 12 7.2 12 1.6 0 3.2-.5 4.2-1.2l1 2.5c-1.5 1-3.3 1.6-5.6 1.6zm14.901-20.8v20.3h-3.701v-20.3h-2.599v-2.3h2.599v-3.2c0-4.3 2.4-7.2 6.9-7.2h.8v2.4h-.3c-2 0-3.699 1-3.699 4.6v3.3h3.899v2.4zm0 0" fill="#679eb2" stroke="#679eb2" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(1.84155 0 0 1.8314 0 .18)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="73" xmlns="http://www.w3.org/2000/svg"><path d="M.184 46.813 26.828 64V51.383h45.625v-9.145H26.828V29.805zm45.988-34.196H.547v9.145h45.625v12.617l26.644-17.191L46.172 0zm0 0" fill="#666"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="59" xmlns="http://www.w3.org/2000/svg"><path d="M59 0H13.41C6.613 0 0 2.668 0 10.668V64h48.273V10.668H6.613c0-3.914 2.684-5.336 5.367-5.336h41.477v53.336l5.363-5.336V0zm0 0" fill="#c93"/><path d="M21.992 40.18c0-5.512 6.434-6.403 6.434-10.493 0-1.777-1.61-3.199-3.754-3.199-2.324.18-4.11 1.778-4.11 1.778L17.88 24.89s2.683-2.848 7.332-2.848c4.289 0 8.402 2.668 8.402 7.289 0 6.402-6.797 7.113-6.797 11.203v1.422h-4.824zm0 5.152h4.824v4.445h-4.824zm0 0" fill="#fff"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g fill="#f60" stroke="#f60" stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zM37.8 64.8c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M64 0H0v64h64zM12.8 12.633H6.399V6.23h6.403zm44.802 0h-38.57V6.23h38.57zm0 44.797H6.23V19.2h51.372zm0 0"/><path d="m16.336 24.59-4.547 4.547 7.41 7.41-7.41 7.242 4.547 4.547 11.957-11.79zm10.613 21.558h12.797v6.399H26.95zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="79" xmlns="http://www.w3.org/2000/svg"><path d="M30.86 9.86c7.523-.567 9.59-5.458 18.43-6.212 4.323-.375 6.956.567 7.331 2.07.188 1.317-1.879 2.446-4.512 2.634-3.574.378-5.078-.938-5.453-2.258-2.633.187-3.008 1.32-3.008 2.258.188 1.507 3.57 3.011 9.024 2.449 6.207-.567 8.277-3.012 7.898-5.457-.562-3.2-5.453-5.836-14.101-5.082-11.098.941-11.098 6.02-18.43 6.773-3.008.188-4.89-.375-5.078-1.691-.188-1.13 1.316-1.883 3.008-1.883 1.695-.188 3.574.187 4.703.562.754-.375.941-.75.754-1.128-.192-1.13-2.633-1.692-5.457-1.317-5.64.567-5.64 3.012-5.453 4.14.754 2.634 4.89 4.516 10.343 4.141zM68.28 22.468c-6.957 1.691-15.797 2.633-26.328 2.633-10.906 0-19.742-1.13-26.512-2.633-6.207-1.696-9.402-3.39-10.718-5.082.562 3.953 1.691 7.902 3.007 11.668-1.503.941-3.007 2.257-4.324 3.761C.961 35.828-.168 39.402.02 42.98c.187 3.575 1.882 6.399 4.703 8.657 2.82 2.258 6.015 2.824 9.402 2.258 1.316-.188 2.82-.942 4.137-1.317-2.82 0-5.266-.941-7.711-2.824-2.633-1.883-4.512-4.703-4.89-7.902-.563-3.012 0-6.024 1.694-8.47.375-.566.75-.94 1.125-1.316.942 2.446 2.07 4.704 3.387 6.961 2.633 3.953 5.266 7.528 7.899 11.293 1.129 2.258 1.879 4.516 2.445 6.586 1.691 2.446 4.137 4.14 7.332 5.082 3.762 1.317 7.71 1.88 11.848 1.88h.375c3.949 0 8.273-.563 12.222-1.88a14.826 14.826 0 0 0 7.149-5.082h.187a27.312 27.312 0 0 1 2.258-6.586c2.629-3.765 5.262-7.34 7.895-11.293 3.574-6.398 6.02-13.738 7.335-21.64-1.128 2.07-4.515 3.761-10.53 5.082zm-52.84-5.457c6.957 1.691 15.793 2.633 26.325 2.633 10.906 0 19.558-.942 26.328-2.633C75.426 15.316 79 13.059 79 10.8c0-1.696-1.691-3.012-4.512-4.14.563.374 1.125 1.128 1.125 1.882 0 2.258-3.383 3.95-9.965 5.457-6.207 1.316-14.101 2.258-23.695 2.258-9.21 0-17.488-.942-23.504-2.258-6.394-1.695-9.777-3.387-9.777-5.457 0-.941.375-1.695 1.691-2.637-3.949 1.696-6.207 2.824-6.207 4.895.188 2.258 3.762 4.515 11.285 6.21zm0 0" fill="#28334c"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M64 0H0v64h64zM12.8 12.633H6.399V6.23h6.403zm44.802 0h-38.57V6.23h38.57zm0 44.797H6.23V19.2h51.372zm0 0"/><path d="m16.336 24.59-4.547 4.547 7.41 7.41-7.41 7.242 4.547 4.547 11.957-11.79zm10.613 21.558h12.797v6.399H26.95zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="68" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M59.906 48.902H0m60.715 7.387V19.035H68v44.8H1.617v-44.8h7.29V56.29zM16.675.164h36.106L34.648 18.543C28.82 12.637 22.668 6.398 16.676.164zm0 0"/><path d="M23.8 33.805v-7.383h7.286v7.383zm22.02 0h-7.285v-7.383h7.285zm-29.468 7.55h7.285v7.383h-7.285zm29.628 7.383v-7.383h7.286v7.383zm-7.609-7.383v7.383h-7.285v-7.383zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="43"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M21.02 0c9.93 0 14.718 5.578 14.718 5.578l-4.433 9.715s-3.903-3.957-9.399-3.957c-6.738 0-9.93 4.676-9.93 10.074 0 5.395 3.368 10.434 9.93 10.434 6.207 0 9.754-4.856 9.754-4.856l5.32 9.356S31.836 43 21.195 43C8.605 43 .273 34.004.273 21.59.093 9.355 8.605 0 21.02 0zm19.152 18.531h7.094v-8.093h5.851v8.093h7.094v6.117h-7.094v8.098h-5.851v-8.098h-7.094zm22.523 0h7.094v-8.093h5.852v8.093h7.093v6.117h-7.093v8.098h-6.032v-8.098h-7.093zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M11.9 28C4.5 28 1 21.501 1 14.5 1 7.5 4.4 1 11.9 1c2.3 0 4.2.6 5.6 1.4l-1 2.1c-1-.6-2.7-1.1-4.2-1.1-5.2 0-7.2 5.5-7.2 11 0 5.4 2.101 10.9 7.2 10.9 1.6 0 3.1-.5 4.2-1.1l1 2.3c-1.4 1.1-3.2 1.5-5.6 1.5zM29.1 28c-1.302 0-2.702-.2-3.5-.501v8.4H22V8.2c1.9-1 4.4-1.4 7-1.4 6.5 0 10 4 10 10.3C39 23.9 35.1 28 29.1 28zM28.8 8.8c-1.1 0-2.4.199-3.2.601v16.1c.7.198 1.601.4 2.799.4 4.601 0 7.002-3.102 7.002-8.6-.102-5.201-1.9-8.5-6.601-8.5zm0 0" fill="#63b763" stroke="#63b763" stroke-miterlimit="10" stroke-width="2" transform="matrix(1.725 0 0 1.72973 0 .086)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#a03537"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="75" xmlns="http://www.w3.org/2000/svg"><path d="M.5 19v-4.1c.9-.1 1.6-.2 2-.4.4-.2.8-.6 1.2-1.001.4-.5.5-1.1.7-1.9.1-.6.2-1.499.2-2.799 0-2.201.1-3.7.4-4.6.2-.8.6-1.6 1.2-2 .5-.5 1.4-.9 2.5-1.2.7-.2 1.9-.4 3.5-.4h.9v3.9c-1.3 0-2.2.1-2.6.3-.4.2-.6.4-.9.6-.2.3-.3.7-.3 1.501 0 .8-.1 2-.2 4.099-.101 1.2-.2 2-.4 2.801-.301.6-.6 1.2-1 1.8-.4.4-1 .9-1.8 1.399.7.4 1.3.8 1.8 1.3s.8 1.2 1.1 1.899c.3.702.4 1.802.4 3.001.1 1.9.1 3.1.1 3.599 0 .702.1 1.202.3 1.602.2.4.5.5.9.6.4.2 1.2.3 2.6.3v4.098h-1c-1.6 0-2.9-.1-3.701-.4-.9-.3-1.6-.6-2.2-1.2-.6-.6-.999-1.2-1.2-1.999-.198-.8-.299-2.1-.299-4 0-2-.1-3.5-.3-4.1-.3-.9-.7-1.601-1.201-2-.698-.5-1.5-.7-2.7-.7zm39.1 0c-.9.1-1.6.2-2 .4s-.8.6-1.2 1.001c-.4.5-.5 1.1-.7 1.9-.099.6-.2 1.499-.2 2.799 0 2.201-.1 3.7-.4 4.6-.2.9-.6 1.6-1.2 2-.5.5-1.4.9-2.5 1.2-.7.2-1.9.4-3.5.4h-.999v-4.1c1.298 0 2.1-.1 2.599-.3s.7-.4.899-.6c.2-.3.301-.7.301-1.501 0-.6.1-2 .2-3.999.099-1.2.3-2.1.5-2.8.3-.7.6-1.3 1.1-1.9.4-.5 1-.9 1.7-1.3-.901-.6-1.6-1.1-2-1.6-.5-.7-1-1.801-1.201-2.8-.199-.8-.299-2.6-.299-5.2 0-.8-.1-1.4-.301-1.8-.199-.3-.4-.5-.799-.6-.2-.3-1-.3-2.5-.3v-4h.999c1.602 0 2.9.1 3.7.4.902.3 1.6.6 2.2 1.2.6.6 1.002 1.2 1.2 2 .201.8.402 2.1.402 4 0 2 .098 3.4.299 4.1.299.9.7 1.601 1.2 1.9.5.4 1.401.6 2.5.6.1.1 0 4.3 0 4.3zm0 0" fill="#72a536" stroke="#72a536" stroke-miterlimit="10" transform="matrix(1.86825 0 0 1.87558 0 .209)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="52"><path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1f7244;stroke-opacity:1;stroke-miterlimit:10" d="M0 1.5h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 7.4h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 13.3h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 19.2h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 25.1h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44" transform="matrix(1.9091 0 0 1.92593 0 .385)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="55" xmlns="http://www.w3.org/2000/svg"><path d="M54.652 53.883 41.801 64 27.289 46.172l-9.3 11.351L.347 0l53.07 29.219-13.277 6.836zm0 0" fill="#8ed200"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="m10.98 2.23 12.672 10.356c-.355-3.75-1.96-7.856-4.46-10.356-1.786-1.785-3.391-2.5-5-2.14-1.426.18-2.5 1.07-3.212 2.14zM2.23 19.191c2.68 2.676 6.786 4.106 10.536 4.461L2.41 10.98c-1.25.891-2.14 1.786-2.32 3.211-.36 1.61.355 3.215 2.14 5zm51.06 22.672L41.862 53.29c1.43 1.43 3.75 2.676 6.07 3.035.715.18 1.25.18 1.965.18 1.07 0 2.141-.18 3.036-.715l7.675 7.676c.356.355.891.535 1.426.535s1.07-.18 1.43-.535c.715-.715.715-2.145 0-2.856l-7.676-7.675c1.606-3.575 0-8.57-2.5-11.07zM4.91 7.766l34.274 42.488 11.07-11.07L7.766 4.91c-.715-.715-1.965-.535-2.68.176-.89.715-.89 1.789-.176 2.68zm0 0" fill="#0091ea"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M17.8.5c-2.9 0-5.4 2.801-5.4 6.2 0 3.4 2.4 6.2 5.4 6.2 2.9 0 5.399-2.8 5.399-6.2C23.199 3.302 20.8.5 17.8.5zm0 10.1c-1.6 0-3-1.7-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9-1.3 3.9-3 3.9zM7 11.8V1.7C7 1 6.5.5 5.8.5S4.6 1 4.6 1.7v10.1c0 .7.5 1.2 1.2 1.2S7 12.4 7 11.8zm-1.1 6.9C3 18.7.5 21.5.5 24.9s2.4 6.2 5.4 6.2 5.401-2.8 5.401-6.2c-.102-3.3-2.5-6.2-5.4-6.2zm0 10.2c-1.6 0-3-1.699-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9c-.1 2.1-1.4 3.9-3 3.9zM19 30V19.9c0-.7-.5-1.2-1.2-1.2s-1.2.5-1.2 1.2V30c0 .7.5 1.2 1.2 1.2S19 30.7 19 30zM31.3 12.7V2.6c0-.7-.499-1.2-1.2-1.2-.7 0-1.1.5-1.1 1.2v10.099c0 .701.5 1.2 1.2 1.2s1.1-.6 1.1-1.2zm-1.2 6.9c-2.9 0-5.401 2.8-5.401 6.2 0 3.4 2.4 6.202 5.4 6.202 2.901 0 5.402-2.802 5.402-6.202S33.1 19.6 30.1 19.6zm0 10.102c-1.6 0-3-1.7-3-3.902 0-2.099 1.3-3.9 3-3.9s3 1.7 3 3.9c0 2.202-1.3 3.902-3 3.902zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" transform="matrix(1.91667 0 0 1.9394 0 .485)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M17.8.5c-2.9 0-5.4 2.801-5.4 6.2 0 3.4 2.4 6.2 5.4 6.2 2.9 0 5.399-2.8 5.399-6.2C23.199 3.302 20.8.5 17.8.5zm0 10.1c-1.6 0-3-1.7-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9-1.3 3.9-3 3.9zM7 11.8V1.7C7 1 6.5.5 5.8.5S4.6 1 4.6 1.7v10.1c0 .7.5 1.2 1.2 1.2S7 12.4 7 11.8zm-1.1 6.9C3 18.7.5 21.5.5 24.9s2.4 6.2 5.4 6.2 5.401-2.8 5.401-6.2c-.102-3.3-2.5-6.2-5.4-6.2zm0 10.2c-1.6 0-3-1.699-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9c-.1 2.1-1.4 3.9-3 3.9zM19 30V19.9c0-.7-.5-1.2-1.2-1.2s-1.2.5-1.2 1.2V30c0 .7.5 1.2 1.2 1.2S19 30.7 19 30zM31.3 12.7V2.6c0-.7-.499-1.2-1.2-1.2-.7 0-1.1.5-1.1 1.2v10.099c0 .701.5 1.2 1.2 1.2s1.1-.6 1.1-1.2zm-1.2 6.9c-2.9 0-5.401 2.8-5.401 6.2 0 3.4 2.4 6.202 5.4 6.202 2.901 0 5.402-2.802 5.402-6.202S33.1 19.6 30.1 19.6zm0 10.102c-1.6 0-3-1.7-3-3.902 0-2.099 1.3-3.9 3-3.9s3 1.7 3 3.9c0 2.202-1.3 3.902-3 3.902zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" transform="matrix(1.91667 0 0 1.9394 0 .485)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#a03537"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#4d1b9b"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="54"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.242 25.867c-.5 0-1.1-.2-1.498-.6l-8.4-8.401c-.802-.799-.802-2.099 0-3l8.4-8.398c.8-.8 2.098-.8 2.999 0 .8.8.8 2.098 0 2.999l-6.9 6.9 6.9 6.9c.8.801.8 2.1 0 3-.401.4-1 .6-1.5.6zm25.1 0c-.499 0-1.099-.2-1.5-.6-.8-.8-.8-2.099 0-3.002l6.9-6.898-6.9-6.9c-.8-.8-.8-2.101 0-3 .8-.8 2.1-.8 3.001 0l8.398 8.4c.802.8.802 2.1 0 3l-8.398 8.4c-.4.4-1.001.6-1.5.6zm-16.7 4.1c-.199 0-.398 0-.698-.1-1.102-.401-1.702-1.5-1.301-2.6l8.398-25.1c.4-1.1 1.5-1.699 2.6-1.301 1.102.4 1.702 1.5 1.301 2.601l-8.398 25.1c-.202.899-1.102 1.4-1.901 1.4zm0 0" transform="matrix(1.74425 0 0 1.75713 0 .013)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#bababa;fill-opacity:1" d="M72.277.21c-4.906 0-3.586 4.978-4.53 7.083-.376.77-.946 2.297-1.509 3.64l.375-.765c-.375.762-.566.953-1.32 1.527 0 0-3.398 2.11-6.422 3.832C55.285 9.211 48.863 5.57 41.875 5.57c-6.984 0-13.594 3.828-16.992 9.957-3.207-1.914-6.61-3.832-6.61-3.832-.753-.574-1.128-.765-1.32-1.527l.375.762c-.566-1.34-1.129-2.68-1.508-3.637C15.066 5 16.383.211 11.29.211c-4.915 0-3.97 6.7-5.477 9.379-.946 1.726-3.778 3.445-5.098 5.363-.192.188-.192.379-.192.574-.566.957-.753 2.297.192 3.637 2.453 4.211 6.039.766 8.305.383.945-.192 2.27-.192 3.777-.574l-.945.191c.757-.191 1.136 0 2.078.574 0 0 3.59 2.106 8.308 4.785v1.149c0 3.64.946 7.469 3.024 10.527a656.505 656.505 0 0 0-11.895 7.278c-.758.574-1.137.765-1.89.574h.753-3.585c-2.27 0-5.102-2.68-7.93.957-2.649 3.637 2.828 5.36 3.965 7.469 1.133 2.105-.38 7.843 4.34 7.27 4.535-.571 3.777-4.403 4.91-6.505.566-.765 1.136-1.918 1.699-3.062l-.375.765c.375-.578.754-.765 1.508-1.343 0 0 5.289-2.868 11.332-6.508v8.804c1.699.766 3.398 1.153 5.097 1.532v-4.785l2.454.574v4.785c1.699.387 3.402.578 5.101.578v-4.789h2.266v4.598c1.695 0 3.398-.196 5.097-.578v-4.786l2.453-.574v4.977c1.7-.38 3.399-.766 5.098-1.532v-8.804a396.942 396.942 0 0 1 11.332 6.508c.754.578 1.133.765 1.512 1.343.566.762.941 1.72 1.32 2.297.946 1.914.375 5.934 4.91 6.504 4.536.578 3.211-5.164 4.344-7.27 1.133-2.109 6.61-3.64 3.965-7.468-2.645-3.637-5.664-1.149-7.93-.957h-3.59.754c-.754 0-.941 0-1.886-.574 0 0-5.477-3.446-11.899-7.278 2.078-3.25 3.024-6.695 3.024-10.527v-1.149c4.722-2.68 8.308-4.785 8.308-4.785.754-.574 1.32-.574 2.078-.574l-.57-.191c1.516.191 2.836.382 3.781.574 2.266.383 5.852 3.828 8.309-.383 2.453-4.21-3.59-6.89-5.098-9.574-.945-1.719-.758-5.164-2.078-7.27 0-.195-.191-.386-.191-.386C74.735.785 73.793.402 72.277.21zM34.324 23.376c.567 0 .758 0 1.32.191 2.645.766 3.403 3.254 2.645 6.125-.754 2.684-3.586 4.403-6.23 3.637-2.645-.766-3.59-3.445-2.645-6.121.57-2.3 2.645-3.832 4.91-3.832zm14.73 0c2.266 0 4.532 1.531 5.098 3.637.754 2.68 0 5.172-2.644 6.129-2.645.761-5.477-.77-6.23-3.641-.755-2.68 0-5.168 2.644-6.125a1.19 1.19 0 0 0 1.133 0zm-7.363 12.824c.754 0 3.961 4.215 3.586 4.79-.379.574-6.797.574-7.175 0-.38-.575 3.02-4.79 3.59-4.79zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M67 .445 36.203 4.63v24.55H67zM30.797 5.172 0 9.355V29.18h30.797zM.18 34.82v19.825l30.797 4.183V35zm36.023 0v24.551L67 63.555V34.82zm0 0" fill="#666"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M57.602 41.688c2.488 0 4.62 1.066 6.218 2.492L58.488 5.422C58.133 2.222 55.29.09 52.266.09H11.734c-3.199 0-5.867 2.133-6.402 5.332L0 44.18c1.777-1.426 3.91-2.493 6.398-2.493zm0 3.023H6.398A6.372 6.372 0 0 0 0 51.109v6.403a6.372 6.372 0 0 0 6.398 6.398h51.204A6.372 6.372 0 0 0 64 57.512v-6.403a6.372 6.372 0 0 0-6.398-6.398zM52.09 56.977h-3.91a1.97 1.97 0 0 1-1.957-1.954c0-1.066.886-1.957 1.957-1.957h3.91c1.066 0 1.953.891 1.953 1.957a1.97 1.97 0 0 1-1.953 1.954zm5.156 0a1.972 1.972 0 0 1-1.957-1.954c0-1.066.89-1.957 1.957-1.957s1.953.891 1.953 1.957c.18 1.067-.71 1.954-1.953 1.954zm0 0" fill="#4d1b9b"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="c"><g filter="url(#a)"><path d="M0 0h72v64H0z" fill-opacity=".6"/></g></mask><clipPath id="b"><path d="M0 0h72v64H0z"/></clipPath><mask id="e"><g filter="url(#a)"><path d="M0 0h72v64H0z" fill-opacity=".6"/></g></mask><clipPath id="d"><path d="M0 0h72v64H0z"/></clipPath><g clip-path="url(#b)" mask="url(#c)"><path d="M67.418 37.824C70.199 40.454 72 44.391 72 48.656c0 8.207-6.71 14.934-14.89 14.934-8.184 0-14.891-6.727-14.891-14.934 0-.492 0-1.148.164-1.64.816-7.387 7.199-13.293 14.89-13.293 3.926-.164 7.528 1.64 10.145 4.101zm0 0" fill="#ef806f"/></g><g clip-path="url(#d)" mask="url(#e)"><path d="M68.563 32.082c0 1.148-.165 2.297-.325 3.61-3.11-2.626-7.039-4.102-11.129-4.102-8.672 0-16.035 6.562-17.02 15.262H11.782C5.238 46.852 0 41.602 0 35.035c0-5.086 3.273-9.515 7.691-11.16v-.656c0-6.07 4.91-10.992 10.965-10.992 1.961 0 3.762.492 5.399 1.312C25.69 6.152 32.398.41 40.418.41c9.328 0 16.855 7.547 16.855 16.902v.329c6.543 1.476 11.29 7.386 11.29 14.441zm0 0" fill="#1ea6c6"/></g><path d="m64.965 48.82-7.528 7.715-7.69-7.715h4.581V38.152h6.055V48.82zm0 0" fill="#fff"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="49" xmlns="http://www.w3.org/2000/svg"><path d="M24.842 21.014c2.2-.7 4.4.7 4.202 2.8-.2 2.4-3.302 3.601-5.1 4.2l.1-.099-.1-.101c1.3-1 3.898-2.199 3.7-4.6-.1-1.2-1.001-2.098-2.702-2v-.2zm-16.099.401.1-.101c-1 0-1.901.4-2.701.7-.798.3-1.8.401-2.198 1.3.399.7 1.4.7 2.398.802 3.4.5 8.302.398 11.701 0 1.799-.201 3.4-.401 4.2-1.201l-.1-.101.1-.1c-3.4.402-7.8 1-11.9.8-1.301-.099-3-.099-3.7-.8.4-.7 1.4-.9 2.1-1.299zm19.9 14.099v-.1c-5.1 2.5-13.201 2.8-20.5 2.201l.1.1-.1.2c2.999.5 6.9.7 10.7.398 3.7-.198 8.199-.698 9.9-2.698zm-14.4-15.398h.1c-.8-1.802-2.3-2.602-2.499-4.7-.2-1.902.7-3.102 1.598-4 1.101-1.201 2.702-2.201 3.901-3.5 1.6-1.803 3.4-4.5 1.9-7.102l-.101.101-.299-.101c.4 2.5-.6 4.101-1.901 5.4-.999 1.201-2.6 2.201-4 3.3-1.6 1.3-3.7 2.901-3.1 5.3.502 2.302 2.8 3.9 4.102 5.4zm8-11.602-.1-.099c-2.7 1-6.701 2.6-7.1 5.698-.1 1.503.399 2.602.9 3.401.4.602 1.1 1 1.3 1.901.2.8 0 1.6-.2 2.2h.1l.1.1c1.099-.8 2.2-1.901 1.899-3.401-.198-1.5-1.9-2.5-2.1-3.899-.1-.802.1-1.5.401-1.9 1.1-1.701 3.5-2.902 4.8-4zm-13.8 17.401-.101-.101c-.5.301-1.5.4-1.4 1.2.1.8 1.5 1 2.2 1.2 3.7.8 9.2.3 11.902-.599l-.1-.101.1-.099c-.301-.101-.7-.7-1.3-.7-.502-.1-1.6.299-2.602.4-1.6.199-3.299.3-4.799.199-1.101-.1-4.5-.1-3.9-1.399zm.9 4.099.1-.1c-.6.201-1.3.4-1.3 1.1 0 .601 1.2 1 1.9 1.3 3.299 1 8.5.4 10.9-.699-.2-.302-.6-.4-.9-.601-.4-.1-.7-.3-1.1-.5-2.001.5-5.1.7-7.5.4-.7-.1-1.701-.1-1.9-.799zm17.699 3.2-.1-.099c-.098 1-1.3 1.1-2.1 1.3-.898.2-1.9.398-2.998.5-4.902.599-11.5.898-16.302 0-.898-.102-2.2-.401-2.499-1.102.4-.698 1.5-.8 2.399-1.198l-.098-.101.098-.1c-1.2.1-2.1.4-2.998.701-.7.3-1.701.698-1.902 1.5.6.8 1.801.8 2.8 1 6.6 1 15.7 1.198 21.402-.7.998-.401 3.098-1 2.1-1.901zm-3.7-5.099c.2 0 .4-.101.702-.2m.898-6.8c-.198 0-.399.1-.7.1m-2.2 1.7c.1 0 .2-.101.401-.101m-12.5-1.6c-.4.1-.8.1-1.3.201m-2.2 15.898c.499.2 1.099.2 1.7.401m20.5-2.2c.1-.1.2-.2.3-.399M19.043.814c0-.099-.098-.3-.098-.399m-4.702 19.7c.1.1.301.4.402.5m2.298 1.199c.1-.1.2-.198.3-.399m5.7-13.2c-.3.099-.499.2-.7.398m-1.198 18.802h.198m-12.6-1.8c0 .1-.2.1-.2.199m.9 4.2.101-.1m-2.8 2.701c-.4 0-.7.1-1 .1m21.399.5c0-.1-.1-.1-.1-.1h-.098" fill="#666" stroke="#666" stroke-miterlimit="10" transform="matrix(1.63519 0 0 1.61722 .336 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><path d="M67.566 0H4.56C2.125 0 .339 1.953.339 4.234V48.04c0 2.445 1.946 4.234 4.22 4.234h62.843c2.438 0 4.223-1.953 4.223-4.234V4.4C71.949 1.952 70.004 0 67.566 0zm0 0" fill="#ced2d8"/><path d="M4.559 10.586h63.007v37.453H4.56zm0 0" fill="#f2f2f2"/><path d="M17.55 5.7a1.462 1.462 0 1 1-2.921 0 1.462 1.462 0 1 1 2.922 0zm0 0" fill="#54b845"/><path d="M12.516 5.7c0 .808-.653 1.464-1.461 1.464a1.465 1.465 0 0 1 0-2.93c.808 0 1.46.657 1.46 1.465zm0 0" fill="#fbd303"/><path d="M7.809 5.7c0 .808-.657 1.464-1.461 1.464-.809 0-1.461-.656-1.461-1.465 0-.808.652-1.465 1.46-1.465.805 0 1.462.657 1.462 1.465zm0 0" fill="#f0582f"/><path d="m57.5 38.594-4.223-1.137c-.324-1.793-1.136-3.422-1.949-4.887l2.11-3.582c.324-.328.164-.976-.16-1.304L50.19 24.59c-.324-.324-.812-.324-1.3-.164l-3.57 2.117c-1.462-.813-3.087-1.625-4.872-1.953l-1.136-4.07c-.165-.489-.489-.817-.977-.817h-4.223c-.484 0-.808.328-.972.817l-1.301 4.07c-1.785.328-3.41 1.14-4.871 1.953l-3.735-1.953c-.324-.324-.972-.164-1.297.164l-3.085 3.094c-.325.324-.325.812-.164 1.3l2.113 3.586c-.813 1.465-1.625 3.094-1.95 4.883l-4.062.977c-.484.164-.809.488-.809.98v4.23c0 .493.325.817.81.981l4.222 1.137c.324 1.793 1.136 3.422 1.949 4.887l-2.11 3.746c-.324.324-.164.976.16 1.304l3.087 3.094c.324.324.812.324 1.3.16l3.57-2.117c1.462.816 3.087 1.629 4.872 1.957l1.137 4.234c.164.489.488.813.976.813h4.223c.484 0 .812-.324.972-.813l1.137-4.234c1.785-.328 3.41-1.14 4.871-1.957l3.574 2.117c.325.328.973.164 1.297-.16l3.086-3.094c.325-.328.325-.816.164-1.304l-2.113-3.582c.813-1.465 1.625-3.094 1.95-4.887l4.058-.977c.488-.164.812-.488.812-.976v-4.399c.164-.488 0-.812-.484-1.14zM36.062 49.832c-4.382 0-7.957-3.582-7.957-7.98 0-4.395 3.575-7.98 7.957-7.98 4.387 0 7.958 3.585 7.958 7.98 0 4.398-3.57 7.98-7.958 7.98zm0 0" fill="#6eb1e1"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="62" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="m11.426 26.762 27.386-4.489-1.007-4.32-27.387 4.488zm4.203 18.453 1.008 4.652 11.086-2.16v-4.652zm-1.516-6.985 13.61-2.492v-3.656c0-.332 0-.664.168-.996l-14.786 2.656zm13.61 16.625L13.945 57.68l-9.07-40.89 35.617-5.653 2.856 12.468c.672 0 1.343 0 1.847-.168L39.82 0 .34 5.652 12.434 60.34l15.457-3.156v-.168zm18.312-7.148c-8.566 0-15.625-2.824-15.625-6.148v6.148c0 3.492 7.059 6.152 15.625 6.152 8.57 0 15.625-2.828 15.625-6.152v-6.148c0 3.324-7.055 6.148-15.625 6.148zm0 9.14c-8.566 0-15.625-2.824-15.625-6.148v6.149C30.41 60.34 37.47 63 46.035 63c8.57 0 15.625-2.824 15.625-6.152v-6.149c0 3.492-7.055 6.149-15.625 6.149zm0-30.917c-8.566 0-15.625 2.828-15.625 6.152v6.316c0 3.493 7.059 6.149 15.625 6.149 8.57 0 15.625-2.824 15.625-6.149v-6.152c0-3.488-7.055-6.316-15.625-6.316zm0 0"/><path d="M46.035 36.902c-8.566 0-14.11-2.824-14.11-4.656 0-1.828 5.544-4.652 14.11-4.652 8.57 0 14.113 2.824 14.113 4.652 0 1.832-5.543 4.656-14.113 4.656zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="m14.974 16.374-11-5.5v-.1l11-5.398V1.574L.374 9.375v3l14.6 7.7zm7.502-3.2L23.174.376h-4.798l.698 12.8zm-1.701 8.002c1.6 0 2.701-1.401 2.701-3.102 0-1.898-1.102-3.1-2.701-3.1s-2.701 1.3-2.701 3.1c-.1 1.7 1 3.102 2.701 3.102zm5.8-19.602v3.802l11.2 5.399v.1l-11.2 5.5v3.7l14.6-7.7v-3.1zm-24 23.401h36.5v2.5h-36.5zm0 7.1h36.5v2.5h-36.5zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.81211 0 0 1.83119 .353 0)"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="45" xmlns="http://www.w3.org/2000/svg"><path d="M22.686 26.4h.8c0 2.401-.4 4.2-1.2 5.3-.801 1.1-1.8 1.7-3 1.7-1.001 0-1.9-.4-2.8-1.1-.9-.699-1.701-2.7-2.4-5.9l-2-8.9-6.902 15.599h-4.4l9.901-21.2c-.5-2.698-1.2-4.799-1.899-6.098-.701-1.3-1.7-2.002-2.7-2.002-.902 0-1.601.301-2.3 1-.6.7-1 1.701-1.1 3.1h-.8c0-2.299.5-4.1 1.4-5.4.899-1.3 1.898-2 3.2-2 .8 0 1.599.302 2.3 1.002.7.699 1.4 1.799 1.9 3.499.599 1.7 1.4 5.1 2.6 10.3l1.599 7.3c.701 3 1.4 5 2.1 6.1.7 1 1.6 1.5 2.6 1.5 1.9-.1 2.901-1.3 3.101-3.8zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" transform="matrix(1.87615 0 0 1.85407 0 .073)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M68.633.102H16.39v7.226H5.738v2.274H0v2.062h5.738v2.684h10.653V26.94H5.738v2.477H0v2.066h5.738v2.27h10.653v13.625H5.738v2.48H0v2.063h5.738v2.273h10.653v9.703h52.242v-9.703h9.629v-2.48H84v-2.063h-5.738v-2.476h-9.63v-13.63h9.63v-2.062H84v-2.066h-5.738v-2.684h-9.63V14.141h9.63v-2.684H84V9.395h-5.738V7.12h-9.63zm-10.04 17.136c-2.253 0-4.097-1.86-4.097-4.129S56.34 8.98 58.594 8.98s4.097 1.86 4.097 4.13c0 2.476-1.843 4.128-4.097 4.128zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="fill-rule:nonzero;fill:#7e57c2;fill-opacity:1;stroke-width:.75;stroke-linecap:butt;stroke-linejoin:miter;stroke:#7e57c2;stroke-opacity:1;stroke-miterlimit:10" d="M6.274 25.574h28.3l-9.698-9.3-4.501 3.802-4.5-3.802zm34.1-25.2v28.002H.376V.374zM26.976 14.576l10.7 10.298v-19.3zm-24.2 10.298 10.7-10.298-10.7-9.002zm1.4-21.7 15.9 13.4 15.9-13.4zm0 0" transform="matrix(2.06135 0 0 2.08166 0 .076)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M16.223 21.805.09 55.844l3.012 3.015 20.035-20.035c-.711-1.594-.532-3.543.886-4.96 1.774-1.774 4.43-1.774 6.204 0 1.773 1.769 1.773 4.429 0 6.202-1.243 1.243-3.368 1.594-4.965.887L5.23 60.984 8.242 64l34.04-16.133L49.73 27.48 36.61 14.36zm46.625-4.075L46.184 1.062c-1.418-1.417-3.547-1.417-4.965 0L37.32 4.966c-1.422 1.418-1.422 3.543 0 4.965l16.664 16.664c1.418 1.418 3.543 1.418 4.965 0l3.899-3.903c1.418-1.418 1.418-3.543 0-4.96zm0 0" fill="#fea500"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.887 38.266c-2.684-.84-4.532-3.36-4.532-6.215 0-2.852 1.848-5.371 4.532-6.211.84-.336 1.343-1.172 1.008-2.012-.84-3.023-1.848-5.707-3.524-8.394-.504-.84-1.344-1.008-2.184-.672-1.007.504-2.015.84-3.19.84-3.692 0-6.548-3.024-6.548-6.547 0-1.176.336-2.184.84-3.188.504-.84.168-1.68-.672-2.183a40.47 40.47 0 0 0-8.39-3.528c-.84-.168-1.68.168-2.016 1.008C37.37 3.852 34.855 5.7 32 5.7s-5.371-1.847-6.21-4.535C25.452.324 24.612-.18 23.772.156c-3.02.84-5.707 1.848-8.39 3.528-.84.503-1.008 1.343-.672 2.183.504 1.004.84 2.012.84 3.188 0 3.691-3.024 6.547-6.547 6.547-1.176 0-2.184-.336-3.191-.84-.84-.504-1.68-.168-2.184.672a40.699 40.699 0 0 0-3.524 8.394c-.167.84.168 1.676 1.008 2.012 2.684.84 4.532 3.36 4.532 6.21 0 2.856-1.848 5.376-4.532 6.216-.84.332-1.343 1.172-1.008 2.011.84 3.024 1.848 5.707 3.524 8.395.504.84 1.344 1.008 2.184.672 1.007-.504 2.015-.84 3.19-.84 3.692 0 6.548 3.02 6.548 6.547 0 1.176-.336 2.183-.84 3.187-.504.84-.168 1.68.672 2.184a40.47 40.47 0 0 0 8.39 3.527h.336c.672 0 1.344-.504 1.512-1.176.84-2.687 3.356-4.535 6.211-4.535s5.371 1.848 6.211 4.535c.336.84 1.176 1.344 2.016 1.008 3.02-.84 5.707-1.847 8.39-3.527.84-.504 1.008-1.344.672-2.184-.504-1.004-.84-2.011-.84-3.187 0-3.692 3.024-6.547 6.547-6.547 1.176 0 2.184.336 3.192.84.84.504 1.68.168 2.183-.672a40.698 40.698 0 0 0 3.524-8.395c.503-.672 0-1.511-.84-1.843zm-30.719 3.691c-5.371 0-9.902-4.363-9.902-9.906 0-5.371 4.363-9.903 9.902-9.903 5.371 0 9.902 4.364 9.902 9.903 0 5.375-4.53 9.906-9.902 9.906zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M46.168 13.516c1.793-.711 3.766-.891 5.738-.891V.008c-8.605-.18-16.851 3.554-22.23 10.308-2.153 2.844-4.125 5.864-5.559 9.243l-4.12 10.128c-1.079 3.024-2.333 6.223-3.767 9.067a31.916 31.916 0 0 1-3.945 6.754c-1.254 1.777-3.047 3.199-5.02 4.09-2.152 1.066-4.66 1.597-7.171 1.597v12.797c8.605.18 16.851-3.554 22.23-10.308 1.613-2.309 3.227-4.797 4.485-7.286l3.406-8h14.879v-12.62h-9.86c.715-1.954 1.793-3.731 3.047-5.508.895-1.602 2.153-2.844 3.407-3.91 1.613-1.422 3.046-2.313 4.48-2.844zm0 0" fill="#d10407"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="60" xmlns="http://www.w3.org/2000/svg"><path d="M21.516 22.047V3.832c0-1.91 1.574-3.297 3.324-3.297h20.816l10.672 10.582v10.754c0 .348-.351.695-.703.695h-3.672c-.351 0-.7-.347-.7-.695v-7.285H44.43c-1.399 0-2.446-1.04-2.446-2.43v-6.59H26.59v16.305c0 .348-.352.695-.7.695h-3.675c-.348.172-.7-.171-.7-.52zm0 39.203V28.984c0-.695.523-1.214 1.226-1.214h36.035c.7 0 1.223.52 1.223 1.214v27.063c0 3.469-2.973 6.418-6.473 6.418H22.914c-.875 0-1.398-.52-1.398-1.215zm25.363-24.98c0 1.562 1.226 2.601 2.625 2.601 1.398 0 2.625-1.215 2.625-2.601 0-1.56-1.227-2.602-2.625-2.602-1.399-.172-2.625 1.043-2.625 2.602zm0 8.847c0 1.563 1.226 2.602 2.625 2.602 1.574 0 2.625-1.215 2.625-2.602 0-1.387-1.227-2.601-2.625-2.773-1.399 0-2.625 1.21-2.625 2.773zM37.96 36.27c0 1.386 1.223 2.601 2.621 2.601 1.402 0 2.625-1.215 2.625-2.601s-1.223-2.602-2.625-2.602c-1.398-.172-2.621 1.043-2.621 2.602zm0 8.847c0 1.387 1.223 2.602 2.621 2.602 1.574 0 2.625-1.215 2.625-2.602 0-1.562-1.223-2.601-2.625-2.773-1.398 0-2.621 1.21-2.621 2.773zm0 8.848c0 1.387 1.223 2.601 2.621 2.601 1.574 0 2.625-1.214 2.625-2.601s-1.223-2.602-2.625-2.602c-1.398 0-2.621 1.215-2.621 2.602zM29.039 36.27c0 1.562 1.223 2.601 2.621 2.601 1.403 0 2.625-1.215 2.625-2.601 0-1.56-1.222-2.602-2.625-2.602-1.574-.172-2.62 1.043-2.62 2.602zm0 8.847c0 1.563 1.223 2.602 2.621 2.602 1.574 0 2.625-1.215 2.625-2.602 0-1.562-1.222-2.601-2.625-2.773-1.398 0-2.62 1.21-2.62 2.773zm0 8.848c0 1.387 1.223 2.601 2.621 2.601 1.574 0 2.625-1.214 2.625-2.601s-1.222-2.602-2.625-2.602c-1.574 0-2.62 1.215-2.62 2.602zm-22.566 8.5h8.57c.7 0 1.227-.52 1.227-1.215V20.832c0-.695-.528-1.215-1.227-1.215h-6.82C3.672 19.617 0 23.262 0 27.77v28.449c0 3.297 2.8 6.246 6.473 6.246zm0 0" fill="#fea500"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="49" xmlns="http://www.w3.org/2000/svg"><path d="M4.524 3.224v10.102h8.5v2.598h-8.5v13.7h-3.9V.626h13.301v2.598zm14.402 26.3V.826h3.7v28.7zm0 0" fill="#d10407" stroke="#d10407" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(2.10753 0 0 2.07742 0 .079)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M46.168 13.516c1.793-.711 3.766-.891 5.738-.891V.008c-8.605-.18-16.851 3.554-22.23 10.308-2.153 2.844-4.125 5.864-5.559 9.243l-4.12 10.128c-1.079 3.024-2.333 6.223-3.767 9.067a31.916 31.916 0 0 1-3.945 6.754c-1.254 1.777-3.047 3.199-5.02 4.09-2.152 1.066-4.66 1.597-7.171 1.597v12.797c8.605.18 16.851-3.554 22.23-10.308 1.613-2.309 3.227-4.797 4.485-7.286l3.406-8h14.879v-12.62h-9.86c.715-1.954 1.793-3.731 3.047-5.508.895-1.602 2.153-2.844 3.407-3.91 1.613-1.422 3.046-2.313 4.48-2.844zm0 0" fill="#d10407"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M81.227 64H2.773a2.514 2.514 0 0 1-2.535-2.54V21.333h83.524v40.129A2.514 2.514 0 0 1 81.227 64zm0 0" fill="#efce4a"/><path d="M33.008 10.059v-7.52A2.514 2.514 0 0 0 30.468 0H2.774A2.514 2.514 0 0 0 .238 2.54v18.792h83.524v-8.734a2.514 2.514 0 0 0-2.535-2.54zm0 0" fill="#ebba16"/><path d="m53.059 42.668-10.754-9.754v6.5H30.94v6.504h11.364v6.5zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M81.227 64H2.773a2.514 2.514 0 0 1-2.535-2.54V21.333h83.524v40.129A2.514 2.514 0 0 1 81.227 64zm0 0" fill="#efce4a"/><path d="M33.008 10.059v-7.52A2.514 2.514 0 0 0 30.468 0H2.774A2.514 2.514 0 0 0 .238 2.54v18.792h83.524v-8.734a2.514 2.514 0 0 0-2.535-2.54zm0 0" fill="#ebba16"/><path d="m42 31.594-9.738 10.77h6.492v11.374h6.492V42.363h6.492zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M81.227 64H2.773a2.514 2.514 0 0 1-2.535-2.54V21.333h83.524v40.129A2.514 2.514 0 0 1 81.227 64zm0 0" fill="#efce4a"/><path d="M33.008 10.059v-7.52A2.514 2.514 0 0 0 30.468 0H2.774A2.514 2.514 0 0 0 .238 2.54v18.792h83.524v-8.734a2.514 2.514 0 0 0-2.535-2.54zm0 0" fill="#ebba16"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M43.8 55.613H11.036c-3.586 0-6.488-2.906-6.488-6.5s2.902-6.504 6.488-6.504h12.797c5.8 0 10.582-4.793 10.582-10.609s-4.781-10.61-10.582-10.61h-6.996c.172.684.172 1.368.172 2.055 0 .684 0 1.368-.172 2.051h6.996c3.586 0 6.484 2.91 6.484 6.504s-2.898 6.504-6.484 6.504H11.035C5.23 38.504.453 43.293.453 49.114c0 5.816 4.777 10.609 10.582 10.609H43.97c-.168-.684-.168-1.371-.168-2.055zm10.067-4.277c-3.414 0-6.312 2.738-6.312 6.332S50.285 64 53.867 64c3.586 0 6.317-2.738 6.317-6.332s-2.73-6.332-6.317-6.332zM19.567 0H6.765C5.742 0 4.719.855 4.719 2.055v15.398C2.16 18.31.453 20.707.453 23.445c0 3.422 2.73 6.332 6.313 6.332 3.586 0 6.316-2.738 6.316-6.332 0-2.738-1.707-5.136-4.266-5.992v-4.789h10.75c1.024 0 2.047-.855 2.047-2.055V2.055C21.441 1.027 20.59 0 19.566 0zm34.3 4.277c-8.191 0-14.847 6.676-14.847 14.887 0 4.45 1.878 8.559 5.292 11.297l7.68 15.23c.68 1.54 2.899 1.54 3.582 0l7.68-15.23c3.414-2.91 5.289-7.016 5.289-11.297.172-8.21-6.484-14.887-14.676-14.887zm0 21.22c-3.414 0-6.312-2.74-6.312-6.333s2.73-6.328 6.312-6.328c3.586 0 6.317 2.734 6.317 6.328s-2.73 6.332-6.317 6.332zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M61.988 2.012v59.976L46.996 17.004zM2.012 61.988h59.976L17.004 46.996zm14.992-14.992 44.984 14.992L32 32zM32 32l29.988 29.988-14.992-44.984zM2.012 46.996v14.992l14.992-14.992zM32 32H17.004v14.996zm14.996-14.996H32V32zM61.988 2.012H46.996v14.992zM17.004 32 2.012 46.996h14.992zM32 17.004 17.004 32H32zM46.996 2.012 32 17.004h14.996zm0 0" fill="#666" stroke="#fff" stroke-width="1.66605"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.773 29.176 34.824 1.226c-1.636-1.636-4.164-1.636-5.797 0L23.23 7.024l7.282 7.286c1.636-.594 3.718-.149 5.054 1.191 1.34 1.336 1.786 3.418 1.192 5.055l7.137 7.133a4.905 4.905 0 0 1 5.054 1.19 4.942 4.942 0 0 1-6.988 6.99c-1.488-1.49-1.785-3.571-1.043-5.356l-6.54-6.54v17.395l1.337.89a4.935 4.935 0 0 1 0 6.99 4.937 4.937 0 0 1-6.985 0c-1.933-1.934-2.082-5.056-.148-6.99.445-.444.89-.89 1.484-1.038V23.527c-.445-.297-1.039-.597-1.484-1.043-1.488-1.484-1.785-3.566-1.043-5.351l-7.137-7.285-19.175 19.18c-1.637 1.632-1.637 4.16 0 5.796l27.949 27.95c1.636 1.636 4.164 1.636 5.797 0l27.8-27.801c1.637-1.633 1.637-4.313 0-5.797zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="42"><path style="stroke:none;fill-rule:nonzero;fill:#2dbcaf;fill-opacity:1" d="M41.156 11.465c-3.449.906-5.804 1.633-9.254 2.539-.906.184-.906.184-1.632-.36-.907-.91-1.27-1.636-2.54-2-3.449-1.812-6.714-1.085-9.98.91-3.809 2.54-5.625 6.169-5.625 10.344 0 4.172 3.086 8.165 7.441 8.528 3.809.363 6.711-.906 9.254-3.63.36-.726.907-1.273 1.633-1.995H19.746c-1.09 0-1.27-.727-1.09-1.633.727-1.816 1.996-4.535 2.723-6.168.183-.184.363-.91 1.27-.91h20.14c0 1.633 0 2.902-.18 4.535-.726 3.996-1.996 7.621-4.535 10.887-3.992 5.265-9.074 8.347-15.61 9.258-5.44.722-10.339-.184-14.694-3.63C3.777 35.056 1.422 30.88.695 25.98c-.73-6.168 1.086-11.246 4.715-15.968C9.223 4.754 14.484 1.668 20.832.578c5.266-.906 10.164-.18 14.7 2.723 2.902 1.996 4.898 4.535 6.35 7.62 0 0 0 .18-.726.544zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#2dbcaf;fill-opacity:1" d="M59.117 41.766c-4.898 0-9.433-1.633-13.242-4.715-3.086-2.723-5.262-6.352-5.809-10.707-.906-6.352.727-11.793 4.54-16.692 3.988-5.265 8.89-8.168 15.421-9.254 5.625-.91 10.887-.363 15.606 2.723 4.351 2.902 7.074 7.074 7.62 12.156.907 7.438-1.089 13.61-6.35 18.688-3.63 3.629-8.165 6.168-13.063 7.078-1.633.363-3.266.723-4.723.723zm13.25-22.133c0-.727 0-1.274-.183-1.817-.907-5.445-6.168-8.527-11.25-7.257-5.262 1.086-8.348 4.351-9.797 9.433-.907 4.356 1.09 8.528 4.898 10.344 2.903 1.27 6.172 1.09 9.07-.184 4.54-2.175 6.899-5.804 7.262-10.52zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="65" xmlns="http://www.w3.org/2000/svg"><path d="M21.61 63.91c-.176 0-.352 0-.528-.176C8.434 58.934 0 46.488 0 32.977 0 14.844 14.582.09 32.5.09S65 14.844 65 32.977c0 13.511-8.434 25.957-21.082 30.757a1.12 1.12 0 0 1-1.055 0c-.351-.18-.527-.355-.699-.71l-7.027-18.669c-.352-.71.175-1.601.875-1.777 3.867-1.422 6.324-5.156 6.324-9.422 0-5.511-4.39-9.957-9.836-9.957s-9.836 4.446-9.836 9.957c0 4.09 2.633 7.82 6.324 9.422.7.356 1.051 1.067.875 1.777l-7.027 18.668c-.172.356-.348.711-.7.711 0 .176-.175.176-.527.176zm0 0" fill="#af7931"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="81" xmlns="http://www.w3.org/2000/svg"><path d="M78.398 6.79C75.508 1.55 70.492 0 66.828 0c-4.437 0-8.101 2.328-7.328 4.074.191.387.965 2.133 1.543 2.906.77 1.165 2.121.196 2.508 0 1.347-.773 2.89-.968 4.433-.773 1.543.191 3.664 1.164 5.207 3.879 3.278 6.398-6.941 19.781-19.863 10.473-13.113-9.118-25.652-6.207-31.437-4.27-5.786 1.941-8.293 3.688-5.977 7.953 3.086 5.82 2.121 4.074 5.012 8.922 4.629 7.758 15.043-3.492 15.043-3.492-7.711 11.441-14.27 8.726-16.778 4.656-2.312-3.492-4.05-7.758-4.05-7.758C-4.336 33.55.87 64 .87 64h9.64C13.02 52.75 21.7 53.14 23.243 64h7.328c6.559-21.914 22.95 0 22.95 0h9.644c-2.7-14.934 5.398-19.586 10.414-28.316 5.399-8.922 10.223-19.586 4.82-28.895zM53.52 35.683c-5.012-1.746-3.278-6.786-3.278-6.786s4.434 1.356 10.414 3.489c-.191 1.36-3.277 4.46-7.136 3.297zm0 0" fill="#02303a"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".25"><path d="M63.773.227H.227v63.546h63.546zM60.324 60.14H4.04V12.574h56.285zm0 0" stroke-width=".4539"/><path d="M9.305 18.203h45.39v6.352H9.305zm7.445 10.348h27.777v2.543H16.75zm3.992 7.808h27.781v2.723h-27.78zm-3.992 8.168h27.777v2.723H16.75zm3.992 8.352h27.781v2.723h-27.78zm0 0" stroke-width=".4539"/></g></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="51"><path style="stroke:none;fill-rule:nonzero;fill:#c19770;fill-opacity:1" d="M30.605 30.332c2.782-.18 5.735.879 7.993 2.824 1.039.883 2.086 2.121 2.777 3.535 1.39-2.296 3.477-4.242 6.082-5.3a15.304 15.304 0 0 1 9.73 0 20.188 20.188 0 0 1 7.293 4.77 62.33 62.33 0 0 0 5.04 4.593c.867.883 1.91 1.41 2.953 1.766 1.562.53 3.297 0 4.343-1.06.868-1.058.868-2.827-.175-4.062-.696-.53-1.563-.53-2.258.176 0 0 0 .176-.176.176-.348.707-.348 1.59.176 2.121-.867-.531-1.563-1.59-1.738-2.828-.172-1.414.87-2.648 2.085-3.004 2.43-.883 5.387-.176 6.774 1.945 1.219 2.118 1.738 4.59 1.39 6.887-.347 2.3-1.566 4.242-3.472 5.656-2.606 1.766-5.734 2.649-9.035 2.297-2.953-.176-6.078-.707-8.688-1.945-4.687-1.941-9.031-4.414-13.722-6.008-1.563-.351-3.473-.879-5.04-1.059h-3.82c-1.562.18-3.125.532-4.515 1.06-4.864 1.593-9.207 4.241-13.899 6.187-3.996 1.59-8.336 2.296-12.504 1.414-2.433-.356-4.691-1.594-6.258-3.535a7.888 7.888 0 0 1-1.906-5.829c-.176-2.12.344-4.066 1.563-5.656 1.214-1.59 3.129-2.297 5.035-2.121 1.215 0 2.262.531 3.129 1.59.52.887.695 1.77.347 2.828-.347.883-.87 1.59-1.566 1.945.352-.53.523-1.414.172-2.12-.516-.708-1.559-.884-2.254-.356-.176 0-.176.18-.348.18-.87.882-.87 2.296-.347 3.355.695 1.059 1.738 1.766 2.949 1.945 1.742 0 3.48-.886 4.691-2.125 2.606-2.648 5.387-4.945 8.34-7.242 2.781-1.941 5.906-2.828 8.86-3zm0-30.215c2.782-.176 5.735.887 7.993 2.828 1.039.883 2.086 2.118 2.777 3.532 1.39-2.297 3.477-4.239 6.082-5.297a15.25 15.25 0 0 1 9.73 0 20.22 20.22 0 0 1 7.293 4.77 62.33 62.33 0 0 0 5.04 4.593c.867.883 1.91 1.414 2.953 1.766 1.562.53 3.297 0 4.343-1.059.868-1.063.868-2.828-.175-4.066-.696-.528-1.563-.528-2.258.18 0 0 0 .175-.176.175-.348.707-.348 1.59.176 2.121-.867-.531-1.563-1.59-1.738-2.828-.172-1.414.87-2.652 2.085-3.004 2.43-.883 5.387-.176 6.778 1.942 1.215 2.12 1.734 4.597 1.387 6.894-.348 2.297-1.563 4.238-3.473 5.652-2.606 1.77-5.734 2.649-9.035 2.297-2.953-.175-6.078-.707-8.684-1.941-4.691-1.945-9.035-4.418-13.723-6.008-1.562-.355-3.476-.887-5.039-1.062h-3.82c-1.566.175-3.129.53-4.52 1.062-4.863 1.59-9.206 4.238-13.898 6.18-3.992 1.593-8.336 2.297-12.504 1.414-2.433-.352-4.691-1.586-6.258-3.531a7.913 7.913 0 0 1-1.906-5.832C-.14 8.773.38 6.832 1.598 5.242c1.214-1.59 3.129-2.297 5.035-2.12 1.215 0 2.262.53 3.129 1.589.52.887.695 1.766.347 2.828-.347.883-.87 1.59-1.566 1.941.352-.527.523-1.41.172-2.117-.516-.707-1.559-.886-2.254-.351-.176 0-.176.172-.348.172-.87.882-.87 2.296-.347 3.359.695 1.059 1.738 1.766 2.949 1.941 1.742 0 3.48-.882 4.691-2.117 2.606-2.648 5.387-4.949 8.34-7.246 2.781-1.941 5.91-2.824 8.86-3.004zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="51"><path style="stroke:none;fill-rule:nonzero;fill:#c19770;fill-opacity:1" d="M30.605 30.332c2.782-.18 5.735.879 7.993 2.824 1.039.883 2.086 2.121 2.777 3.535 1.39-2.296 3.477-4.242 6.082-5.3a15.304 15.304 0 0 1 9.73 0 20.188 20.188 0 0 1 7.293 4.77 62.33 62.33 0 0 0 5.04 4.593c.867.883 1.91 1.41 2.953 1.766 1.562.53 3.297 0 4.343-1.06.868-1.058.868-2.827-.175-4.062-.696-.53-1.563-.53-2.258.176 0 0 0 .176-.176.176-.348.707-.348 1.59.176 2.121-.867-.531-1.563-1.59-1.738-2.828-.172-1.414.87-2.648 2.085-3.004 2.43-.883 5.387-.176 6.774 1.945 1.219 2.118 1.738 4.59 1.39 6.887-.347 2.3-1.566 4.242-3.472 5.656-2.606 1.766-5.734 2.649-9.035 2.297-2.953-.176-6.078-.707-8.688-1.945-4.687-1.941-9.031-4.414-13.722-6.008-1.563-.351-3.473-.879-5.04-1.059h-3.82c-1.562.18-3.125.532-4.515 1.06-4.864 1.593-9.207 4.241-13.899 6.187-3.996 1.59-8.336 2.296-12.504 1.414-2.433-.356-4.691-1.594-6.258-3.535a7.888 7.888 0 0 1-1.906-5.829c-.176-2.12.344-4.066 1.563-5.656 1.214-1.59 3.129-2.297 5.035-2.121 1.215 0 2.262.531 3.129 1.59.52.887.695 1.77.347 2.828-.347.883-.87 1.59-1.566 1.945.352-.53.523-1.414.172-2.12-.516-.708-1.559-.884-2.254-.356-.176 0-.176.18-.348.18-.87.882-.87 2.296-.347 3.355.695 1.059 1.738 1.766 2.949 1.945 1.742 0 3.48-.886 4.691-2.125 2.606-2.648 5.387-4.945 8.34-7.242 2.781-1.766 5.906-2.828 8.86-3zm0-30.215c2.782-.176 5.735.887 7.993 2.828 1.039.883 2.086 2.118 2.777 3.532 1.39-2.297 3.477-4.239 6.082-5.297a15.25 15.25 0 0 1 9.73 0 20.22 20.22 0 0 1 7.293 4.77 62.33 62.33 0 0 0 5.04 4.593c.867.883 1.91 1.414 2.953 1.766 1.562.53 3.297 0 4.343-1.059.868-1.063.868-2.828-.175-4.066-.696-.528-1.563-.528-2.258.18 0 0 0 .175-.176.175-.348.707-.348 1.59.176 2.121-.867-.531-1.563-1.59-1.738-2.828-.172-1.414.87-2.652 2.085-3.004 2.43-.883 5.387-.176 6.778 1.942 1.215 2.12 1.734 4.597 1.387 6.894-.348 2.297-1.563 4.238-3.473 5.652-2.606 1.77-5.734 2.649-9.035 2.297-2.953-.175-6.078-.707-8.684-1.941-4.691-1.945-9.035-4.418-13.723-6.008-1.562-.355-3.476-.887-5.039-1.062h-3.82c-1.566.175-3.129.53-4.52 1.062-4.863 1.59-9.206 4.238-13.898 6.18-3.992 1.593-8.336 2.297-12.504 1.414-2.433-.352-4.691-1.586-6.258-3.531a7.913 7.913 0 0 1-1.906-5.832C-.14 8.773.38 6.832 1.598 5.242c1.214-1.59 3.129-2.297 5.035-2.12 1.215 0 2.262.53 3.129 1.589.52.887.695 1.766.347 2.828-.347.883-.87 1.59-1.566 1.941.352-.527.523-1.41.172-2.117-.516-.707-1.559-.886-2.254-.351-.176 0-.176.172-.348.172-.87.882-.87 2.296-.347 3.359.695 1.059 1.738 1.766 2.949 1.941 1.742 0 3.48-.882 4.691-2.117 2.606-2.648 5.387-4.949 8.34-7.246 2.781-1.766 5.91-2.824 8.86-3.004zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="59" xmlns="http://www.w3.org/2000/svg"><path d="M59 0H13.41C6.613 0 0 2.668 0 10.668V64h48.273V10.668H6.613c0-3.914 2.684-5.336 5.367-5.336h41.477v53.336l5.363-5.336V0zm0 0" fill="#c93"/><path d="M21.992 40.18c0-5.512 6.434-6.403 6.434-10.493 0-1.777-1.61-3.199-3.754-3.199-2.324.18-4.11 1.778-4.11 1.778L17.88 24.89s2.683-2.848 7.332-2.848c4.289 0 8.402 2.668 8.402 7.289 0 6.402-6.797 7.113-6.797 11.203v1.422h-4.824zm0 5.152h4.824v4.445h-4.824zm0 0" fill="#fff"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="59"><path style="stroke:none;fill-rule:nonzero;fill:#8f4e8b;fill-opacity:1" d="m.469 59 19.367-29.5L.469 0h14.379l19.367 29.5L14.848 59zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#8f4e8b;fill-opacity:1" d="m19.836 59 19.363-29.5L19.836 0h14.379l38.73 59h-14.57L46.293 40.633 34.215 59zm46.59-17.191-6.328-9.965H82.53v9.965zm-9.59-14.653-6.516-9.965h32.211v9.965zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="59"><path style="stroke:none;fill-rule:nonzero;fill:#8f4e8b;fill-opacity:1" d="m.469 59 19.367-29.5L.469 0h14.379l19.367 29.5L14.848 59zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#8f4e8b;fill-opacity:1" d="m19.836 59 19.363-29.5L19.836 0h14.379l38.73 59h-14.57L46.293 40.633 34.215 59zm46.59-17.191-6.328-9.965H82.53v9.965zm-9.59-14.653-6.516-9.965h32.211v9.965zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#d75b26;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#d75b26;stroke-opacity:1;stroke-miterlimit:10" d="M11.241 25.867c-.498 0-1.1-.2-1.5-.6l-8.398-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-7 6.9 6.9 6.9c.8.8.8 2.098 0 3-.4.4-.9.6-1.402.6zm25 0c-.5 0-1.099-.2-1.499-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.3c-.4.5-.898.7-1.5.7zm-16.698 4.1c-.2 0-.402 0-.7-.1-1.1-.399-1.7-1.5-1.3-2.599l8.399-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#d75b26;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#d75b26;stroke-opacity:1;stroke-miterlimit:10" d="M11.241 25.867c-.498 0-1.1-.2-1.5-.6l-8.398-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-7 6.9 6.9 6.9c.8.8.8 2.098 0 3-.4.4-.9.6-1.402.6zm25 0c-.5 0-1.099-.2-1.499-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.3c-.4.5-.898.7-1.5.7zm-16.698 4.1c-.2 0-.402 0-.7-.1-1.1-.399-1.7-1.5-1.3-2.599l8.399-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M33.383.531c.176-.707.527-.707.699 0l7.176 22.582c.176.707.875 1.235 1.574 1.235h23.45c.698 0 .698.351.175.707L47.383 38.816c-.524.528-.7 1.235-.524 1.942l7.172 22.582c.176.707 0 .883-.523.351L34.434 49.754c-.524-.352-1.399-.352-1.926 0L13.438 63.69c-.528.356-.876.176-.528-.351l7.176-22.582c.176-.707 0-1.414-.527-1.942L.489 24.88c-.528-.356-.352-.707.175-.707H24.11c.7 0 1.399-.531 1.575-1.235zm0 0" fill="#8ed200"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M33.383.531c.176-.707.527-.707.699 0l7.176 22.582c.176.707.875 1.235 1.574 1.235h23.45c.698 0 .698.351.175.707L47.383 38.816c-.524.528-.7 1.235-.524 1.942l7.172 22.582c.176.707 0 .883-.523.351L34.434 49.754c-.524-.352-1.399-.352-1.926 0L13.438 63.69c-.528.356-.876.176-.528-.351l7.176-22.582c.176-.707 0-1.414-.527-1.942L.489 24.88c-.528-.356-.352-.707.175-.707H24.11c.7 0 1.399-.531 1.575-1.235zm0 0" fill="#8ed200"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M5.102 59.219v-10.11h11.605v10.11zm14.066 0v-10.11h12.836v10.11zM5.102 46.805V35.457h11.605v11.348zm14.066 0V35.457h12.836v11.348zM5.102 33.152V23.047h11.605v10.105zM34.645 59.22v-10.11H47.48v10.11zM19.168 33.152V23.047h12.836v10.105zm30.95 26.067v-10.11h11.605v10.11zM34.644 46.805V35.457H47.48v11.348zm-14.07-30.496c0 .53-.528 1.062-1.231 1.062h-2.637c-.703 0-1.23-.531-1.23-1.062V6.203c0-.535.527-1.066 1.23-1.066h2.461c.703 0 1.23.531 1.23 1.066V16.31zm29.542 30.496V35.457h11.606v11.348zM34.645 33.152V23.047H47.48v10.105zm15.472 0V23.047h11.606v10.105zm1.406-16.843c0 .53-.527 1.062-1.23 1.062h-2.637c-.703 0-1.23-.531-1.23-1.062V6.203c0-.535.527-1.066 1.23-1.066h2.637c.703 0 1.23.531 1.23 1.066zM67 14.004c0-2.484-2.285-4.434-5.102-4.434h-5.097V6.203c0-3.016-2.813-5.676-6.508-5.676h-2.637c-3.515 0-6.508 2.48-6.508 5.676V9.57H25.676V6.203c0-3.016-2.817-5.676-6.508-5.676h-2.637c-3.52 0-6.508 2.48-6.508 5.676V9.57H5.102C2.285 9.57 0 11.7 0 14.004v45.035c0 2.484 2.285 4.434 5.102 4.434h56.62c2.817 0 5.102-2.13 5.102-4.434V14.004zm0 0" fill="#111"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="48" xmlns="http://www.w3.org/2000/svg"><path d="M47.66 38.852c0 3.89-2.894 17.074-2.894 17.074-.68 3.21-3.575 6.594-7.317 6.594H19.75c-2.555 0-4.938-1.524-5.957-3.891 0 0-8.68-15.887-12.082-21.637-2.383-4.054-2.383-4.054.68-5.746a2.905 2.905 0 0 1 1.703-.508c1.191 0 2.039.676 2.89 1.692l5.278 6.086 1.531 2.027V3.863c0-1.86 1.703-3.383 3.742-3.383 1.875 0 3.406 1.524 3.406 3.383l.68 23.664h1.531l.34-4.054c0-1.86 1.531-3.383 3.406-3.383 1.872 0 3.403 1.523 3.403 3.383l.34 4.898h1.53l.34-3.21c0-1.86 1.532-3.38 3.407-3.38 1.871 0 3.402 1.52 3.402 3.38l.34 3.21v.848h1.192l.34-1.692c0-1.859 1.53-3.379 3.406-3.379 1.87 0 3.402 1.52 3.402 3.38-.34 0-.34 7.437-.34 11.324zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0" fill="#bababa"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32C64 14.43 49.57 0 32 0S0 14.43 0 32s14.43 32 32 32c17.57-.629 32-14.43 32-32zm0 0" fill="#3c3"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zm-6.4-10.5c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="71" xmlns="http://www.w3.org/2000/svg"><path d="m12.225 17.126-7.7 7.598c-.9 1.001-1.4 2.201-1.4 3.501 0 1.2.5 2.5 1.4 3.4l.1.1c.9.9 2.2 1.4 3.4 1.4 1.3 0 2.5-.5 3.5-1.4l8.6-8.6 9.3-9.3c.4-.4.6-1 .6-1.5s-.2-1.101-.6-1.5-1-.6-1.5-.6-1.1.2-1.5.6l-13.4 13.3c-.6.6-1.5.6-2.2 0-.6-.6-.6-1.6 0-2.2l13.3-13.299c1-1 2.3-1.502 3.7-1.502 1.3 0 2.7.501 3.7 1.502 1 .998 1.5 2.299 1.5 3.698 0 1.3-.5 2.7-1.5 3.701l-9.3 9.3-8.6 8.6c-1.5 1.5-3.6 2.3-5.6 2.3-2 0-4-.7-5.499-2.2l-.1-.1c-1.5-1.5-2.3-3.501-2.3-5.6 0-2.001.8-4.1 2.3-5.6l8.6-8.599 10.899-11c2-2.001 4.6-3.002 7.3-3.002s5.3 1 7.3 3.002c1.999 1.999 2.999 4.6 2.999 7.3 0 2.6-1 5.3-3 7.299l-14.9 14.9c-.6.599-1.599.599-2.199 0-.6-.6-.6-1.6 0-2.201l14.9-14.898c1.4-1.4 2.1-3.301 2.1-5.1s-.7-3.701-2.1-5.101-3.3-2.1-5.2-2.1c-1.9 0-3.7.7-5.1 2.1zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".25" transform="matrix(1.7579 0 0 1.76066 .65 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="58" xmlns="http://www.w3.org/2000/svg"><path d="M.624 29.625V.725h3.9v28.9zm19.3.4c-6.5 0-9.899-4-9.899-9.9s3.4-10.6 9.9-10.6c1.1 0 2.3.1 3.5.4v-9.3h3.7v28c-1.6.8-4.2 1.4-7.2 1.4zm3.501-18.2c-.9-.2-1.9-.4-2.9-.4-5.1 0-6.8 4-6.8 8.3 0 4.7 1.8 8.1 6.4 8.1 1.5 0 2.5-.2 3.3-.6zm0 0" fill="#db007b" stroke="#db007b" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(2.09009 0 0 2.08311 0 .076)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M35.2 13.332c-1.067 0-1.954.355-2.665 1.066-.715.711-1.07 1.602-1.07 2.668s.355 1.957 1.07 2.668c.711.711 1.598 1.067 2.664 1.067 1.067 0 1.957-.356 2.668-1.067.711-.71 1.067-1.601 1.067-2.668s-.356-1.957-1.067-2.668c-.535-.71-1.422-1.066-2.668-1.066zm1.777 12.09-.176.355h.176zm-.176.355c-3.555.535-6.934.711-10.489 1.246l-.355 1.598h.887c.535 0 1.066.18 1.422.535s.535.711.535 1.067c0 .53-.18.886-.535 2.132l-3.73 12.622C24.18 46.043 24 46.754 24 47.465c0 1.07.355 1.781 1.066 2.492.711.711 2.844.887 3.91.887 3.024 0 8-1.598 10.669-6.223l-2.133-1.242c-1.067 1.777-3.024 3.02-4.09 3.555-1.067.53-1.602.355-1.777.355-.18 0-.356 0-.536-.18-.175-.175-.175-.355-.175-.53 0-.356.175-1.067.53-2.134zm0 0"/><path d="M32 1.777C15.29 1.777 1.777 15.29 1.777 32S15.29 62.223 32 62.223 62.223 48.71 62.223 32 48.71 1.777 32 1.777zm0 3.38c14.934 0 26.844 12.09 26.844 26.843 0 14.934-12.09 26.844-26.844 26.844S5.156 46.754 5.156 32C5.156 17.066 17.066 5.156 32 5.156zm0 0" stroke="#999" stroke-miterlimit="10" stroke-width="3.55556"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M47.914 26.813V9.688L31.828 0 15.914 9.512v17.3L0 35.462v19.2L16.434 64 32 55.004 47.566 64 64 54.66V35.633zm-2.941 0-11.59 6.398V20.066l11.59-6.746zM31.828 3.633l11.414 6.918-11.414 6.746-11.07-6.918zM4.844 36.324l12.8-6.746 11.243 6.399-12.453 7.265zm12.972 9.512 12.625-7.262v13.492l-12.625 7.438zm17.47-9.86 11.245-6.398 12.797 6.918-11.762 6.746zm25.6 16.782-11.761 6.746V45.836l11.762-6.742zm0 0" fill="#938886"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M39.06.14s-7.599 3.599-13.7 4.4c-6.1.798-17.9 1.6-20.2 1-2.3-.601-4-1.201-4-1.201l.501 1.6-1.5 1.3.3.5h.6l1.9.201.3.799 1.1.1.501 1.8s2.2.2 2.9.2c.698 0 1.999-.101 1.999-.101v.7l.501.101v.801l-1.1.9h.3v.298s-2.4.201-3.302 0c-1-.098-1.098-.098-1.098-.098l-.101.098v.402h.2l.101 1.798 4.899-.198-.499 6.6v.798l-4.101-.198v-1.7h.9l.1-.701.801-.2.099-.2-3.2-.7-2.4.7.4.3h.4v.6h.8v1.6l-.899.2.2.4.2.1v1.301h.6v5.9l-.901.1.101 1.7.6.098-.101 4h2.5l-.499-3.8 3.798-.1-.299 2.1-.4 1.502h3.801v-3.702l2.599-.198-.099 2.398-.1 1.401h2.198l-.099-3.8h.301l.299-1.9h-.4l-.1-.8-.2-2.2.1-2.5h.501v-1.5h-.602l.101-1.8.701-.1v-.601l.4-.098.4-.301-2.5-.5-2.5.6.2.4h.6l.098.7h.701v1.6l-3-.1.1-1.7.101-1.5v-2l.1-2.099 5.999-.301 7.1-.4.101 1.299-.301 3.202-.099 2.999h-2.6v-2.1h.999v-.6l.4-.1v-.101h.4l.2-.299-3-.7-2.9.6.2.399h.3v.2h.4v.701h.9v1.9h-.9v.4h.3v1.6h.6l-.099 6.4-.802.2v.3h.301v1.6h.501l-.2 2.3-.2 1.9 3.1.101-.101-1.7-.2-1.401v-1.2h2.601l-.101 1.9v2l1.5.1 2-.1.6-.2-.4-1.1-.1-1.7-.2-1.2 2.5-.1-.1 1.5v2.3l1 .1h1.001l.499-.2-.3-2.2-.1-1.6h.4v-1.5h.1v-.299h-.5l-.099-1.1V24.74h.4v-1.6h.2v-.301h-.6V21.04l.898-.1-.098-.601h.4v-.1l.498-.3-2.7-.6-2.5.6.2.3h.402v.1h.4v.7h.898l.102 1.7H28.06l-.4-2.6-.299-1.4-.2-2.501.1-1.399 6.3-.5v-2.1l.298-.1v-.301l-.2-.098s-3.5.5-4.6.6c-.298 0-.298 0-.498.098v-.4l-1.3-.7v-.799h.5v-1.1s2.7-.2 3.7-.399c.999-.2 2.398-.502 2.398-.502l.701-1.798 1.3-.402.1-.299 3-.801.3-.299-1.1-2.499.1-.6.6-.301.4-1.299zM24.662 9.638v.901h.699v.9l-1.3.901-.1.299h.501v.2l-4.2.2v-.6l.2-.3v-.6l.099-.299v-.402l.2-.698zm-8.502.801-.098.6.298.4v.7l.301.5-.1.5.2.301-4.199.199v-.1l.499-.099v-.2l-1.099-.601-.1-.9h.5v-1.001zm6.101 14.1 2.5.2-.099 3.4.299 2.8-2.7-.1zm-16.9.1 4 .2-.3 2.3v2l.3 1.1-4 .2zm25.9 0v5.8l-2.6.2-.1-3.1-.3-2.8zm-19.5.1h3v5.4h-2.799l-.1-1.7v-1.7zm0 0" fill="#b41717" stroke="#b41717" stroke-miterlimit="10" stroke-width=".25" transform="matrix(1.7 0 0 1.71166 0 .105)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zm0 0" fill="#265db4" stroke="#265db4" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M37.8 64.8c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><g fill="#265db4" stroke="#265db4"><path d="M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zm0 0" fill="#265db4" stroke="#265db4" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M37.8 64.8c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><g fill="#265db4" stroke="#265db4"><path d="M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="75" xmlns="http://www.w3.org/2000/svg"><path d="M.5 19v-4.1c.9-.1 1.6-.2 2-.4.4-.2.8-.6 1.2-1.001.4-.5.5-1.1.7-1.9.1-.6.2-1.499.2-2.799 0-2.201.1-3.7.4-4.6.2-.8.6-1.6 1.2-2 .5-.5 1.4-.9 2.5-1.2.7-.2 1.9-.4 3.5-.4h.9v3.9c-1.3 0-2.2.1-2.6.3-.4.2-.6.4-.9.6-.2.3-.3.7-.3 1.501 0 .8-.1 2-.2 4.099-.101 1.2-.2 2-.4 2.801-.301.6-.6 1.2-1 1.8-.4.4-1 .9-1.8 1.399.7.4 1.3.8 1.8 1.3s.8 1.2 1.1 1.899c.3.702.4 1.802.4 3.001.1 1.9.1 3.1.1 3.599 0 .702.1 1.202.3 1.602.2.4.5.5.9.6.4.2 1.2.3 2.6.3v4.098h-1c-1.6 0-2.9-.1-3.701-.4-.9-.3-1.6-.6-2.2-1.2-.6-.6-.999-1.2-1.2-1.999-.198-.8-.299-2.1-.299-4 0-2-.1-3.5-.3-4.1-.3-.9-.7-1.601-1.201-2-.698-.5-1.5-.7-2.7-.7zm39.1 0c-.9.1-1.6.2-2 .4s-.8.6-1.2 1.001c-.4.5-.5 1.1-.7 1.9-.099.6-.2 1.499-.2 2.799 0 2.201-.1 3.7-.4 4.6-.2.9-.6 1.6-1.2 2-.5.5-1.4.9-2.5 1.2-.7.2-1.9.4-3.5.4h-.999v-4.1c1.298 0 2.1-.1 2.599-.3s.7-.4.899-.6c.2-.3.301-.7.301-1.501 0-.6.1-2 .2-3.999.099-1.2.3-2.1.5-2.8.3-.7.6-1.3 1.1-1.9.4-.5 1-.9 1.7-1.3-.901-.6-1.6-1.1-2-1.6-.5-.7-1-1.801-1.201-2.8-.199-.8-.299-2.6-.299-5.2 0-.8-.1-1.4-.301-1.8-.199-.3-.4-.5-.799-.6-.2-.3-1-.3-2.5-.3v-4h.999c1.602 0 2.9.1 3.7.4.902.3 1.6.6 2.2 1.2.6.6 1.002 1.2 1.2 2 .201.8.402 2.1.402 4 0 2 .098 3.4.299 4.1.299.9.7 1.601 1.2 1.9.5.4 1.401.6 2.5.6.1.1 0 4.3 0 4.3zm0 0" fill="#307ac6" stroke="#307ac6" stroke-miterlimit="10" transform="matrix(1.86825 0 0 1.87558 0 .209)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="77" xmlns="http://www.w3.org/2000/svg"><g fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".5"><path d="M16.4 67.5v-4.1c.901-.1 1.6-.2 2-.4.4-.2.8-.6 1.2-1 .4-.5.5-1.1.7-1.9.1-.6.2-1.5.2-2.8 0-2.2.1-3.7.4-4.6.2-.8.599-1.6 1.2-2 .5-.5 1.4-.9 2.5-1.2.7-.2 1.9-.4 3.5-.4h.9V53c-1.3 0-2.2.1-2.6.3-.4.2-.6.4-.9.6-.2.3-.3.7-.3 1.5 0 .801-.1 2-.2 4.1-.1 1.2-.2 2-.4 2.8-.3.6-.6 1.2-1 1.8-.4.4-1 .9-1.8 1.401.7.4 1.301.8 1.8 1.299.5.499.801 1.2 1.1 1.9.3.7.4 1.8.4 3 .1 1.9.1 3.1.1 3.6 0 .7.1 1.2.3 1.6.199.4.5.5.9.6.4.2 1.2.3 2.6.3v4.1h-1c-1.6 0-2.9-.1-3.7-.4-.9-.3-1.6-.6-2.2-1.199-.601-.601-1-1.2-1.2-2.002-.2-.799-.3-2.1-.3-4 0-2-.1-3.5-.3-4.1-.3-.898-.7-1.6-1.2-1.999-.7-.5-1.5-.7-2.7-.7zm39.1 0c-.9.1-1.6.2-2 .4-.401.2-.8.6-1.2 1-.4.5-.499 1.1-.7 1.9-.1.6-.2 1.5-.2 2.8 0 2.2-.1 3.7-.4 4.6-.2.9-.6 1.6-1.2 2-.5.5-1.4.9-2.501 1.2-.698.2-1.9.4-3.5.4h-1v-4.1c1.3 0 2.101-.1 2.6-.3.501-.2.7-.4.902-.6.2-.3.3-.7.3-1.5 0-.601.098-2 .199-4 .1-1.2.3-2.099.499-2.8.302-.7.602-1.3 1.1-1.9.401-.5 1.001-.9 1.701-1.3-.9-.6-1.6-1.1-2-1.6-.5-.7-1-1.8-1.2-2.8-.2-.8-.3-2.6-.3-5.2 0-.8-.1-1.401-.3-1.8-.2-.3-.4-.5-.8-.6-.2-.3-1-.3-2.5-.3v-4h1c1.6 0 2.9.1 3.7.4.9.3 1.6.6 2.2 1.199.6.601 1 1.2 1.2 2.002.2.799.4 2.1.4 4 0 2 .1 3.4.301 4.1.3.898.698 1.6 1.2 1.9.499.399 1.398.598 2.499.598.1.1 0 4.302 0 4.302zm0 0" transform="matrix(1.90195 0 0 1.91617 -29.917 -93.413)"/><path d="M44.1 67.1c-.7-.3-1.2-.9-1.2-1.599 0-.701.5-1.4 1.2-1.6.299-.1.4-.3.299-.502-.3-.799-.499-1.598-.998-2.2-.1-.3-.4-.3-.602-.2-.2.1-.499.3-.799.3-1 0-1.7-.799-1.7-1.7 0-.3.1-.599.3-.799.1-.3 0-.4-.2-.6-.7-.4-1.499-.7-2.2-1-.3-.1-.4.1-.5.3-.3.7-.9 1.2-1.6 1.2s-1.4-.5-1.6-1.2c-.101-.3-.3-.4-.5-.3-.8.3-1.6.5-2.2 1-.301.1-.301.4-.2.6.2.3.3.5.3.8 0 1-.8 1.7-1.699 1.7-.302 0-.602-.1-.801-.3-.3-.1-.4 0-.6.2-.4.7-.7 1.5-1 2.2-.1.3.1.4.3.5.7.3 1.2.9 1.2 1.601 0 .7-.5 1.398-1.2 1.598-.3.1-.4.302-.3.502.3.799.5 1.6 1 2.2.1.299.4.299.6.2.3-.2.5-.3.801-.3.998 0 1.699.799 1.699 1.7 0 .3-.1.599-.3.799-.101.3 0 .4.2.6.7.4 1.5.7 2.2 1 .2 0 .399-.1.399-.3.302-.7.902-1.2 1.602-1.2.698 0 1.399.5 1.6 1.2.098.3.3.4.499.3.801-.3 1.6-.5 2.2-1 .3-.1.3-.4.2-.6-.1-.3-.301-.5-.301-.8 0-1 .801-1.7 1.7-1.7.3 0 .6.1.802.3.3.1.4 0 .6-.2.4-.701.7-1.5 1-2.2.199-.1.098-.4-.202-.5zm-8.3 1c-1.5 0-2.699-1.2-2.699-2.701 0-1.498 1.2-2.699 2.699-2.699 1.499 0 2.7 1.2 2.7 2.699.1 1.5-1.201 2.701-2.7 2.701zm0 0" transform="matrix(1.90195 0 0 1.91617 -29.917 -93.413)"/></g></svg> |
| 1 | ||
| 1 | <svg height="63" width="49" xmlns="http://www.w3.org/2000/svg"><path d="M24.842 21.014c2.2-.7 4.4.7 4.202 2.8-.2 2.4-3.302 3.601-5.1 4.2l.1-.099-.1-.101c1.3-1 3.898-2.199 3.7-4.6-.1-1.2-1.001-2.098-2.702-2v-.2zm-16.099.401.1-.101c-1 0-1.901.4-2.701.7-.798.3-1.8.401-2.198 1.3.399.7 1.4.7 2.398.802 3.4.5 8.302.398 11.701 0 1.799-.201 3.4-.401 4.2-1.201l-.1-.101.1-.1c-3.4.402-7.8 1-11.9.8-1.301-.099-3-.099-3.7-.8.4-.7 1.4-.9 2.1-1.299zm19.9 14.099v-.1c-5.1 2.5-13.201 2.8-20.5 2.201l.1.1-.1.2c2.999.5 6.9.7 10.7.398 3.7-.198 8.199-.698 9.9-2.698zm-14.4-15.398h.1c-.8-1.802-2.3-2.602-2.499-4.7-.2-1.902.7-3.102 1.598-4 1.101-1.201 2.702-2.201 3.901-3.5 1.6-1.803 3.4-4.5 1.9-7.102l-.101.101-.299-.101c.4 2.5-.6 4.101-1.901 5.4-.999 1.201-2.6 2.201-4 3.3-1.6 1.3-3.7 2.901-3.1 5.3.502 2.302 2.8 3.9 4.102 5.4zm8-11.602-.1-.099c-2.7 1-6.701 2.6-7.1 5.698-.1 1.503.399 2.602.9 3.401.4.602 1.1 1 1.3 1.901.2.8 0 1.6-.2 2.2h.1l.1.1c1.099-.8 2.2-1.901 1.899-3.401-.198-1.5-1.9-2.5-2.1-3.899-.1-.802.1-1.5.401-1.9 1.1-1.701 3.5-2.902 4.8-4zm-13.8 17.401-.101-.101c-.5.301-1.5.4-1.4 1.2.1.8 1.5 1 2.2 1.2 3.7.8 9.2.3 11.902-.599l-.1-.101.1-.099c-.301-.101-.7-.7-1.3-.7-.502-.1-1.6.299-2.602.4-1.6.199-3.299.3-4.799.199-1.101-.1-4.5-.1-3.9-1.399zm.9 4.099.1-.1c-.6.201-1.3.4-1.3 1.1 0 .601 1.2 1 1.9 1.3 3.299 1 8.5.4 10.9-.699-.2-.302-.6-.4-.9-.601-.4-.1-.7-.3-1.1-.5-2.001.5-5.1.7-7.5.4-.7-.1-1.701-.1-1.9-.799zm17.699 3.2-.1-.099c-.098 1-1.3 1.1-2.1 1.3-.898.2-1.9.398-2.998.5-4.902.599-11.5.898-16.302 0-.898-.102-2.2-.401-2.499-1.102.4-.698 1.5-.8 2.399-1.198l-.098-.101.098-.1c-1.2.1-2.1.4-2.998.701-.7.3-1.701.698-1.902 1.5.6.8 1.801.8 2.8 1 6.6 1 15.7 1.198 21.402-.7.998-.401 3.098-1 2.1-1.901zm-3.7-5.099c.2 0 .4-.101.702-.2m.898-6.8c-.198 0-.399.1-.7.1m-2.2 1.7c.1 0 .2-.101.401-.101m-12.5-1.6c-.4.1-.8.1-1.3.201m-2.2 15.898c.499.2 1.099.2 1.7.401m20.5-2.2c.1-.1.2-.2.3-.399M19.043.814c0-.099-.098-.3-.098-.399m-4.702 19.7c.1.1.301.4.402.5m2.298 1.199c.1-.1.2-.198.3-.399m5.7-13.2c-.3.099-.499.2-.7.398m-1.198 18.802h.198m-12.6-1.8c0 .1-.2.1-.2.199m.9 4.2.101-.1m-2.8 2.701c-.4 0-.7.1-1 .1m21.399.5c0-.1-.1-.1-.1-.1h-.098" fill="#666" stroke="#666" stroke-miterlimit="10" transform="matrix(1.63519 0 0 1.61722 .336 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><g fill="#72bed3" stroke="#72bed3" stroke-miterlimit="10" stroke-width=".75"><path d="M56.4 65.6c0-2.7-3.4-5.3-8.5-6.9 1.2-5.3.7-9.5-1.7-10.9-.6-.3-1.2-.4-1.9-.4v1.9c.4 0 .7.1.9.201 1.1.7 1.7 3.098 1.2 6.299-.1.8-.2 1.6-.4 2.4-1.7-.401-3.4-.7-5.3-.9-1.099-1.6-2.3-2.9-3.4-4.2 2.8-2.5 5.3-3.9 7-3.9V47.3c-2.3 0-5.3 1.7-8.301 4.5-3.098-2.8-6.098-4.4-8.3-4.4V49.3c1.802 0 4.201 1.3 7 3.9-1.198 1.2-2.3 2.6-3.4 4.2-1.9.2-3.698.399-5.3.9-.098-.9-.298-1.7-.4-2.4-.4-3.1.102-5.7 1.201-6.301.2-.098.6-.198.9-.198v-1.902c-.7 0-1.3.1-1.9.402-2.3 1.3-2.8 5.498-1.7 10.899-5.3 1.6-8.7 4.199-8.7 6.9 0 2.7 3.4 5.3 8.5 6.9-1.2 5.301-.7 9.5 1.7 10.9.6.3 1.2.401 1.9.401 2.3 0 5.3-1.7 8.301-4.5 3.098 2.8 6.098 4.4 8.3 4.4.698 0 1.299-.1 1.9-.401C48.3 82.1 48.8 77.9 47.7 72.5c5.4-1.7 8.7-4.2 8.7-6.9zm-10.8-5.601c-.3 1.1-.7 2.202-1.1 3.3-.3-.699-.701-1.299-1.099-1.998-.4-.7-.8-1.302-1.2-2.002 1.2.2 2.3.401 3.399.7zM41.8 68.9c-.7 1.099-1.3 2.2-1.999 3.098-1.2.1-2.401.2-3.7.2s-2.501-.1-3.8-.1c-.7-.998-1.3-1.999-2.002-3.1-.7-1.099-1.198-2.2-1.799-3.299.6-1.1 1.1-2.2 1.799-3.3.702-1.1 1.302-2.2 2.001-3.101 1.2-.098 2.5-.098 3.8-.098s2.5.098 3.8.098c.7 1 1.3 2.002 1.999 3.1.701 1.101 1.2 2.2 1.801 3.301-.7 1.001-1.3 2.1-1.9 3.2zm2.7-1.101c.4 1.1.9 2.2 1.199 3.3-1.1.2-2.298.4-3.4.7.4-.7.8-1.301 1.2-2 .3-.6.601-1.3 1.001-2zM36 76.7C35.2 75.9 34.4 74.9 33.7 74c.798 0 1.5.1 2.298.1.8 0 1.6 0 2.3-.1-.7.999-1.5 1.9-2.3 2.7zm-6.2-4.9c-1.2-.1-2.3-.4-3.399-.7.3-1.1.7-2.199 1.1-3.3.301.7.699 1.3 1.1 2 .4.699.8 1.3 1.2 2zM36 54.5c.8.8 1.6 1.799 2.3 2.7-.798 0-1.5-.1-2.3-.1-.798 0-1.599 0-2.298.1.7-1 1.5-1.9 2.298-2.7zm-6.2 4.9c-.4.7-.8 1.301-1.2 2-.4.7-.7 1.3-1.1 2-.4-1.101-.899-2.2-1.2-3.3 1.2-.4 2.3-.6 3.5-.7zm-7.5 10.4c-3-1.2-4.9-2.9-4.9-4.2 0-1.3 1.9-3 4.9-4.2.701-.3 1.502-.601 2.3-.9.4 1.701 1.102 3.3 1.9 5.1a46.22 46.22 0 0 0-1.9 5c-.798-.199-1.599-.4-2.3-.8zm4.4 11.899c-1.1-.7-1.7-3.098-1.198-6.299.099-.8.2-1.6.4-2.4 1.7.401 3.4.7 5.3.9 1.1 1.6 2.3 2.9 3.4 4.2-2.8 2.5-5.3 3.9-7 3.9-.2-.1-.6-.1-.902-.301zM46.5 75.4c.4 3.1-.099 5.7-1.198 6.299-.2.1-.6.2-.902.2-1.799 0-4.2-1.299-7-3.9 1.201-1.199 2.3-2.599 3.4-4.2 1.9-.2 3.701-.398 5.3-.9.2.8.301 1.701.4 2.501zm3.2-5.6c-.7.3-1.5.601-2.298.9-.4-1.701-1.102-3.3-1.9-5.1a46.22 46.22 0 0 0 1.9-5c.798.199 1.599.6 2.298.899C52.7 62.7 54.6 64.4 54.6 65.7c-.102 1.2-2.002 2.9-4.902 4.1zm0 0" transform="matrix(1.69822 0 0 1.71352 -24.966 -80.407)"/><path d="M39.801 65.6a3.8 3.8 0 1 1-7.601 0 3.8 3.8 0 0 1 7.601 0zm0 0" transform="matrix(1.69822 0 0 1.71352 -24.966 -80.407)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#1f385e"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="60" xmlns="http://www.w3.org/2000/svg"><path d="M.125 47.379V28.586c0-1.125.379-1.879 1.523-2.441 14.29-7.707 28.579-15.598 42.868-23.493 2.097-.937 4.004-2.254 6.48-2.254 4 .188 7.43 2.442 8.57 6.204 1.145 3.757 0 8.078-3.238 10.144-3.617 2.258-7.621 4.324-11.43 6.578C30.61 31.031 16.32 38.922 1.84 46.816c-.574.188-.953.375-1.715.563zm0 0" fill="#e88e3d"/><path d="M22.797 40.426c.57-.375.953-.563 1.144-.938 4.762-2.633 9.524-5.074 13.907-7.52.953-.562 1.715-.562 2.668.188 5.336 4.887 10.859 9.399 16.004 14.285 3.046 2.63 3.812 6.012 2.667 9.582-.953 3.57-3.62 5.641-7.43 6.204-2.476.375-4.952-.375-6.859-1.88-7.242-6.577-14.48-13.156-22.101-19.921.191.375.191.187 0 0zM.125 22.008c0-4.695-.383-9.207.191-13.528C.886 3.406 5.84.398 11.367.96c4.57.567 8.383 5.263 8 9.774 0 .563-.379 1.13-.953 1.317-5.906 3.195-11.812 6.578-17.91 9.77.191.187 0 0-.379.187zm19.242 20.297c0 4.324.192 8.082 0 12.027-.379 4.7-4.762 8.27-9.336 8.27-4.57 0-8.953-3.383-9.715-7.891-.191-1.316 0-2.258 1.332-3.008 5.336-3.008 10.86-5.828 16.196-8.832.379 0 .761-.187 1.523-.566zm0 0" fill="#5171a5"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="60" xmlns="http://www.w3.org/2000/svg"><path d="M.125 47.379V28.586c0-1.125.379-1.879 1.523-2.441 14.29-7.707 28.579-15.598 42.868-23.493 2.097-.937 4.004-2.254 6.48-2.254 4 .188 7.43 2.442 8.57 6.204 1.145 3.757 0 8.078-3.238 10.144-3.617 2.258-7.621 4.324-11.43 6.578C30.61 31.031 16.32 38.922 1.84 46.816c-.574.188-.953.375-1.715.563zm0 0" fill="#e88e3d"/><path d="M22.797 40.426c.57-.375.953-.563 1.144-.938 4.762-2.633 9.524-5.074 13.907-7.52.953-.562 1.715-.562 2.668.188 5.336 4.887 10.859 9.399 16.004 14.285 3.046 2.63 3.812 6.012 2.667 9.582-.953 3.57-3.62 5.641-7.43 6.204-2.476.375-4.952-.375-6.859-1.88-7.242-6.577-14.48-13.156-22.101-19.921.191.375.191.187 0 0zM.125 22.008c0-4.695-.383-9.207.191-13.528C.886 3.406 5.84.398 11.367.96c4.57.567 8.383 5.263 8 9.774 0 .563-.379 1.13-.953 1.317-5.906 3.195-11.812 6.578-17.91 9.77.191.187 0 0-.379.187zm19.242 20.297c0 4.324.192 8.082 0 12.027-.379 4.7-4.762 8.27-9.336 8.27-4.57 0-8.953-3.383-9.715-7.891-.191-1.316 0-2.258 1.332-3.008 5.336-3.008 10.86-5.828 16.196-8.832.379 0 .761-.187 1.523-.566zm0 0" fill="#5171a5"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M32.707 50.086c-4.18-4.148-9.402.348-13.93 4.324-5.742 5.184-9.746 8.125-13.402 4.668-2.613-2.594-2.438-5.707 0-8.992l2.438 2.25a1.847 1.847 0 0 0 2.261 0L37.406 27.27 26.09 16.03.848 43.344c-.524.691-.524 1.558 0 2.25l2.437 2.418c-4.527 5.36-2.96 10.547 0 13.484 5.918 5.879 12.535.172 17.758-4.496 4.7-4.148 7.66-6.395 9.574-4.492.695.52 1.738.52 2.262-.172.348-.691.348-1.559-.172-2.25zm-8.008-19.188c-.699.692-1.57.692-2.265 0-.696-.691-.696-1.554 0-2.246l2.265-2.246c.696-.691 1.567-.691 2.262 0 .695.692.695 1.555 0 2.246zm14.797-5.875c.348.344.695.516 1.043.516 2.262 0 4.7-.516 6.617-1.727L29.57 6.352c-1.218 2.074-1.738 4.324-1.738 6.57 0 .344.172.863.52 1.035zm10.27-21.261c-5.047-5.016-13.23-5.016-18.278 0L49.766 21.91a12.77 12.77 0 0 0 0-18.148zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="75" xmlns="http://www.w3.org/2000/svg"><path d="M.5 19v-4.1c.9-.1 1.6-.2 2-.4.4-.2.8-.6 1.2-1.001.4-.5.5-1.1.7-1.9.1-.6.2-1.499.2-2.799 0-2.201.1-3.7.4-4.6.2-.8.6-1.6 1.2-2 .5-.5 1.4-.9 2.5-1.2.7-.2 1.9-.4 3.5-.4h.9v3.9c-1.3 0-2.2.1-2.6.3-.4.2-.6.4-.9.6-.2.3-.3.7-.3 1.501 0 .8-.1 2-.2 4.099-.101 1.2-.2 2-.4 2.801-.301.6-.6 1.2-1 1.8-.4.4-1 .9-1.8 1.399.7.4 1.3.8 1.8 1.3s.8 1.2 1.1 1.899c.3.702.4 1.802.4 3.001.1 1.9.1 3.1.1 3.599 0 .702.1 1.202.3 1.602.2.4.5.5.9.6.4.2 1.2.3 2.6.3v4.098h-1c-1.6 0-2.9-.1-3.701-.4-.9-.3-1.6-.6-2.2-1.2-.6-.6-.999-1.2-1.2-1.999-.198-.8-.299-2.1-.299-4 0-2-.1-3.5-.3-4.1-.3-.9-.7-1.601-1.201-2-.698-.5-1.5-.7-2.7-.7zm39.1 0c-.9.1-1.6.2-2 .4s-.8.6-1.2 1.001c-.4.5-.5 1.1-.7 1.9-.099.6-.2 1.499-.2 2.799 0 2.201-.1 3.7-.4 4.6-.2.9-.6 1.6-1.2 2-.5.5-1.4.9-2.5 1.2-.7.2-1.9.4-3.5.4h-.999v-4.1c1.298 0 2.1-.1 2.599-.3s.7-.4.899-.6c.2-.3.301-.7.301-1.501 0-.6.1-2 .2-3.999.099-1.2.3-2.1.5-2.8.3-.7.6-1.3 1.1-1.9.4-.5 1-.9 1.7-1.3-.901-.6-1.6-1.1-2-1.6-.5-.7-1-1.801-1.201-2.8-.199-.8-.299-2.6-.299-5.2 0-.8-.1-1.4-.301-1.8-.199-.3-.4-.5-.799-.6-.2-.3-1-.3-2.5-.3v-4h.999c1.602 0 2.9.1 3.7.4.902.3 1.6.6 2.2 1.2.6.6 1.002 1.2 1.2 2 .201.8.402 2.1.402 4 0 2 .098 3.4.299 4.1.299.9.7 1.601 1.2 1.9.5.4 1.401.6 2.5.6.1.1 0 4.3 0 4.3zm0 0" fill="#7058c6" stroke="#7058c6" stroke-miterlimit="10" transform="matrix(1.86825 0 0 1.87558 0 .209)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#c93" stroke="#c93" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="45" xmlns="http://www.w3.org/2000/svg"><path d="M22.686 26.4h.8c0 2.401-.4 4.2-1.2 5.3-.801 1.1-1.8 1.7-3 1.7-1.001 0-1.9-.4-2.8-1.1-.9-.699-1.701-2.7-2.4-5.9l-2-8.9-6.902 15.599h-4.4l9.901-21.2c-.5-2.698-1.2-4.799-1.899-6.098-.701-1.3-1.7-2.002-2.7-2.002-.902 0-1.601.301-2.3 1-.6.7-1 1.701-1.1 3.1h-.8c0-2.299.5-4.1 1.4-5.4.899-1.3 1.898-2 3.2-2 .8 0 1.599.302 2.3 1.002.7.699 1.4 1.799 1.9 3.499.599 1.7 1.4 5.1 2.6 10.3l1.599 7.3c.701 3 1.4 5 2.1 6.1.7 1 1.6 1.5 2.6 1.5 1.9-.1 2.901-1.3 3.101-3.8zm0 0" fill="#066" stroke="#066" stroke-miterlimit="10" transform="matrix(1.87615 0 0 1.85407 0 .073)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M34.84 4.973 23.66 16.156a19.478 19.478 0 0 1 10.266 0c.55.184.914.364 1.281.364l5.684-5.68c3.3-3.3 8.8-3.3 12.097 0 3.301 3.297 3.301 8.797 0 12.098L41.074 34.852c-1.101 1.101-2.383 1.836-3.851 2.199-2.746.734-6.047 0-8.246-2.2-1.47-1.464-2.383-3.48-2.383-5.316-.735.367-1.285.735-1.836 1.102l-5.317 5.316c.735 1.832 2.02 3.484 3.485 4.95 2.199 2.199 4.765 3.667 7.699 4.398 4.398 1.101 9.164.55 13.016-1.832 1.28-.735 2.382-1.649 3.3-2.567L59.04 28.805c6.598-6.602 6.598-17.418 0-24.016a17.443 17.443 0 0 0-24.2.184zm0 0"/><path d="M40.156 47.867c-3.847 1.102-7.883.918-11.73-.367l-5.5 5.5c-3.301 3.3-8.801 3.3-12.098 0-3.3-3.297-3.3-8.797 0-12.098l12.098-12.097c1.101-1.102 2.383-1.836 3.851-2.2 2.746-.734 6.047 0 8.246 2.2 1.47 1.465 2.383 3.48 2.383 5.5.551-.368 1.285-.735 1.836-1.102l5.317-5.316c-.735-1.832-2.02-3.485-3.485-4.95-2.199-2.199-4.765-3.667-7.699-4.398-4.398-1.102-9.164-.55-13.016 1.832-1.28.734-2.382 1.649-3.3 2.567L4.96 35.035c-6.598 6.602-6.598 17.414 0 24.016 6.598 6.597 17.414 6.597 24.016 0zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M27.91 64A27.846 27.846 0 0 1 0 36.09C0 20.62 12.445 8 28.445 8.18c15.11.355 27.2 12.441 27.2 27.91C55.645 51.555 43.199 64 27.91 64zm11.38-47.645c-4.446 0-8.356 3.91-8.356 8.356 0 4.445 3.554 8.355 8.355 8.355 4.445 0 8.356-3.554 8.356-8.355 0-4.621-3.555-8.356-8.356-8.356zm16.355 0c-4.446 0-8.356-3.554-8-8.355 0-4.445 3.554-8 8.355-8 4.445 0 8 3.91 8 8.355 0 4.446-3.91 8-8.355 8zm0 0" fill="navy"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M4.059 10.39h40.254c2.109 0 3.69-1.613 3.69-3.761 0-2.149-1.581-3.758-3.69-3.758H4.059c-2.11 0-3.692 1.61-3.692 3.758 0 2.152 1.582 3.762 3.692 3.762zm0 19.891h40.254c2.109 0 3.69-1.613 3.69-3.765 0-2.149-1.581-3.762-3.69-3.762H4.059c-2.11 0-3.692 1.613-3.692 3.762 0 2.148 1.582 3.765 3.692 3.765zm19.336 10.57H4.059c-2.11 0-3.692 1.614-3.692 3.762 0 2.149 1.582 3.766 3.692 3.766h19.336c2.109 0 3.69-1.617 3.69-3.766 0-2.148-1.581-3.761-3.69-3.761zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M70.68 9.496c-2.813-1.434-6.504-3.582-7.91-6.629C62.77 1.254 61.54 0 59.957 0c-1.582 0-2.812 1.254-2.812 2.867v38.52c-2.989-1.614-8.614-1.075-12.833 1.433-6.68 3.766-9.492 10.93-6.68 15.766 2.813 4.84 10.723 5.914 17.4 2.152 4.573-2.687 7.738-6.988 7.913-11.289V16.305c9.492 0 15.29 3.941 13.18 13.437-.352 1.793-1.05 3.403-1.754 5.195-.355.54-.355 1.254.176 1.793.527.536 1.402.356 2.11-.359 3.515-3.582 5.796-8.242 5.976-13.437-.18-6.805-6.508-10.75-11.953-13.438zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M4.059 10.39h40.254c2.109 0 3.69-1.613 3.69-3.761 0-2.149-1.581-3.758-3.69-3.758H4.059c-2.11 0-3.692 1.61-3.692 3.758 0 2.152 1.582 3.762 3.692 3.762zm0 19.891h40.254c2.109 0 3.69-1.613 3.69-3.765 0-2.149-1.581-3.762-3.69-3.762H4.059c-2.11 0-3.692 1.613-3.692 3.762 0 2.148 1.582 3.765 3.692 3.765zm19.336 10.57H4.059c-2.11 0-3.692 1.614-3.692 3.762 0 2.149 1.582 3.766 3.692 3.766h19.336c2.109 0 3.69-1.617 3.69-3.766 0-2.148-1.581-3.761-3.69-3.761zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M70.68 9.496c-2.813-1.434-6.504-3.582-7.91-6.629C62.77 1.254 61.54 0 59.957 0c-1.582 0-2.812 1.254-2.812 2.867v38.52c-2.989-1.614-8.614-1.075-12.833 1.433-6.68 3.766-9.492 10.93-6.68 15.766 2.813 4.84 10.723 5.914 17.4 2.152 4.573-2.687 7.738-6.988 7.913-11.289V16.305c9.492 0 15.29 3.941 13.18 13.437-.352 1.793-1.05 3.403-1.754 5.195-.355.54-.355 1.254.176 1.793.527.536 1.402.356 2.11-.359 3.515-3.582 5.796-8.242 5.976-13.437-.18-6.805-6.508-10.75-11.953-13.438zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="m60.137 40.719-.184-.36-.36-.359c0-5.395-3.234-10.066-7.73-12.406-.539-1.977-.18-2.696-.18-2.696.18-.18.18-.539.364-.718h4.672c1.078 0 2.156-.36 2.875-1.258 2.52-2.516 3.777-5.754 3.777-9.348 0-6.652-4.676-12.047-11.144-12.945-.364 0-.723-.18-1.082-.18h-37.57C6.382.45.448 6.383.448 13.574c0 .54 0 1.078.18 1.797.36 12.223 9.527 22.113 14.383 26.606H5.664c-1.437 0-2.879.718-3.418 2.16C1.168 46.113.45 48.27.45 50.426c0 7.191 5.934 13.125 13.125 13.125h37.93c6.832-.719 12.047-6.473 12.047-13.125-.18-3.414-1.438-7.192-3.414-9.707M51.145 4.586c4.675.539 8.449 4.312 8.449 9.348 0 2.695-1.078 4.851-2.875 6.652H22.563c1.437-1.98 2.335-4.137 2.335-6.652 0-3.778-1.976-7.192-4.671-9.348zM4.227 50.426c0-1.617.539-3.235 1.257-4.313h15.82c.72 1.438 1.259 2.875 1.259 4.313 0 5.035-4.137 9.168-9.348 9.168-5.215 0-8.988-4.313-8.988-9.168m46.918 9.168H19.863c3.059-2.156 4.856-5.39 4.856-9.348 0-3.773-1.977-7.191-4.672-9.348h.18S4.766 29.395 4.586 14.832c0-.539-.18-.898-.18-1.437 0-5.036 4.133-9.348 9.348-9.348 5.21 0 9.348 4.133 9.348 9.348v.539c0 .898-.18 1.796-.54 2.515-.359 1.078-.898 1.977-1.617 2.875l-2.34 3.239H48.63c0 .18-.18.359-.36.539-.539 1.078-.718 2.156-.718 3.234-.54 0-1.258-.18-1.977-.18-7.55 0-13.844 6.114-13.844 13.844s6.114 13.844 13.844 13.844c5.57 0 10.426-3.239 12.582-8.27.719 1.617 1.258 3.235 1.258 4.852.18 4.676-3.594 8.808-8.27 9.168M51.685 40l-9.168 6.832V32.988zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".898875"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#039"><path d="M.324 12.07C-.777 14.828 1.43 23.102 1.43 23.102c4.226 11.77 14.156 21.882 14.156 21.882 9.746 10.114 19.492 16.364 28.133 18.387 8.64 2.02 10.297-1.473 10.297-1.473s7.168-6.988 9.191-9.375c2.023-2.574-.55-4.046-.55-4.046s-12.505-7.54-14.524-8.274c-2.024-.918-3.13.55-4.414 1.656-1.29 1.102-3.864 3.493-3.864 3.493-1.468.183-4.226-.918-8.64-4.414C26.8 37.444 21.469 30.823 20 28.434c-1.473-2.204-1.473-4.594-1.473-4.594s1.84-1.473 3.68-3.496c1.836-2.02 1.285-3.86 1.285-3.86l-5.699-10.48C14.301-1.352 13.379.12 13.379.12c-2.39.918-4.41 2.758-5.7 4.043-.917.922-5.882 5.149-7.355 7.906zM49.97 27.7c1.472 0 2.758-1.102 2.758-2.759 0-8.09-6.618-15.078-15.075-15.078-1.472 0-2.761 1.106-2.761 2.758 0 1.473 1.105 2.762 2.761 2.762 5.145 0 9.375 4.226 9.375 9.375 0 1.656 1.473 2.941 2.942 2.941zm0 0"/><path d="M38.938 1.223c-1.473 0-2.758 1.105-2.758 2.757 0 1.473 1.101 2.758 2.757 2.758a16.87 16.87 0 0 1 16.915 16.918c0 1.469 1.105 2.758 2.757 2.758 1.657 0 2.762-1.105 2.762-2.758 0-12.32-10.113-22.433-22.434-22.433zm-3.676 16.363c-1.473 0-2.758 1.105-2.758 2.758 0 1.656 1.101 2.758 2.758 2.758 2.39 0 4.41 2.023 4.41 4.414 0 1.472 1.105 2.757 2.758 2.757 1.472 0 2.757-1.101 2.757-2.757-.183-5.516-4.593-9.93-9.925-9.93zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M56.797 45.254c-.356-.531-.711-.707-1.242-.707H38.91c-.531 1.238-1.238 2.297-1.77 3.535-1.417 2.828-3.011 5.832-4.425 8.305v.18c-.887 1.413-2.305 2.472-4.074 2.472s-3.188-.883-4.07-2.473c-.532-.886-2.305-4.242-4.43-8.484-.707-1.238-1.239-2.473-1.77-3.71H9.34c-.531 0-1.063.35-1.414.882L.133 61.69c-.176.528-.176 1.059 0 1.414.355.528.707.708 1.238.708h46.215c.531 0 1.062-.356 1.418-.887l7.793-16.258c.351-.352.176-1.059 0-1.414zm0 0"/><path d="M28.465.188c-9.387 0-17.176 7.601-17.176 17.144 0 5.656 6.195 19.086 11.332 29.512 2.48 4.773 4.426 8.308 4.426 8.484.355.531.71.883 1.418.883.707 0 1.062-.352 1.414-.883 0 0 1.95-3.535 4.43-8.484 5.132-10.25 11.332-23.68 11.332-29.512C45.64 7.789 37.848.187 28.465.187zm0 27.57c-4.25 0-7.969-3.356-8.324-7.598v-.883c0-4.597 3.718-8.308 8.324-8.308 4.25 0 7.969 3.36 8.32 7.422v.886c0 4.594-3.719 8.48-8.32 8.48zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#999"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="39"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M0 38.824V.176h11.2l11.198 14.183L33.602.176H44.8v38.648H33.6v-22.16l-11.203 14.18-11.195-14.18v22.16zm67.2 0L50.397 20.031h11.204V.176h11.195V20.03H84zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M66.824 40.465c-.176 1.59-2.097 2.824-5.945 3.707-3.852.879-8.75 1.41-14.52 1.41h-4.898v9.172c1.574.176 3.324.176 4.898.176 5.77 0 10.668-.528 14.52-1.586 3.848-1.059 5.945-2.293 6.121-3.707-.176-.352-.176-9.172-.176-9.172zm-20.64-6.7c-1.75 0-3.325 0-4.899-.18v9.352h4.899c5.773 0 10.671-.53 14.52-1.59s5.944-2.292 5.944-3.702v-8.997c-.171 1.586-2.097 2.825-6.12 3.704-3.673 1.058-8.571 1.59-14.344 1.414zm0-11.468c-1.75 0-3.325 0-4.899-.176v9.352c1.574.175 3.324.175 4.899.175 5.773 0 10.671-.53 14.695-1.59C64.727 29 66.824 27.767 67 26.356V17.36c-.176 1.59-2.098 2.825-6.121 3.704-4.024.707-8.922 1.234-14.695 1.234zm0-13.05c-1.75 0-3.325 0-4.899.175v10.406c1.574.176 3.324.176 4.899.176 5.773 0 10.671-.527 14.695-1.586 3.848-1.059 5.945-2.293 6.121-3.703-.176-1.59-2.098-2.824-6.121-3.883-4.024-1.055-8.922-1.41-14.695-1.586zM18.02 23.886c-.176.527-.528 2.293-1.227 5.293l-1.223 5.113h5.07l-1.222-5.113c-.7-3-1.227-4.766-1.227-5.293zM0 7.129v49.918l37.785 6.527V.426zm23.09 37.219-1.399-5.645-6.996-.176-1.398 5.29-4.375-.352 6.648-23.813 5.07-.351 7.348 25.222zm0 0" fill="#a03537"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#999"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="79" xmlns="http://www.w3.org/2000/svg"><path d="m42.852 35.445 4.695 1.344 2.851-10.418-4.695-1.344c0-1.68-.168-3.359-.672-5.039l4.192-2.52-5.364-9.405-4.359 2.519a18.036 18.036 0 0 0-4.023-3.023l1.34-4.704L26.421 0l-1.34 4.703c-1.676 0-3.352.168-5.027.672l-2.516-4.2-9.387 5.376 2.512 4.199a18.053 18.053 0 0 0-3.016 4.031l-4.695-1.343L.105 23.852l4.692 1.343c0 1.68.168 3.36.672 5.04l-4.192 2.523 5.364 9.406 4.191-2.52a18.126 18.126 0 0 0 4.023 3.024l-1.34 4.703 10.395 2.856 1.34-4.704c1.676 0 3.352-.168 5.031-.671l2.512 4.199 9.39-5.375-2.515-4.2c1.172-1.175 2.348-2.519 3.184-4.03zm-25.985-5.547c-2.68-4.535-1.004-10.414 3.52-13.101 4.527-2.688 10.394-1.008 13.078 3.527 2.683 4.535 1.004 10.414-3.52 13.106-4.527 2.687-10.394 1.175-13.078-3.532zm50.63 33.262 6.034-3.527-1.676-2.856c.84-.84 1.508-1.68 2.012-2.687l3.184.84 1.844-6.887-3.184-.84c0-1.176-.168-2.183-.504-3.36l2.852-1.679-3.52-6.047-2.852 1.68c-.84-.84-1.675-1.512-2.683-2.016l.84-3.191-6.875-1.852-.836 3.196c-1.176 0-2.18.168-3.356.504l-1.675-2.86-5.7 3.7 1.676 2.855c-.836.84-1.508 1.68-2.012 2.687l-3.183-1.007-1.844 6.886 3.184.84c0 1.176.168 2.184.504 3.36l-2.852 1.68 3.523 6.046 2.848-1.68c.84.84 1.676 1.512 2.684 2.016l-.84 3.191L61.965 64l.836-3.191c1.176 0 2.18-.168 3.355-.504-.336 0 1.34 2.855 1.34 2.855zM57.101 50.563c-1.676-3.024-.668-6.887 2.347-8.567 3.02-1.68 6.875-.672 8.551 2.352 1.676 3.023.672 6.886-2.348 8.566-3.015 1.68-6.875.672-8.55-2.352zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.14 63.773H1.86a1.627 1.627 0 0 1-1.633-1.632V1.859C.227.953.953.227 1.859.227h60.282c.906 0 1.632.726 1.632 1.632v60.282c0 .906-.726 1.632-1.632 1.632zM3.314 59.777c0 .547.363.727.726.727h55.559c.543 0 .726-.363.726-.727V45.434c0-.543-.363-.723-.726-.723H4.223c-.547 0-.727.363-.727.723v14.343zm56.464-56.28H4.223c-.547 0-.727.362-.727.726v36.492c0 .18 0 .363.18.363l11.8-14.707 11.985 7.082 13.434-15.976 19.793 15.43V4.222c0-.547-.364-.727-.91-.727zm-48.476 44.3c2.543 0 4.722 2.176 4.722 4.719s-2.18 4.722-4.722 4.722c-2.54 0-4.719-2-4.719-4.539 0-2.543 2.18-4.902 4.719-4.902zm8.715 3.266h36.496c.543 0 .726.363.726.726v1.637c0 .543-.363.722-.726.722H20.016c-.543 0-.727-.359-.727-.722v-1.637c0-.363.363-.726.727-.726zm0 0" fill="#3c3" stroke="#3c3" stroke-miterlimit="10" stroke-width=".4539"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#a87c2d" stroke="#a87c2d" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M0 7.336 37.754.008v63.984L0 57.02zm0 0"/><path d="M64.164 57.734H32.082c-1.594 0-2.836-1.25-2.836-2.859V9.305c0-1.61 1.242-2.864 2.836-2.864h32.082C65.758 6.441 67 7.695 67 9.305v45.57c0 1.61-1.242 2.86-2.836 2.86zM32.082 9.125c-.176 0-.355.18-.355.355v45.575c0 .18.18.36.355.36h32.082c.176 0 .356-.18.356-.36V9.305c0-.18-.18-.36-.356-.36 0 .18-32.082.18-32.082.18zm0 0"/><path d="M59.555 34.324H55.3V19.313H35.629v-4.29h23.926zm0 0"/><path d="m57.25 38.078-7.621-8.402h15.066zM37.719 42.578l8.144-8.215 8.149 8.215-8.149 8.215zm0 0"/></g><path d="M23.574 22.348c-.71-.715-1.418-1.07-2.48-1.43-.887-.355-2.13-.176-3.192-.176-2.129 0-5.847.356-5.847.356l-.18 20.73 3.898.36v-7.329s2.305.36 4.254-.18c1.067-.355 2.13-.89 2.66-1.429.711-.715 1.243-1.43 1.594-2.145.535-1.07.711-2.144.711-3.753.356-1.965-.176-3.75-1.418-5.004zm-3.012 7.148c-.71 1.61-2.66 1.61-2.66 1.61h-2.129v-6.614s1.418-.176 2.485 0c.531.18 1.062.36 1.418.54 1.062.89 1.594 3.034.886 4.464zm0 0" fill="#fff"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><g fill="#2d7136"><path d="M0 7.336 37.754.008v63.984L0 57.02zm0 0"/><path d="M64.164 57.734H32.082c-1.594 0-2.836-1.25-2.836-2.859V9.305c0-1.61 1.242-2.864 2.836-2.864h32.082C65.758 6.441 67 7.695 67 9.305v45.57c0 1.61-1.242 2.86-2.836 2.86zM32.082 9.125c-.176 0-.355.18-.355.355v45.575c0 .18.18.36.355.36h32.082c.176 0 .356-.18.356-.36V9.305c0-.18-.18-.36-.356-.36 0 .18-32.082.18-32.082.18zm0 0"/><path d="M59.555 34.324H55.3V19.313H35.629v-4.29h23.926zm0 0"/><path d="m57.25 38.078-7.621-8.402h15.066zM37.719 42.578l8.144-8.215 8.149 8.215-8.149 8.215zm0 0"/></g><path d="M23.574 22.348c-.71-.715-1.418-1.07-2.48-1.43-.887-.355-2.13-.176-3.192-.176-2.129 0-5.847.356-5.847.356l-.18 20.73 3.898.36v-7.329s2.305.36 4.254-.18c1.067-.355 2.13-.89 2.66-1.429.711-.715 1.243-1.43 1.594-2.145.535-1.07.711-2.144.711-3.753.356-1.965-.176-3.75-1.418-5.004zm-3.012 7.148c-.71 1.61-2.66 1.61-2.66 1.61h-2.129v-6.614s1.418-.176 2.485 0c.531.18 1.062.36 1.418.54 1.062.89 1.594 3.034.886 4.464zm0 0" fill="#fff"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><g fill="#2d7136"><path d="M0 7.336 37.754.008v63.984L0 57.02zm0 0"/><path d="M64.164 57.734H32.082c-1.594 0-2.836-1.25-2.836-2.859V9.305c0-1.61 1.242-2.864 2.836-2.864h32.082C65.758 6.441 67 7.695 67 9.305v45.57c0 1.61-1.242 2.86-2.836 2.86zM32.082 9.125c-.176 0-.355.18-.355.355v45.575c0 .18.18.36.355.36h32.082c.176 0 .356-.18.356-.36V9.305c0-.18-.18-.36-.356-.36 0 .18-32.082.18-32.082.18zm0 0"/><path d="M59.555 34.324H55.3V19.313H35.629v-4.29h23.926zm0 0"/><path d="m57.25 38.078-7.621-8.402h15.066zM37.719 42.578l8.144-8.215 8.149 8.215-8.149 8.215zm0 0"/></g><path d="M23.574 22.348c-.71-.715-1.418-1.07-2.48-1.43-.887-.355-2.13-.176-3.192-.176-2.129 0-5.847.356-5.847.356l-.18 20.73 3.898.36v-7.329s2.305.36 4.254-.18c1.067-.355 2.13-.89 2.66-1.429.711-.715 1.243-1.43 1.594-2.145.535-1.07.711-2.144.711-3.753.356-1.965-.176-3.75-1.418-5.004zm-3.012 7.148c-.71 1.61-2.66 1.61-2.66 1.61h-2.129v-6.614s1.418-.176 2.485 0c.531.18 1.062.36 1.418.54 1.062.89 1.594 3.034.886 4.464zm0 0" fill="#fff"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="fill-rule:nonzero;fill:#1d6fb5;fill-opacity:1;stroke-width:.75;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1d6fb5;stroke-opacity:1;stroke-miterlimit:10" d="M6.274 25.574h28.3l-9.698-9.3-4.501 3.802-4.5-3.802zm34.1-25.2v28.002H.376V.374zM26.976 14.576l10.7 10.298v-19.3zm-24.2 10.298 10.7-10.298-10.7-9.002zm1.4-21.7 15.9 13.4 15.9-13.4zm0 0" transform="matrix(2.06135 0 0 2.08166 0 .076)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#4d1b9b"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#55486d"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#ff0021"><path d="M49.453.188h-14.45V64h14.45c7.883 0 14.453-6.383 14.453-14.453V14.453C63.906 6.57 57.523.188 49.453.188zm0 41.289c-4.129 0-7.508-3.375-7.508-7.508 0-4.13 3.38-7.504 7.508-7.504s7.508 3.375 7.508 7.504c0 4.133-3.379 7.508-7.508 7.508zM31.437 64h-16.89C6.664 64 .094 57.617.094 49.547V14.453C.094 6.383 6.477 0 14.547 0h16.89zM14.547 3.941c-5.82 0-10.7 4.692-10.7 10.7v35.093c0 5.82 4.692 10.7 10.7 10.7H27.87V3.94zm0 0"/><path d="M15.672 26.465c-4.129 0-7.508-3.38-7.508-7.508 0-4.129 3.379-7.508 7.508-7.508s7.508 3.38 7.508 7.508c0 4.129-3.38 7.508-7.508 7.508zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="66" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa" stroke-miterlimit="10"><path d="M58 47.3v4.2H14.4v-4.2m43.2 10.1V61.6H14V57.4M57.6 68v4.1H14V68M57.6 78.5v4.2H14v-4.2" stroke="#bababa" transform="matrix(1.46667 0 0 1.48837 -19.8 -64.744)"/><path d="M29.8 60.9v-1.8c.5-.2 1.2-.399 2.2-.7.9-.3 1.8-.4 2.799-.599 1.001-.202 2-.302 2.9-.402.9-.1 1.8-.2 2.602-.2l.898.602-4.8 22.8h3.7v1.9c-.4.298-.999.6-1.598.9-.602.299-1.3.498-2 .8-.7.3-1.401.399-2.102.499-.7.1-1.398.199-2 .199-1.398 0-2.2-.3-2.799-.798-.4-.501-.6-1.102-.6-1.7 0-.701.1-1.402.2-2.1.099-.7.299-1.402.4-2.202L33.2 61.7zm4.5-11.999c0-1.202.4-2.202 1.2-2.8.801-.7 1.8-1 3.1-1 1.4 0 2.4.3 3.2 1 .8.698 1.2 1.598 1.2 2.8 0 1.1-.4 2.1-1.2 2.698-.8.701-1.9 1-3.2 1-1.2 0-2.2-.299-3.1-1-.701-.598-1.2-1.498-1.2-2.698zm0 0" stroke="#fff" stroke-width="3" transform="matrix(1.46667 0 0 1.48837 -19.8 -64.744)"/></g></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#666;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M60.887 19.578c2.941-7.27 3.285-13.152-.348-16.789C56.56-1.19 46.867.02 36.656 4.867h-1.21c-7.27 0-14.192 2.598-19.383 7.27-4.325 3.98-7.614 9.172-9 15.054 1.039-1.21 6.578-7.789 12.98-11.421.172 0 1.73-1.04 1.73-1.04-.171 0-3.289 2.942-3.808 3.637C3.949 32.73-4.184 54.535 2.219 60.937c4.152 4.153 11.765 3.29 20.593-1.558 3.81 1.73 7.961 2.598 12.633 2.598 6.059 0 11.594-1.559 16.442-4.848 5.02-3.285 8.652-8.133 10.73-14.016H47.04c-2.074 3.809-6.574 6.403-11.422 6.403-6.746 0-12.457-5.54-12.633-11.942v-.52h40.844v-.863c0-1.039.172-2.25.172-2.941 0-4.848-1.04-9.52-3.113-13.672zM6.719 59.555c-3.29-3.29-2.25-9.348 1.554-16.79 1.735 5.02 4.848 9.348 8.657 12.633 1.21 1.04 2.593 2.079 3.98 2.77-6.406 3.46-11.598 3.98-14.191 1.387zm41.015-30.461H23.332v-.172c.344-6.23 6.23-11.594 12.98-11.594 6.403 0 11.594 5.02 11.938 11.594v.172zM59.848 18.02c-1.211-2.079-2.77-3.98-4.672-5.54a29.6 29.6 0 0 0-9.692-6.054c6.403-2.942 11.766-3.461 14.536-.52 2.421 2.422 2.25 6.75-.172 12.114 0 .171 0 .171 0 0zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><g fill="#a03537"><path d="M38.684 63.957c-8.098-.34-15.356-2.875-19.239-6.77-1.183-1.011-2.363-2.535-2.87-3.55l-.337-.676v-7.617c0-7.614 0-7.614.168-6.934.34 1.692 1.352 3.383 2.871 4.735 1.012.847 3.035 2.37 4.723 3.214 2.871 1.524 6.586 2.54 10.465 3.047 2.363.34 3.207.34 6.582.34s4.223 0 6.582-.34c3.883-.508 7.43-1.691 10.465-3.047 1.687-.843 3.715-2.199 4.726-3.214 1.352-1.352 2.532-3.043 2.871-4.735.168-.508.168-.508.168 6.934v7.445l-.34.68c-1.18 2.367-3.207 4.398-5.906 6.09-5.23 3.046-13.164 4.738-20.93 4.398zm0-18.95c-7.086-.339-13.668-2.37-17.887-5.413-1.016-.68-2.363-2.032-2.871-2.707a10.877 10.877 0 0 1-1.352-2.371l-.336-.676v-7.445c0-7.446 0-7.446.168-6.938.34 1.184.844 2.54 1.856 3.555.508.675 1.351 1.523 1.86 1.86.167.171.675.339 1.01.679 3.376 2.367 8.102 4.058 13.5 4.906 2.364.336 3.208.336 6.587.336 3.375 0 4.218 0 6.582-.336 3.879-.508 7.426-1.691 10.46-3.047 1.692-.847 3.716-2.2 4.727-3.215 1.352-1.351 2.364-3.047 2.871-4.738.168-.508.168-.508.168 6.938v7.445l-.507 1.015c-.844 1.524-1.348 2.368-2.364 3.383-1.011 1.016-2.023 1.864-3.375 2.54-5.398 3.046-13.332 4.738-21.097 4.23zm-.504-18.78c-4.727-.34-8.438-1.184-11.985-2.54-4.218-1.69-7.257-3.89-8.777-6.597a5.733 5.733 0 0 1-.844-2.031c-.168-.676-.336-2.368-.168-3.383C17.418 6.262 24.676 1.859 34.465.34 36.828 0 37.672 0 41.047 0s4.223 0 6.582.34c3.883.508 7.43 1.691 10.465 3.043 4.39 2.199 7.09 5.078 7.597 8.12.168.849.168 2.708-.171 3.388-.504 1.691-1.18 2.707-2.532 4.058-3.543 3.723-9.789 6.094-17.55 6.938-1.016.34-6.247.34-7.258.34zm0 0"/><path d="M38.5 55.7h1.7c2.501.2 4.5.8 6.5 1.6 3.699-1.7 9.1-.399 12.399.9-4.298-.4-9.3 0-12.2 1.7-2.9-2.4-8.498-2.999-13.699-2.4 1.4-1 3.1-1.5 5.3-1.8zm-1.3 6.601c-3 .199-5.5 1.198-7.2 2.6-5-2.302-13.7-1.3-17 1.798-.299.201-.6.402-.5.702 2.8-.9 6.3-1.6 9.901-1.302 3.5.3 6.198 1.5 8.2 3.1 3.6-3.299 8.999-5.1 15.898-5-2.4-1.4-5.8-2.3-9.3-1.898zm0 0" stroke="#fff" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.68776 0 0 1.692 -20.28 -79.405)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="83" xmlns="http://www.w3.org/2000/svg"><path d="M62.797 9.066H82.93v13.512H62.797zm0 20.801H82.93V43.38H62.797zm0 20.621H82.93V64H62.797zm-27.371 0h20.308V64H35.426zm-27.196 0h20.13V64H8.23zM43.371 0h2.824c4.239.355 7.594 1.422 10.95 2.668 6.359-2.848 15.187-.711 20.84 1.422-7.063-.711-15.54 0-20.485 2.844-4.945-4.09-14.305-5.157-22.957-4.09A22.506 22.506 0 0 1 43.371 0zM41.43 10.844c-5.121.355-9.36 1.957-12.008 4.265C20.945 11.2 6.465 12.977.988 18.133c-.531.355-1.058.71-.883 1.246 4.77-1.422 10.598-2.668 16.602-2.133 6.004.531 10.418 2.488 13.773 5.152 6.18-5.507 15.188-8.71 26.665-8.53-4.06-1.778-9.887-3.38-15.715-3.024zm-5.828 19.199h20.132v13.512H35.602zm-27.196 0H28.54v13.512H8.406zm0 0" fill="#1f7244"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M43.399 59.6h15.802v2.498H43.399zm-30.9 6.902h46.702V69.3H12.499zm0 6.799h46.702v2.798H12.499zm0 0" transform="matrix(1.7529 0 0 1.7867 -21.473 -85.752)"/><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="M46.363.363h2.985c4.554.36 8.058 1.434 11.566 2.864C67.574.187 77.043 2.512 83 4.832c-7.54-.715-16.477 0-21.738 3.04-5.258-4.286-15.075-5.54-24.364-4.286 2.63-1.79 5.786-2.863 9.465-3.223zM44.262 11.98c-5.434.36-9.82 2.145-12.797 4.649-8.942-4.113-24.367-2.324-30.149 3.21-.527.36-1.054.72-.879 1.25 5.086-1.605 11.22-2.855 17.528-2.32 6.312.536 11.047 2.68 14.55 5.54 6.485-5.899 16.13-9.29 28.223-9.114-4.207-1.965-10.343-3.57-16.476-3.215zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M12.5 80h46.7v2.8H12.5zm0 0" transform="matrix(1.7529 0 0 1.7867 -21.473 -85.752)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><g fill="#1d6fb5"><path d="M18.754 25.742c-2.668.36-4.8 3.219-4.8 6.258s2.132 6.078 4.8 6.258c2.668.355 4.805-2.504 4.805-6.258s-2.137-6.613-4.805-6.258zm0 0"/><path d="M.074 7.508v49.52L38.504 64V0zm18.68 34.683c-4.27-.539-7.649-5.187-7.649-10.191 0-5.184 3.38-9.652 7.649-10.191 4.27-.536 7.652 4.113 7.652 10.191 0 6.258-3.383 10.727-7.652 10.191zm50.172-27.175L47.754 32.715l-5.691-4.649v-14.66h26.863zm0 0"/><path d="m68.926 18.414-21.172 17.7-5.691-4.65V51.31h26.863zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M43.399 59.6h15.802v2.498H43.399zm-30.9 6.902h46.702V69.3H12.499zm0 6.799h46.702v2.798H12.499zm0 0" transform="matrix(1.7529 0 0 1.7867 -21.473 -85.752)"/><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="M46.363.363h2.985c4.554.36 8.058 1.434 11.566 2.864C67.574.187 77.043 2.512 83 4.832c-7.54-.715-16.477 0-21.738 3.04-5.258-4.286-15.075-5.54-24.364-4.286 2.63-1.79 5.786-2.863 9.465-3.223zM44.262 11.98c-5.434.36-9.82 2.145-12.797 4.649-8.942-4.113-24.367-2.324-30.149 3.21-.527.36-1.054.72-.879 1.25 5.086-1.605 11.22-2.855 17.528-2.32 6.312.536 11.047 2.68 14.55 5.54 6.485-5.899 16.13-9.29 28.223-9.114-4.207-1.965-10.343-3.57-16.476-3.215zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M12.5 80h46.7v2.8H12.5zm0 0" transform="matrix(1.7529 0 0 1.7867 -21.473 -85.752)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#4d1b9b"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#4d1b9b"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#bababa;fill-opacity:1" d="M78.953 17.746c-.969-.191-1.742-.387-2.707-.191-2.125.191-4.441 1.36-6.566 1.746-1.543.199-2.895-.973-3.09-2.719a102.076 102.076 0 0 1 0-15.379c0-.777-.578-1.168-1.156-1.168C59.64-.156 54.039.425 48.05 1.79c-1.547.387-2.7 1.754-2.7 3.113.188 3.118 2.313 6.618 1.348 9.926-.773 2.918-3.09 5.059-6.18 5.645-2.894.586-5.988-.778-7.53-3.114-1.93-2.726-.966-6.62-1.739-9.925-.383-1.559-1.93-2.34-3.473-2.145a62.499 62.499 0 0 0-16.804 4.09c-.77.195-1.157.973-.77 1.555 2.125 4.671 3.664 9.539 5.02 14.597.386 1.559-.578 3.117-2.125 3.504-2.125.586-4.637.195-6.758.777-.969.196-1.738.586-2.508 1.168-2.707 1.754-3.867 4.672-3.48 7.59.39 2.727 2.507 5.063 5.02 6.23 3.284 1.165 6.183-.972 9.464-1.167 1.543-.192 2.898.972 3.09 2.726.387 5.059.387 10.313 0 15.371 0 .782.578 1.168 1.16 1.168 5.789.391 11.586-.386 17.379-1.75 1.543-.39 2.703-1.75 2.703-3.113-.195-3.308-2.508-6.617-1.543-10.12.77-2.727 3.473-5.06 6.18-5.454 2.699-.387 5.988.781 7.53 3.117 2.122 2.727.966 6.813 1.74 10.121.382 1.555 1.929 2.336 3.472 2.14 5.797-.585 11.59-1.753 16.805-4.089.77-.195 1.156-.973.77-1.555-2.126-4.672-3.669-9.535-5.02-14.597-.387-1.559.578-3.114 2.125-3.5 3.09-.782 6.566.195 9.46-1.95 2.126-1.75 3.477-4.671 2.895-7.394.195-3.695-2.121-6.227-4.629-7.008zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#a03537"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M27.762.637c-3.899 0-6.95 3.218-6.95 6.949 0 4.746 2.711 10.68 5.422 16.277-2.203 6.782-4.574 14.07-7.797 20.172-6.44 2.547-12.207 4.41-15.597 7.293l-.168.168C1.484 52.852.637 54.546.637 56.414c0 3.898 3.218 6.95 6.949 6.95 1.867 0 3.73-.677 4.918-2.036 0 0 .168 0 .168-.168 2.543-3.05 5.594-8.644 8.308-13.562 6.102-2.375 12.715-4.918 18.817-6.442 4.578 3.73 11.191 6.102 16.617 6.102 3.898 0 6.95-3.223 6.95-6.95 0-3.902-3.22-6.953-6.95-6.953-4.41 0-10.68 1.528-15.43 3.223a56.197 56.197 0 0 1-10.172-13.223c1.868-5.765 4.07-11.359 4.07-15.77-.171-3.898-3.222-6.948-7.12-6.948zm0 4.066c1.527 0 2.71 1.188 2.71 2.715 0 2.035-1.183 5.934-2.37 10-1.696-4.066-3.223-7.965-3.223-10 .172-1.356 1.355-2.715 2.883-2.715zm1.187 23.906c2.035 3.391 4.578 6.442 7.29 9.157a171.201 171.201 0 0 0-12.208 4.066c2.035-4.238 3.563-8.816 4.918-13.223zm27.465 8.985a2.679 2.679 0 0 1 2.711 2.715 2.678 2.678 0 0 1-2.71 2.71c-3.224 0-7.63-1.355-11.192-3.39 4.07-1.016 8.648-2.035 11.191-2.035zM14.875 49.973c-2.031 3.558-3.898 6.78-5.254 8.476-.508.508-1.016.676-1.863.676a2.679 2.679 0 0 1-2.715-2.71c0-.68.34-1.528.68-1.868 1.523-1.356 5.086-2.879 9.152-4.574zm0 0" fill="#c11e07" stroke="#c11e07" stroke-miterlimit="10" stroke-width="1.27152"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#999"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M65.66 13.137 33.5.168 20.77 5.391l32.16 12.8zM12.73 8.59 1.34 13.137 33.5 26.105l11.39-4.546zM0 50.695l32.496 13.137V27.789L0 14.652zm54.27-30.82V29.98l-1.34-.843-1.34 1.851-1.34-.672-1.34 1.852-1.34-.84-1.34 1.852V23.074L34.504 27.79v36.043L67 50.695V14.652zm0 0" fill="#6781b2"/><path d="M7.371 36.21c.668.337 1.34.337 1.676.169.332-.168.836-.504 1.172-1.012.332-.504.5-1.008.332-1.176-.164-.171-.5-.675-1.172-.843l-1.004-.676-1.844 3.035zm-5.195 2.528c-.164-.168-.164-.168 0-.34l4.86-8.082.167-.168 3.516 1.348c1.172.504 1.844 1.008 2.18 1.852.335.843.167 1.683-.504 2.695-.168.336-.504.84-.836 1.008-.336.34-.672.508-1.172.676-.504.168-1.008.336-1.508.336-.504 0-1.176-.168-1.844-.504l-1.508-.508-1.34 2.023-.167.168zm14.238 2.864c-.168-.172-.168-.172 0-.34l2.18-3.535c.168-.336.332-.676.168-.676 0-.168-.168-.336-.672-.504l-1.34-.504-2.68 4.379-.168.168-1.843-.676s-.168 0-.168-.168v-.168l4.859-8.082.168-.168 1.844.672s.164 0 .164.168v.168l-1.172 2.023 1.34.504c1.008.336 1.676.844 2.011 1.516.336.504.168 1.348-.335 2.02l-2.344 3.706-.168.168zm7.707 1.007c.668.34 1.34.34 1.84.168.504-.168.84-.504 1.176-1.007.332-.508.5-1.012.332-1.18-.168-.336-.5-.676-1.172-.844l-1.172-.504-1.844 3.031zm-5.36 2.528c-.167-.168-.167-.168-.167-.336l5.023-8.086s.168-.168.336 0l3.684 1.347c1.172.508 1.843 1.012 2.18 1.852.331.844.167 1.688-.504 2.695-.168.34-.504.844-.836 1.012-.336.336-.672.504-1.176.672-.5.172-1.172.34-1.672.34-.504 0-1.176-.168-2.012-.508l-1.34-.336-1.34 2.02s-.167.171-.335 0zm0 0" fill="#fff"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="52"><path style="stroke:none;fill-rule:nonzero;fill:#6781b2;fill-opacity:1" d="M36.18 38.57c3.308 1.235 8.703 1.063 12.535.528.7 1.593-.348 3.715.347 5.48.352.887 1.395 1.594 3.137 2.477.348.18.867.18 1.219.355l.348.352c.695.531 2.09 1.418 2.437 1.418.524.172 1.047.172 1.39.172h1.747c2.433-.172 4.87-.883 5.57-1.766 1.219-1.59.524-5.125.348-8.309-.176-2.652-.524-6.187 0-8.308.176-.707.87-1.418 1.215-2.301 1.394-3.004 2.789-7.957 2.27-12.73-.352-2.122-1.395-4.063-1.571-5.653 3.308.352 6.441-.355 9.226 0 1.747.172 3.137 1.41 4.704 1.235.351-.883 1.39-1.415 1.39-2.473.176-1.238-.344-2.656-.867-3.54-2.266-.35-4.004 1.77-6.266 1.95-.703 0-1.57-.18-2.441-.355-2.613-.18-6.266.527-8.531 0-1.567-.356-2.961-2.122-4.528-2.829-.347-.18-1.043 0-1.394-.355-.52-.176-.871-.531-1.215-.531-1.742-.703-3.66-1.414-5.402-1.59C48.543.91 44.016.91 39.836 1.266c-1.39.18-2.61.882-4.004.53C34.785 1.618 34.613.91 33.918.56c-2.965-1.414-5.922.175-7.84 1.238-1.39.707-3.129 1.766-4.527 1.945-1.39.352-3.48 0-4.7 0-1.566 0-3.656.352-5.398.531-1.566.352-3.828.528-4.7 1.235-2.437 1.414-3.132 7.957-4.007 11.847-.344 1.415-.867 2.829-1.215 4.243-.523 3.18-.875 6.543-.875 9.547-.172 6.187-.867 14.851 2.27 17.148.695.531 2.957 1.238 3.652.887.176 0 1.047-.887 1.219-1.239.176-.53-.344-1.238-.344-1.945 0-1.418-.351-3.184-.351-4.598 0-3.71.695-7.777 1.566-9.37 0-.176.523-.176.523-.352.176-.352 0-.707.352-.887.695-.703 1.738-1.414 2.434-1.59 2.09-.883 3.308.176 4.18 1.414 1.741 2.301 2.09 6.188 2.265 9.903v2.297c0 .882-.352 1.765-.352 2.296.524 1.414 2.961 2.125 4.008 2.832 0 .528.176 1.239.52 1.59.523.887 1.394 1.414 1.918 1.766 2.609 1.418 9.226.531 10.449-1.234.172-.18.344-.356.344-.708.171-.53.523-1.062.523-1.414 1.047-3.183-.172-6.011.348-9.37zM32.523 6.922c-.171.18-.171.18 0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.887 38.266c-2.684-.84-4.532-3.36-4.532-6.215 0-2.852 1.848-5.371 4.532-6.211.84-.336 1.343-1.172 1.008-2.012-.84-3.023-1.848-5.707-3.524-8.394-.504-.84-1.344-1.008-2.184-.672-1.007.504-2.015.84-3.19.84-3.692 0-6.548-3.024-6.548-6.547 0-1.176.336-2.184.84-3.188.504-.84.168-1.68-.672-2.183a40.47 40.47 0 0 0-8.39-3.528c-.84-.168-1.68.168-2.016 1.008C37.37 3.852 34.855 5.7 32 5.7s-5.371-1.847-6.21-4.535C25.452.324 24.612-.18 23.772.156c-3.02.84-5.707 1.848-8.39 3.528-.84.503-1.008 1.343-.672 2.183.504 1.004.84 2.012.84 3.188 0 3.691-3.024 6.547-6.547 6.547-1.176 0-2.184-.336-3.191-.84-.84-.504-1.68-.168-2.184.672a40.699 40.699 0 0 0-3.524 8.394c-.167.84.168 1.676 1.008 2.012 2.684.84 4.532 3.36 4.532 6.21 0 2.856-1.848 5.376-4.532 6.216-.84.332-1.343 1.172-1.008 2.011.84 3.024 1.848 5.707 3.524 8.395.504.84 1.344 1.008 2.184.672 1.007-.504 2.015-.84 3.19-.84 3.692 0 6.548 3.02 6.548 6.547 0 1.176-.336 2.183-.84 3.187-.504.84-.168 1.68.672 2.184a40.47 40.47 0 0 0 8.39 3.527h.336c.672 0 1.344-.504 1.512-1.176.84-2.687 3.356-4.535 6.211-4.535s5.371 1.848 6.211 4.535c.336.84 1.176 1.344 2.016 1.008 3.02-.84 5.707-1.847 8.39-3.527.84-.504 1.008-1.344.672-2.184-.504-1.004-.84-2.011-.84-3.187 0-3.692 3.024-6.547 6.547-6.547 1.176 0 2.184.336 3.192.84.84.504 1.68.168 2.183-.672a40.698 40.698 0 0 0 3.524-8.395c.503-.672 0-1.511-.84-1.843zm-30.719 3.691c-5.371 0-9.902-4.363-9.902-9.906 0-5.371 4.363-9.903 9.902-9.903 5.371 0 9.902 4.364 9.902 9.903 0 5.375-4.53 9.906-9.902 9.906zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#4d1b9b"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#3a3c5b"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="M69.5 0H4.633C2.125 0 .285 2.008.285 4.352V49.39c0 2.511 2.004 4.351 4.348 4.351h64.699c2.508 0 4.348-2.008 4.348-4.351V4.52C74.016 2.008 72.008 0 69.5 0zm0 0" fill="#ced2d8"/><path d="M4.633 10.883H69.5V49.39H4.633zm0 0" fill="#f2f2f2"/><path d="M18.008 5.86c0 .831-.676 1.507-1.508 1.507a1.508 1.508 0 0 1 0-3.015c.832 0 1.508.675 1.508 1.507zm0 0" fill="#54b845"/><path d="M12.824 5.86c0 .831-.676 1.507-1.504 1.507a1.509 1.509 0 0 1 0-3.015c.828 0 1.504.675 1.504 1.507zm0 0" fill="#fbd303"/><path d="M7.977 5.86c0 .831-.676 1.507-1.508 1.507a1.508 1.508 0 0 1 0-3.015c.832 0 1.508.675 1.508 1.507zm0 0" fill="#f0582f"/><path d="M27.703 63.285c-.836-.668-1.672-1.336-2.34-2.176-.668-.836-1.34-1.84-2.008-2.68-1.503-2.175-2.507-4.519-3.343-6.863-1.004-2.843-1.336-5.523-1.336-8.203 0-3.015.668-5.523 1.84-7.703 1-1.672 2.34-3.18 4.011-4.183 1.672-1.004 3.512-1.508 5.348-1.676.672 0 1.34.168 2.176.336.5.168 1.168.336 2.004.668 1.004.336 1.672.672 1.84.672.667.168 1.171.335 1.503.335.336 0 .672-.167 1.172-.167.336-.168.836-.336 1.504-.672.668-.332 1.34-.5 1.84-.668l2.008-.504c.668-.168 1.504-.168 2.172-.168 1.336.168 2.508.336 3.68.84 2.003.836 3.507 2.007 4.68 3.683-.5.332-1.005.668-1.337 1.004-1.004.836-1.672 1.84-2.344 2.844-.836 1.508-1.168 3.184-1.168 4.855 0 2.012.5 3.852 1.672 5.36a7.757 7.757 0 0 0 2.84 2.847c.672.332 1.172.668 1.672.836-.164.668-.5 1.34-.836 2.008-.668 1.508-1.504 3.016-2.34 4.356-.836 1.172-1.504 2.007-2.004 2.675-.836.84-1.507 1.676-2.175 2.012-.836.5-1.84.836-2.676.836-.668 0-1.336 0-2.004-.168-.504-.168-1.172-.336-1.672-.668-.504-.336-1.172-.504-1.672-.672-.668-.168-1.504-.332-2.176-.332-.836 0-1.504.164-2.172.332s-1.171.336-1.671.672c-.836.332-1.336.5-1.672.668-.668.168-1.172.336-1.84.336-1.336.168-2.34-.168-3.176-.672zm13.04-34.992c-1.337.672-2.509.84-3.677.84-.168-1.172 0-2.512.5-3.852.504-1.172 1.004-2.176 1.84-3.18.836-1.007 1.84-1.843 3.008-2.343 1.172-.672 2.508-1.008 3.512-1.008.168 1.34 0 2.512-.5 3.852-.504 1.171-1.004 2.343-1.84 3.347-.836.84-1.84 1.676-2.844 2.344zm0 0" fill="#c6a8e5"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#999"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#a87c2d" stroke="#a87c2d" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M55.809.504S31.965.832 16.73 19.992c-.828 1.145-1.656 2.13-2.484 3.274-.496.656-.996 7.207-.996 7.207s-.66-.493-.992-1.309c-.496-.984-.664-2.129-.664-2.129C2.984 40.953 5.469 48.16 5.469 48.16c-.664 1.637-1.989 2.617-3.809 6.387C-.16 58.313.004 61.914.004 61.914c0 .656.164.82.496.164 0 0 1.988-3.766 3.477-6.223.996-1.636 3.976-5.402 3.976-5.402s4.969.16 10.93-1.312c-.496-.164-2.153-.657-3.313-1.145-1.16-.492-1.82-1.312-1.82-1.312l21.36-4.91c2.98-1.801 5.628-3.93 7.785-6.551 11.257-13.266 14.074-33.57 14.074-33.57.164-.657-.332-1.15-1.16-1.15zM34.613 21.629s-9.937 8.68-14.902 13.266C14.742 39.48 8.117 50.453 8.117 50.453L5.633 48.16s1.824-4.422 9.11-13.265c7.12-8.68 19.538-14.083 19.538-14.083 1.492-.656 1.657-.328.332.817zm0 0" fill="#ef712f"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#c93" stroke="#c93" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M10.867 48.152c0 1.551 1.04 2.582 2.602 2.582h2.773v9.305c0 2.066 1.91 3.961 3.992 3.961s3.989-1.895 3.989-3.96v-9.306h5.379v9.305c0 2.066 1.91 3.961 3.992 3.961s3.988-1.895 3.988-3.96v-9.306h2.602c1.562 0 2.605-1.03 2.605-2.757V21.449H10.867zM4.097 21.45C2.017 21.45.11 23.344.11 25.41v18.606c0 2.066 1.907 3.96 3.989 3.96s3.992-1.894 3.992-3.96V25.41c0-2.066-1.735-3.96-3.992-3.96zm45.805 0c-2.082 0-3.992 1.895-3.992 3.961v18.606c0 2.066 1.91 3.96 3.992 3.96s3.989-1.894 3.989-3.96V25.41c0-2.066-1.907-3.96-3.989-3.96zM36.367 5.945l3.473-3.449c.52-.516.52-1.375 0-1.894a1.373 1.373 0 0 0-1.91 0L33.94 4.566c-1.91-1.379-4.34-1.894-6.941-1.894-2.777 0-5.031.515-7.285 1.55L15.898.259c-.523-.344-1.562-.344-2.082 0-.347.515-.347 1.55 0 2.066l3.47 3.446c-3.817 2.93-6.419 7.41-6.419 12.75h32.266c0-5.168-2.602-9.82-6.766-12.575zm-14.746 7.407h-2.773v-2.586h2.773zm13.531 0H32.38v-2.586h2.773zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M16.223 21.805.09 55.844l3.012 3.015 20.035-20.035c-.711-1.594-.532-3.543.886-4.96 1.774-1.774 4.43-1.774 6.204 0 1.773 1.769 1.773 4.429 0 6.202-1.243 1.243-3.368 1.594-4.965.887L5.23 60.984 8.242 64l34.04-16.133L49.73 27.48 36.61 14.36zm46.625-4.075L46.184 1.062c-1.418-1.417-3.547-1.417-4.965 0L37.32 4.966c-1.422 1.418-1.422 3.543 0 4.965l16.664 16.664c1.418 1.418 3.543 1.418 4.965 0l3.899-3.903c1.418-1.418 1.418-3.543 0-4.96zm0 0" fill="#fea500"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="51" xmlns="http://www.w3.org/2000/svg"><g fill="#737373"><path d="M28.21 1.082 6.696 13.367v24.414l21.516 12.285 21.516-12.285V13.367zm0 0"/><path d="m6.695 13.367 21.516 12.285 21.516-12.285L28.21 1.082zM6.695 37.781l21.516 12.285 21.516-12.285L28.21 25.652zm0 0"/></g><path d="M6.055 39.04 27.57 51.167c.16.156.48.156.64.156.157 0 .477-.156.638-.156l21.515-12.285c.477-.156.637-.63.637-1.102V13.367c0-.472-.16-.785-.637-1.101L28.848.136c-.48-.156-.797-.156-1.278 0L6.055 12.423c-.477.16-.637.633-.637 1.101v24.57c0 .16.16.634.637.946zM28.21 2.5l18.965 10.867L28.21 24.395 9.242 13.367zm2.547 23.152 17.691-10.078v21.574L29.484 48.02V26.44M7.97 35.574v-20L25.66 25.652l1.274.79V48.02L7.969 37.148" fill="#fff"/><path d="M11.953 35.102.637 41.559v12.757l11.316 6.457 11.156-6.457V41.56zm0 0" fill="#fff"/><path d="m.637 41.559 11.316 6.3 11.156-6.3-11.156-6.457zm0 12.757 11.316 6.457 11.156-6.457-11.156-6.457zm0 0" fill="#fff"/><path d="m.32 54.95 11.313 6.456c.16.157.16.157.32.157s.16 0 .32-.157l11.313-6.457c.16-.16.32-.316.32-.472V41.559c0-.157-.16-.473-.32-.473l-11.313-6.457a.496.496 0 0 0-.64 0L.32 41.086c-.16.16-.32.316-.32.473v12.757c0 .16.16.473.32.633zm11.633-19.06 9.883 5.669-9.883 5.828-9.883-5.828zm1.274 11.97 9.246-5.196v11.34l-9.883 5.512v-11.34M1.434 53.215v-10.55l9.246 5.194.636.473v11.34l-9.882-5.668" fill="#444"/><path d="m24.383 45.656-7.328 4.094v8.348l7.328 4.254 7.332-4.254V49.75zm0 0" fill="#517889"/><path d="m17.055 49.75 7.328 4.094 7.332-4.094-7.332-4.094zm0 8.348 7.328 4.254 7.332-4.254-7.332-4.254zm0 0" fill="#517889"/><path d="m16.895 58.57 7.332 4.254c.156 0 .156.156.156.156s.16 0 .16-.156l7.332-4.254c.16-.156.16-.156.16-.472V49.75c0-.156-.16-.316-.16-.473l-7.332-4.254c-.16-.156-.316-.156-.477 0l-7.332 4.254c-.16.157-.16.157-.16.473v8.348c.16.156.16.316.32.472zm7.488-12.441 6.535 3.621-6.535 3.621-6.531-3.777zm.957 7.715 5.898-3.465v7.402l-6.535 3.625V54.16m-7.172 3.149v-6.77l5.899 3.465.476.156v7.246l-6.535-3.625" fill="#fff"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="M7.225 18.125c-1 0-2-.1-2.8-.3v12.1h-3.8v-27.7c1.9-.9 4.5-1.6 7.2-1.6 6.8 0 9.9 3.6 9.9 8.7 0 4.9-3.2 8.8-10.5 8.8zm.6-15.2c-1.3 0-2.5.3-3.4.6v12.1c.8.1 1.8.2 2.8.2 5 0 6.8-2.9 6.8-6.5 0-3.9-1.8-6.4-6.2-6.4zm19.5 27.5c-2.2 0-4.7-.6-6.1-1.6l1-2.4c1.3.8 3.1 1.3 4.7 1.3 2.6 0 4.4-1.7 4.4-3.9 0-5.5-9.6-3.3-9.6-10.8 0-3.5 2.8-6.2 7-6.2 2.2 0 4.2.5 5.7 1.6l-1 2.2c-1.2-.8-2.7-1.3-4.2-1.3-2.6 0-3.8 1.6-3.8 3.6 0 5.2 9.7 3.1 9.7 10.8-.1 3.6-3.2 6.7-7.8 6.7zm0 0" fill="#03c" stroke="#03c" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(2.05225 0 0 2.0612 .316 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><g fill="#1d6fb5"><path d="M18.754 25.742c-2.668.36-4.8 3.219-4.8 6.258s2.132 6.078 4.8 6.258c2.668.355 4.805-2.504 4.805-6.258s-2.137-6.613-4.805-6.258zm0 0"/><path d="M.074 7.508v49.52L38.504 64V0zm18.68 34.683c-4.27-.539-7.649-5.187-7.649-10.191 0-5.184 3.38-9.652 7.649-10.191 4.27-.536 7.652 4.113 7.652 10.191 0 6.258-3.383 10.727-7.652 10.191zm50.172-27.175L47.754 32.715l-5.691-4.649v-14.66h26.863zm0 0"/><path d="m68.926 18.414-21.172 17.7-5.691-4.65V51.31h26.863zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#077265"><path d="M23.91 25.406c0-4.746-2.285-7.031-5.976-7.031-1.407 0-2.461.176-3.164.527V32.97c.703.351 1.582.527 2.636.527 4.219-.351 6.504-2.992 6.504-8.09zm0 0"/><path d="m0 53.715 38.68 9.844V.44L0 10.285zm11.605-38.508c1.582-.527 3.692-.703 6.153-.703 3.34 0 5.625 1.055 7.035 2.992 1.406 1.758 2.285 4.219 2.285 7.559s-.703 5.976-1.933 7.738c-1.762 2.637-4.575 4.043-7.739 4.043-1.054 0-1.933 0-2.636-.352v14.418h-3.34V15.207zm30.418-6.855v3.867h13.008V23.12H42.023v4.043h13.008v4.746H42.023v3.871h13.008v4.922H42.023v4.219h13.008v4.926H42.023v8.086H64V8.352zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="63" width="62" xmlns="http://www.w3.org/2000/svg"><path d="M29.816 62.316c-7.492-.168-12.261-2.523-13.453-6.398l-.172-.504V47.84c0-8.758 0-8.926.512-10.27.852-2.527 2.895-4.21 5.961-4.882l.68-.168h8.687c6.301 0 8.852 0 9.196-.168 1.87-.34 2.894-.676 3.914-1.516 1.363-1.012 2.386-2.695 2.726-4.379.34-1.348.34-1.18.34-5.89v-4.212h6.129l.684.168c3.234 1.012 5.617 3.872 6.64 8.586.34 1.852.34 1.852.34 6.399 0 4.379 0 4.379-.34 5.89-.34 1.348-.683 2.696-1.363 3.875-1.192 2.356-3.067 4.04-5.277 4.715-1.364.504-.172.336-12.774.504h-11.41v2.02h14.988v7.746c-.172.504-.172 1.008-.683 1.683-.508.672-1.36 1.512-1.871 2.02-2.043 1.348-5.11 2.187-9.196 2.355zm10.387-4.714c1.363-.168 2.555-1.684 2.215-3.028-.172-1.18-1.023-2.023-2.215-2.191-1.871-.168-3.406 1.347-3.066 3.031.172 1.348 1.363 2.188 2.726 2.188zM7.848 46.66c-1.703-.336-3.403-1.347-4.598-2.863C1.04 41.105-.152 36.39.016 30.668c.172-3.54.68-6.23 1.875-8.59 1.359-3.027 3.574-4.543 6.468-5.219.684-.168.684-.168 11.75-.168h11.239c.172 0 .172-.168.172-1.007v-1.012H16.535V10.8c0-4.211 0-4.211.34-5.051 1.363-2.695 5.11-4.379 11.066-4.883.512 0 1.536-.168 2.727-.168 6.64-.168 11.578 1.008 14.133 3.367l.851.84c.34.508.852 1.348 1.024 2.192l.168.504v8.082c0 7.406 0 8.078-.168 8.586-.172.671-.512 1.683-.684 2.187-1.02 1.852-2.722 3.031-4.937 3.703-1.364.336-.852.336-10.387.508-9.535 0-9.027 0-10.387.336-2.726.672-4.597 2.691-5.281 5.555-.34 1.347-.34 1.18-.34 5.89v4.38h-2.894c-2.895 0-3.746 0-3.918-.169zm16.007-36.027c1.024-.508 1.875-1.852 1.532-2.863-.34-1.012-1.02-1.852-1.871-2.188-1.532-.508-3.235.504-3.407 2.188-.168 1.347.512 2.695 1.875 3.03.168.169.508.169 1.02.169.34-.168.34-.168.851-.336zm0 0" fill="#fed142"/><path d="M7.848 46.66c-1.703-.336-3.403-1.347-4.598-2.863C1.04 41.105-.152 36.39.016 30.668c.171-3.54.68-6.23 1.875-8.59 1.359-3.027 3.574-4.543 6.468-5.219.684-.168.684-.168 11.75-.168h11.239c.172 0 .172-.168.172-1.007v-1.012H16.535V10.8c0-4.211 0-4.211.34-5.051 1.363-2.695 5.11-4.379 11.066-4.883.512 0 1.536-.168 2.727-.168 6.64-.168 11.578 1.008 14.133 3.367l.851.84c.34.508.852 1.348 1.024 2.192l.168.504v8.082c0 7.406 0 8.078-.168 8.586-.172.671-.512 1.683-.684 2.187-1.02 1.852-2.722 3.031-4.937 3.703-1.364.336-.852.336-10.387.508-9.535 0-9.027 0-10.387.336-2.726.672-4.597 2.691-5.281 5.555-.34 1.347-.34 1.18-.34 5.89v4.38h-2.894c-2.895 0-3.746 0-3.918-.169zm16.007-36.027c1.024-.508 1.875-1.852 1.532-2.863-.34-1.012-1.02-1.852-1.871-2.188-1.532-.508-3.235.504-3.407 2.188-.168 1.347.512 2.695 1.875 3.03.168.169.508.169 1.02.169.34-.168.34-.168.851-.336zm0 0" fill="#3571a3"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="62" xmlns="http://www.w3.org/2000/svg"><g fill="#666"><path d="M29.816 62.316c-7.492-.168-12.261-2.523-13.453-6.398l-.172-.504V47.84c0-8.758 0-8.926.512-10.27.852-2.527 2.895-4.21 5.961-4.882l.68-.168h8.687c6.301 0 8.852 0 9.196-.168 1.87-.34 2.894-.676 3.914-1.516 1.363-1.012 2.386-2.695 2.726-4.379.34-1.348.34-1.18.34-5.89v-4.212h6.129l.684.168c3.234 1.012 5.617 3.872 6.64 8.586.34 1.852.34 1.852.34 6.399 0 4.379 0 4.379-.34 5.89-.34 1.348-.683 2.696-1.363 3.875-1.192 2.356-3.067 4.04-5.277 4.715-1.364.504-.172.336-12.774.504h-11.41v2.02h14.988v7.746c-.172.504-.172 1.008-.683 1.683-.508.672-1.36 1.512-1.871 2.02-2.043 1.348-5.11 2.187-9.196 2.355zm10.387-4.714c1.363-.168 2.555-1.684 2.215-3.028-.172-1.18-1.023-2.023-2.215-2.191-1.871-.168-3.406 1.347-3.066 3.031.172 1.348 1.363 2.188 2.726 2.188zM7.848 46.66c-1.703-.336-3.403-1.347-4.598-2.863C1.04 41.105-.152 36.39.016 30.668c.172-3.54.68-6.23 1.875-8.59 1.359-3.027 3.574-4.543 6.468-5.219.684-.168.684-.168 11.75-.168h11.239c.172 0 .172-.168.172-1.007v-1.012H16.535V10.8c0-4.211 0-4.211.34-5.051 1.363-2.695 5.11-4.379 11.066-4.883.512 0 1.536-.168 2.727-.168 6.64-.168 11.578 1.008 14.133 3.367l.851.84c.34.508.852 1.348 1.024 2.192l.168.504v8.082c0 7.406 0 8.078-.168 8.586-.172.671-.512 1.683-.684 2.187-1.02 1.852-2.722 3.031-4.937 3.703-1.364.336-.852.336-10.387.508-9.535 0-9.027 0-10.387.336-2.726.672-4.597 2.691-5.281 5.555-.34 1.347-.34 1.18-.34 5.89v4.38h-2.894c-2.895 0-3.746 0-3.918-.169zm16.007-36.027c1.024-.508 1.875-1.852 1.532-2.863-.34-1.012-1.02-1.852-1.871-2.188-1.532-.508-3.235.504-3.407 2.188-.168 1.347.512 2.695 1.875 3.03.168.169.508.169 1.02.169.34-.168.34-.168.851-.336zm0 0"/><path d="M7.848 46.66c-1.703-.336-3.403-1.347-4.598-2.863C1.04 41.105-.152 36.39.016 30.668c.171-3.54.68-6.23 1.875-8.59 1.359-3.027 3.574-4.543 6.468-5.219.684-.168.684-.168 11.75-.168h11.239c.172 0 .172-.168.172-1.007v-1.012H16.535V10.8c0-4.211 0-4.211.34-5.051 1.363-2.695 5.11-4.379 11.066-4.883.512 0 1.536-.168 2.727-.168 6.64-.168 11.578 1.008 14.133 3.367l.851.84c.34.508.852 1.348 1.024 2.192l.168.504v8.082c0 7.406 0 8.078-.168 8.586-.172.671-.512 1.683-.684 2.187-1.02 1.852-2.722 3.031-4.937 3.703-1.364.336-.852.336-10.387.508-9.535 0-9.027 0-10.387.336-2.726.672-4.597 2.691-5.281 5.555-.34 1.347-.34 1.18-.34 5.89v4.38h-2.894c-2.895 0-3.746 0-3.918-.169zm16.007-36.027c1.024-.508 1.875-1.852 1.532-2.863-.34-1.012-1.02-1.852-1.871-2.188-1.532-.508-3.235.504-3.407 2.188-.168 1.347.512 2.695 1.875 3.03.168.169.508.169 1.02.169.34-.168.34-.168.851-.336zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="83" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientTransform="matrix(.14059 0 0 .09419 -.088 -.345)" gradientUnits="userSpaceOnUse" x1=".741" x2="590.86" y1="3.666" y2="593.79"><stop offset="0" stop-color="#cbced0"/><stop offset="1" stop-color="#84838b"/></linearGradient><linearGradient id="b" gradientTransform="matrix(.1139 0 0 .11627 -.088 -.345)" gradientUnits="userSpaceOnUse" x1="301.03" x2="703.07" y1="151.4" y2="553.44"><stop offset="0" stop-color="#276dc3"/><stop offset="1" stop-color="#165caa"/></linearGradient><g fill-rule="evenodd"><path d="M41.5 55.586C18.59 55.586.016 43.14.016 27.793.016 12.441 18.59 0 41.5 0s41.484 12.441 41.484 27.793c0 15.348-18.574 27.793-41.484 27.793zm6.352-44.719c-17.414 0-31.536 8.504-31.536 19 0 10.492 14.122 19 31.536 19s30.265-5.816 30.265-19c0-13.18-12.851-19-30.265-19zm0 0" fill="url(#a)"/><path d="M63.195 43.047s2.508.758 3.97 1.496c.503.254 1.382.766 2.01 1.437.622.657.923 1.325.923 1.325l9.894 16.687L64 64l-7.48-14.047s-1.532-2.633-2.473-3.398c-.785-.637-1.121-.864-1.899-.864h-3.8l.004 18.297-14.153.008V17.258h28.418s12.946.23 12.946 12.55-12.368 13.235-12.368 13.235zM57.04 27.395l-8.566-.004-.008 7.945 8.574-.004s3.969-.012 3.969-4.039c0-4.113-3.969-3.898-3.969-3.898zm0 0" fill="url(#b)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="54"><path style="fill-rule:nonzero;fill:#039;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#039;stroke-opacity:1;stroke-miterlimit:10" d="m10.45 23.48-7.7-8.5v-.1l7.7-8.5V.68L.25 12.681v4.5l10.2 11.898zM36.25.679V6.38l7.9 8.5v.1l-7.9 8.5v5.6l10.2-11.7v-4.9zM28.549 5.08c-1.299-.6-3-1.6-3.598-3 0-.7-.602-1.3-1.301-1.3-.7 0-1.299.6-1.299 1.3v17.3c-1.3-.7-3.9-.5-5.9.6-3.002 1.7-4.3 4.899-3.002 7.2 1.301 2.2 5.002 2.7 8 1 2.1-1.2 3.502-3.2 3.601-5.1v-15c4.4 0 7 1.8 6 6-.199.8-.401 1.6-.8 2.299-.2.302-.2.5.1.802.2.198.6.198 1-.2 1.7-1.6 2.7-3.7 2.7-6.001 0-2.9-2.9-4.8-5.501-5.9zm0 0" transform="matrix(1.79872 0 0 1.81157 0 .047)"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M61.988 2.012v59.976L46.996 17.004zM2.012 61.988h59.976L17.004 46.996zm14.992-14.992 44.984 14.992L32 32zM32 32l29.988 29.988-14.992-44.984zM2.012 46.996v14.992l14.992-14.992zM32 32H17.004v14.996zm14.996-14.996H32V32zM61.988 2.012H46.996v14.992zM17.004 32 2.012 46.996h14.992zM32 17.004 17.004 32H32zM46.996 2.012 32 17.004h14.996zm0 0" fill="#992315" stroke="#fff" stroke-width="1.66605"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="54"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.242 25.867c-.5 0-1.1-.2-1.498-.6l-8.4-8.401c-.802-.799-.802-2.099 0-3l8.4-8.398c.8-.8 2.098-.8 2.999 0 .8.8.8 2.098 0 2.999l-6.9 6.9 6.9 6.9c.8.801.8 2.1 0 3-.401.4-1 .6-1.5.6zm25.1 0c-.499 0-1.099-.2-1.5-.6-.8-.8-.8-2.099 0-3.002l6.9-6.898-6.9-6.9c-.8-.8-.8-2.101 0-3 .8-.8 2.1-.8 3.001 0l8.398 8.4c.802.8.802 2.1 0 3l-8.398 8.4c-.4.4-1.001.6-1.5.6zm-16.7 4.1c-.199 0-.398 0-.698-.1-1.102-.401-1.702-1.5-1.301-2.6l8.398-25.1c.4-1.1 1.5-1.699 2.6-1.301 1.102.4 1.702 1.5 1.301 2.601l-8.398 25.1c-.202.899-1.102 1.4-1.901 1.4zm0 0" transform="matrix(1.74425 0 0 1.75713 0 .013)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="54"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.242 25.867c-.5 0-1.1-.2-1.498-.6l-8.4-8.401c-.802-.799-.802-2.099 0-3l8.4-8.398c.8-.8 2.098-.8 2.999 0 .8.8.8 2.098 0 2.999l-6.9 6.9 6.9 6.9c.8.801.8 2.1 0 3-.401.4-1 .6-1.5.6zm25.1 0c-.499 0-1.099-.2-1.5-.6-.8-.8-.8-2.099 0-3.002l6.9-6.898-6.9-6.9c-.8-.8-.8-2.101 0-3 .8-.8 2.1-.8 3.001 0l8.398 8.4c.802.8.802 2.1 0 3l-8.398 8.4c-.4.4-1.001.6-1.5.6zm-16.7 4.1c-.199 0-.398 0-.698-.1-1.102-.401-1.702-1.5-1.301-2.6l8.398-25.1c.4-1.1 1.5-1.699 2.6-1.301 1.102.4 1.702 1.5 1.301 2.601l-8.398 25.1c-.202.899-1.102 1.4-1.901 1.4zm0 0" transform="matrix(1.74425 0 0 1.75713 0 .013)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M0 56.355v-7.644h15.29V64H0zm22.754 0v-7.644h15.289V64h-15.29zm22.933 0v-7.644h15.29V64h-15.29zM48 38.934l-5.332-5.332L48 28.266l5.332-5.332 5.336 5.332L64 33.602l-5.332 5.332a125.106 125.106 0 0 1-5.336 5.332zM0 33.602v-7.645h15.29v15.29H0zm22.754 0v-7.645h15.289v15.29h-15.29zM25.066 16l-5.332-5.332 5.332-5.336L30.398 0l5.336 5.332 5.332 5.336L35.734 16c-2.843 3.023-5.336 5.332-5.336 5.332zM0 10.668V3.023h15.29v15.29H0zm0 0" fill="#3a898d"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M54 36.781c0 14.856-12.04 26.813-27 26.813S0 51.637 0 36.78c0-5.12 1.547-9.906 4.129-14.004l8.77 4.953a16.11 16.11 0 0 0-2.75 9.051c0 9.223 7.566 16.735 16.851 16.735s16.852-7.512 16.852-16.735c0-7.683-5.329-14.172-12.551-16.222v8.539L6.19 14.754 31.301.406v9.906C44.199 12.364 54 23.462 54 36.782zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientTransform="matrix(.08235 0 0 .05573 -.06 .108)" gradientUnits="userSpaceOnUse" x1=".741" x2="590.86" y1="3.666" y2="593.79"><stop offset="0" stop-color="#cbced0"/><stop offset="1" stop-color="#84838b"/></linearGradient><linearGradient id="b" gradientTransform="matrix(.06671 0 0 .0688 -.06 .108)" gradientUnits="userSpaceOnUse" x1="301.03" x2="703.07" y1="151.4" y2="553.44"><stop offset="0" stop-color="#276dc3"/><stop offset="1" stop-color="#165caa"/></linearGradient><path d="M27.406 63.688V41.383h6.48l6.477 8.187 6.48-8.187h6.481v22.304h-6.48V50.898l-6.48 8.184-6.477-8.184v12.79zm38.875 0-9.719-10.844h6.481V41.383h6.477v11.46H76zm0 0" fill="#999"/><g fill-rule="evenodd"><path d="M24.297 33.2C10.879 33.2 0 25.84 0 16.757S10.879.312 24.297.312c13.422 0 24.3 7.364 24.3 16.446s-10.878 16.441-24.3 16.441zm3.719-26.458c-10.2 0-18.47 5.031-18.47 11.242 0 6.207 8.27 11.243 18.47 11.243s17.73-3.442 17.73-11.243c0-7.797-7.531-11.242-17.73-11.242zm0 0" fill="url(#a)"/><path d="M37.004 25.781s1.473.45 2.324.887c.297.152.813.453 1.18.852.363.386.539.78.539.78l5.797 9.876-9.367.004-4.38-8.313s-.898-1.555-1.449-2.008c-.46-.375-.66-.511-1.113-.511H28.31l.003 10.828-8.289.004V10.523h16.645s7.582.137 7.582 7.426c0 7.29-7.246 7.832-7.246 7.832zm-3.606-9.258-5.015-.003-.004 4.703 5.02-.004s2.324-.008 2.324-2.39c0-2.434-2.325-2.306-2.325-2.306zm0 0" fill="url(#b)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M68.633.102H16.39v7.226H5.738v2.274H0v2.062h5.738v2.684h10.653V26.94H5.738v2.477H0v2.066h5.738v2.27h10.653v13.625H5.738v2.48H0v2.063h5.738v2.273h10.653v9.703h52.242v-9.703h9.629v-2.48H84v-2.063h-5.738v-2.476h-9.63v-13.63h9.63v-2.062H84v-2.066h-5.738v-2.684h-9.63V14.141h9.63v-2.684H84V9.395h-5.738V7.12h-9.63zm-10.04 17.136c-2.253 0-4.097-1.86-4.097-4.129S56.34 8.98 58.594 8.98s4.097 1.86 4.097 4.13c0 2.476-1.843 4.128-4.097 4.128zm0 0" fill="#099"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#55486d"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="47" xmlns="http://www.w3.org/2000/svg"><path d="M10.62.6c-.398 0-.8.3-.998.7l-1 1.901h-6.8c-.7 0-1.302.6-1.302 1.2-.098 10.6.2 21.299 0 31.899 0 .7.602 1.3 1.302 1.3H26.02c.7 0 1.302-.6 1.302-1.3V4.4c0-.6-.602-1.302-1.302-1.302h-6.799l-1-1.899c-.2-.4-.7-.7-1.2-.7-2.2.1-4.501.1-6.4.1zm0 0" fill="#666" stroke="#666" stroke-miterlimit="10" transform="matrix(1.67417 0 0 1.65354 .211 0)"/><path d="M5.438 9.426h7.53c0 2.148.169 4.133 2.18 4.133h17.075c2.175 0 2.175-2.149 2.175-4.133h7.536v48.449H5.605c-.168-16.207-.168-32.41-.168-48.45zm0 0" fill="#fff"/><path d="M10.793 21H36.41v4.3H10.793zm0 8.434H36.41v4.296H10.793zm0 8.433H36.41V42H10.793zm0 8.434h17.078v4.297H10.793zm0 0" fill="#666"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M14.54 49.375c1.593 1.594 2.48 3.723 2.48 6.027 0 2.305-.887 4.43-2.48 6.028a8.46 8.46 0 0 1-6.032 2.48 8.451 8.451 0 0 1-6.028-2.48C.887 59.832 0 57.883 0 55.402c0-2.304.887-4.433 2.48-6.027a8.439 8.439 0 0 1 6.028-2.484c2.484 0 4.433 1.066 6.031 2.484zM.175 21.719v12.23c7.98 0 15.426 3.192 21.097 8.688 5.676 5.672 8.688 13.12 8.688 21.097h12.234c0-11.523-4.789-22.16-12.41-29.785C22.16 26.504 11.7 21.72.175 21.72zm0-21.63V12.32c28.367 0 51.59 23.227 51.59 51.59H64c0-17.55-7.09-33.504-18.793-45.027C33.684 7.18 17.73.09.176.09zm0 0" fill="#dd7d36"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#666"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M61.988 2.012v59.976L46.996 17.004zM2.012 61.988h59.976L17.004 46.996zm14.992-14.992 44.984 14.992L32 32zM32 32l29.988 29.988-14.992-44.984zM2.012 46.996v14.992l14.992-14.992zM32 32H17.004v14.996zm14.996-14.996H32V32zM61.988 2.012H46.996v14.992zM17.004 32 2.012 46.996h14.992zM32 17.004 17.004 32H32zM46.996 2.012 32 17.004h14.996zm0 0" fill="#666" stroke="#fff" stroke-width="1.66605"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M61.988 2.012v59.976L46.996 17.004zM2.012 61.988h59.976L17.004 46.996zm14.992-14.992 44.984 14.992L32 32zM32 32l29.988 29.988-14.992-44.984zM2.012 46.996v14.992l14.992-14.992zM32 32H17.004v14.996zm14.996-14.996H32V32zM61.988 2.012H46.996v14.992zM17.004 32 2.012 46.996h14.992zM32 17.004 17.004 32H32zM46.996 2.012 32 17.004h14.996zm0 0" fill="#992315" stroke="#fff" stroke-width="1.66605"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="71" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientTransform="matrix(.07693 0 0 .05184 -.057 .13)" gradientUnits="userSpaceOnUse" x1=".741" x2="590.86" y1="3.666" y2="593.79"><stop offset="0" stop-color="#cbced0"/><stop offset="1" stop-color="#84838b"/></linearGradient><linearGradient id="b" gradientTransform="matrix(.06233 0 0 .064 -.057 .13)" gradientUnits="userSpaceOnUse" x1="301.03" x2="703.07" y1="151.4" y2="553.44"><stop offset="0" stop-color="#276dc3"/><stop offset="1" stop-color="#165caa"/></linearGradient><path d="M22.7 30.914C10.163 30.914 0 24.066 0 15.617 0 7.168 10.164.32 22.7.32c12.534 0 22.698 6.848 22.698 15.297 0 8.45-10.16 15.297-22.699 15.297zm3.476-24.613c-9.531 0-17.254 4.683-17.254 10.457 0 5.777 7.723 10.46 17.254 10.46 9.527 0 16.558-3.202 16.558-10.46 0-7.254-7.03-10.457-16.558-10.457zm0 0" fill="url(#a)" fill-rule="evenodd"/><path d="M34.57 24.016s1.375.414 2.172.82c.278.144.758.426 1.102.793.34.363.504.73.504.73l5.414 9.184-8.75.004-4.094-7.73s-.836-1.45-1.352-1.872c-.43-.347-.613-.472-1.039-.472H26.45v10.07l-7.742.004V9.82h15.55s7.083.13 7.083 6.907c0 6.78-6.77 7.285-6.77 7.285zm-3.367-8.618h-4.687l-.004 4.372h4.691s2.172-.008 2.172-2.227c0-2.262-2.172-2.145-2.172-2.145zm0 0" fill="url(#b)" fill-rule="evenodd"/><path d="M-24.46 120.549a1.43 1.43 0 0 1-.97-.386l-5.364-5.43c-.517-.517-.517-1.357 0-1.94l5.43-5.43c.515-.517 1.357-.517 1.938 0 .518.517.518 1.36 0 1.94l-4.46 4.461 4.46 4.46c.518.517.518 1.356 0 1.939-.322.257-.71.386-1.035.386zm16.222 0a1.43 1.43 0 0 1-.97-.386c-.517-.517-.517-1.357 0-1.94l4.461-4.459-4.46-4.461c-.518-.517-.518-1.357 0-1.94.517-.517 1.357-.517 1.94 0l5.428 5.43c.517.517.517 1.357 0 1.94l-5.427 5.43a1.432 1.432 0 0 1-.972.386zm-10.793 2.65c-.13 0-.26 0-.452-.062-.71-.26-1.098-.971-.84-1.683l5.427-16.222c.26-.711.972-1.1 1.683-.84.71.257 1.098.969.84 1.68l-5.43 16.222c-.13.583-.71.906-1.228.906zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".97" transform="matrix(1.47916 0 0 1.48836 72.435 -120.409)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="fill-rule:nonzero;fill:#cd6799;fill-opacity:1;stroke-width:.25;stroke-linecap:butt;stroke-linejoin:miter;stroke:#cd6799;stroke-opacity:1;stroke-miterlimit:10" d="M40.764 20.63c-1.602 0-3.101.4-4.3 1-.4-.9-.9-1.601-1.001-2.198-.1-.7-.2-1.001-.1-1.8.1-.804.599-1.904.599-2.003 0-.098-.1-.498-1-.498-.998 0-1.797.099-1.797.4-.101.197-.302.799-.4 1.398-.102.9-1.903 3.901-2.903 5.502-.3-.7-.6-1.201-.7-1.6-.1-.701-.2-1.002-.1-1.8.1-.802.6-1.902.6-2.001 0-.101-.1-.498-1-.498s-1.801.098-1.801.4c-.096.3-.197.798-.398 1.4-.2.597-2.5 5.598-3.1 6.999-.302.698-.6 1.2-.7 1.598v.101c-.099.301-.3.5-.3.5-.1.2-.3.4-.4.4-.101 0-.101-.599 0-1.5.4-1.8 1.199-4.5 1.199-4.6 0-.1.1-.5-.5-.8-.7-.2-.9.101-.997.101-.103 0-.103.099-.103.099s.7-3.1-1.399-3.1c-1.3 0-3.2 1.5-4.2 2.801-.6.299-1.8.997-3.2 1.697-.499.301-1 .602-1.5.802l-.1-.101c-2.598-2.8-7.5-4.8-7.3-8.6.099-1.398.5-4.999 9.3-9.3 7.202-3.598 13-2.6 13.9-.4 1.4 3.2-3.1 9.002-10.6 9.799-2.901.303-4.3-.797-4.7-1.199-.4-.4-.4-.4-.6-.4-.2.102-.098.501 0 .701.2.6 1.202 1.6 2.699 2.101 1.4.397 4.7.698 8.702-.9 4.498-1.8 8.099-6.602 6.998-10.7-1-4.102-7.9-5.5-14.4-3.202-3.898 1.401-8.098 3.5-11.1 6.4-3.6 3.4-4.099 6.2-3.9 7.502.801 4.299 6.8 7.1 9.202 9.199-.1.1-.201.1-.302.1-1.2.6-5.7 2.901-6.897 5.5-1.3 2.9.197 4.898 1.2 5.1 3.099.898 6.198-.7 7.897-3.2 1.7-2.501 1.501-5.8.702-7.3v-.101c.3-.1.7-.4.998-.499.6-.4 1.2-.7 1.7-1-.299.8-.5 1.8-.599 3.2-.098 1.6.5 3.7 1.399 4.5.4.4.8.4 1.103.4.998 0 1.497-.801 1.998-1.8.6-1.2 1.2-2.6 1.2-2.6s-.7 3.799 1.2 3.799c.7 0 1.4-.9 1.7-1.3l.1-.099.1-.1c.299-.501.9-1.5 1.8-3.401 1.2-2.3 2.3-5.298 2.3-5.298s.1.698.4 1.899c.2.7.7 1.5 1 2.2-.3.4-.401.6-.401.6-.2.3-.398.599-.698.998-1 1.1-2.101 2.402-2.2 2.802-.1.4-.1.7.197 1 .201.2.703.2 1.202.2.8-.1 1.499-.3 1.698-.4.4-.1 1-.4 1.502-.8 1-.7 1.499-1.601 1.4-2.9 0-.701-.3-1.4-.5-2.1.1-.1.1-.2.2-.4 1.5-2.1 2.6-4.5 2.6-4.5s.1.7.4 1.9c.1.6.5 1.2.8 1.9-1.4 1.1-2.202 2.4-2.5 3.2-.5 1.501-.1 2.3.699 2.399.4.1.9-.101 1.202-.299.499-.1 1-.4 1.6-.801 1-.698 1.799-1.6 1.799-2.898 0-.602-.1-1.203-.4-1.701 1.2-.501 2.602-.7 4.5-.501 4.099.5 4.9 3.001 4.8 4.101-.1 1.1-1 1.7-1.3 1.8-.3.1-.4.2-.4.4s.1.1.4.1c.4-.1 2.1-.9 2.199-2.9.201-2.2-2.2-4.9-6.4-4.9zm-31.5 10.701c-1.3 1.5-3.203 2.099-4.002 1.5-.9-.501-.499-2.7 1.101-4.301.998-1 2.3-1.8 3.2-2.299.2-.1.5-.3.799-.5.1 0 .1-.101.1-.101.101-.101.101-.101.202-.101.6 2.2 0 4.1-1.4 5.802zm9.9-6.801c-.401 1.2-1.5 4.1-2.1 4-.501-.101-.801-2.301-.1-4.5.4-1.1 1.199-2.4 1.6-2.901.7-.8 1.499-1.1 1.799-.7.2.202-.9 3.4-1.2 4.1zm8.1 3.899c-.202.1-.403.1-.403.1l.1-.1s1-1.1 1.402-1.599c.199-.298.5-.7.8-1v.1c-.1 1.199-1.4 2.1-1.9 2.5zm6.2-1.401c-.1-.099-.1-.4.399-1.5.2-.399.7-1.1 1.4-1.8.1.301.1.503.1.802 0 1.6-1.102 2.2-1.9 2.498zm0 0" transform="matrix(1.7456 0 0 1.77926 .217 0)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="fill-rule:nonzero;fill:#cd6799;fill-opacity:1;stroke-width:.25;stroke-linecap:butt;stroke-linejoin:miter;stroke:#cd6799;stroke-opacity:1;stroke-miterlimit:10" d="M40.764 20.63c-1.602 0-3.101.4-4.3 1-.4-.9-.9-1.601-1.001-2.198-.1-.7-.2-1.001-.1-1.8.1-.804.599-1.904.599-2.003 0-.098-.1-.498-1-.498-.998 0-1.797.099-1.797.4-.101.197-.302.799-.4 1.398-.102.9-1.903 3.901-2.903 5.502-.3-.7-.6-1.201-.7-1.6-.1-.701-.2-1.002-.1-1.8.1-.802.6-1.902.6-2.001 0-.101-.1-.498-1-.498s-1.801.098-1.801.4c-.096.3-.197.798-.398 1.4-.2.597-2.5 5.598-3.1 6.999-.302.698-.6 1.2-.7 1.598v.101c-.099.301-.3.5-.3.5-.1.2-.3.4-.4.4-.101 0-.101-.599 0-1.5.4-1.8 1.199-4.5 1.199-4.6 0-.1.1-.5-.5-.8-.7-.2-.9.101-.997.101-.103 0-.103.099-.103.099s.7-3.1-1.399-3.1c-1.3 0-3.2 1.5-4.2 2.801-.6.299-1.8.997-3.2 1.697-.499.301-1 .602-1.5.802l-.1-.101c-2.598-2.8-7.5-4.8-7.3-8.6.099-1.398.5-4.999 9.3-9.3 7.202-3.598 13-2.6 13.9-.4 1.4 3.2-3.1 9.002-10.6 9.799-2.901.303-4.3-.797-4.7-1.199-.4-.4-.4-.4-.6-.4-.2.102-.098.501 0 .701.2.6 1.202 1.6 2.699 2.101 1.4.397 4.7.698 8.702-.9 4.498-1.8 8.099-6.602 6.998-10.7-1-4.102-7.9-5.5-14.4-3.202-3.898 1.401-8.098 3.5-11.1 6.4-3.6 3.4-4.099 6.2-3.9 7.502.801 4.299 6.8 7.1 9.202 9.199-.1.1-.201.1-.302.1-1.2.6-5.7 2.901-6.897 5.5-1.3 2.9.197 4.898 1.2 5.1 3.099.898 6.198-.7 7.897-3.2 1.7-2.501 1.501-5.8.702-7.3v-.101c.3-.1.7-.4.998-.499.6-.4 1.2-.7 1.7-1-.299.8-.5 1.8-.599 3.2-.098 1.6.5 3.7 1.399 4.5.4.4.8.4 1.103.4.998 0 1.497-.801 1.998-1.8.6-1.2 1.2-2.6 1.2-2.6s-.7 3.799 1.2 3.799c.7 0 1.4-.9 1.7-1.3l.1-.099.1-.1c.299-.501.9-1.5 1.8-3.401 1.2-2.3 2.3-5.298 2.3-5.298s.1.698.4 1.899c.2.7.7 1.5 1 2.2-.3.4-.401.6-.401.6-.2.3-.398.599-.698.998-1 1.1-2.101 2.402-2.2 2.802-.1.4-.1.7.197 1 .201.2.703.2 1.202.2.8-.1 1.499-.3 1.698-.4.4-.1 1-.4 1.502-.8 1-.7 1.499-1.601 1.4-2.9 0-.701-.3-1.4-.5-2.1.1-.1.1-.2.2-.4 1.5-2.1 2.6-4.5 2.6-4.5s.1.7.4 1.9c.1.6.5 1.2.8 1.9-1.4 1.1-2.202 2.4-2.5 3.2-.5 1.501-.1 2.3.699 2.399.4.1.9-.101 1.202-.299.499-.1 1-.4 1.6-.801 1-.698 1.799-1.6 1.799-2.898 0-.602-.1-1.203-.4-1.701 1.2-.501 2.602-.7 4.5-.501 4.099.5 4.9 3.001 4.8 4.101-.1 1.1-1 1.7-1.3 1.8-.3.1-.4.2-.4.4s.1.1.4.1c.4-.1 2.1-.9 2.199-2.9.201-2.2-2.2-4.9-6.4-4.9zm-31.5 10.701c-1.3 1.5-3.203 2.099-4.002 1.5-.9-.501-.499-2.7 1.101-4.301.998-1 2.3-1.8 3.2-2.299.2-.1.5-.3.799-.5.1 0 .1-.101.1-.101.101-.101.101-.101.202-.101.6 2.2 0 4.1-1.4 5.802zm9.9-6.801c-.401 1.2-1.5 4.1-2.1 4-.501-.101-.801-2.301-.1-4.5.4-1.1 1.199-2.4 1.6-2.901.7-.8 1.499-1.1 1.799-.7.2.202-.9 3.4-1.2 4.1zm8.1 3.899c-.202.1-.403.1-.403.1l.1-.1s1-1.1 1.402-1.599c.199-.298.5-.7.8-1v.1c-.1 1.199-1.4 2.1-1.9 2.5zm6.2-1.401c-.1-.099-.1-.4.399-1.5.2-.399.7-1.1 1.4-1.8.1.301.1.503.1.802 0 1.6-1.102 2.2-1.9 2.498zm0 0" transform="matrix(1.7456 0 0 1.77926 .217 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#a03537"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="79" xmlns="http://www.w3.org/2000/svg"><path d="m42.852 35.445 4.695 1.344 2.851-10.418-4.695-1.344c0-1.68-.168-3.359-.672-5.039l4.192-2.52-5.364-9.405-4.359 2.519a18.036 18.036 0 0 0-4.023-3.023l1.34-4.704L26.421 0l-1.34 4.703c-1.676 0-3.352.168-5.027.672l-2.516-4.2-9.387 5.376 2.512 4.199a18.053 18.053 0 0 0-3.016 4.031l-4.695-1.343L.105 23.852l4.692 1.343c0 1.68.168 3.36.672 5.04l-4.192 2.523 5.364 9.406 4.191-2.52a18.126 18.126 0 0 0 4.023 3.024l-1.34 4.703 10.395 2.856 1.34-4.704c1.676 0 3.352-.168 5.031-.671l2.512 4.199 9.39-5.375-2.515-4.2c1.172-1.175 2.348-2.519 3.184-4.03zm-25.985-5.547c-2.68-4.535-1.004-10.414 3.52-13.101 4.527-2.688 10.394-1.008 13.078 3.527 2.683 4.535 1.004 10.414-3.52 13.106-4.527 2.687-10.394 1.175-13.078-3.532zm50.63 33.262 6.034-3.527-1.676-2.856c.84-.84 1.508-1.68 2.012-2.687l3.184.84 1.844-6.887-3.184-.84c0-1.176-.168-2.183-.504-3.36l2.852-1.679-3.52-6.047-2.852 1.68c-.84-.84-1.675-1.512-2.683-2.016l.84-3.191-6.875-1.852-.836 3.196c-1.176 0-2.18.168-3.356.504l-1.675-2.86-5.7 3.7 1.676 2.855c-.836.84-1.508 1.68-2.012 2.687l-3.183-1.007-1.844 6.886 3.184.84c0 1.176.168 2.184.504 3.36l-2.852 1.68 3.523 6.046 2.848-1.68c.84.84 1.676 1.512 2.684 2.016l-.84 3.191L61.965 64l.836-3.191c1.176 0 2.18-.168 3.355-.504-.336 0 1.34 2.855 1.34 2.855zM57.101 50.563c-1.676-3.024-.668-6.887 2.347-8.567 3.02-1.68 6.875-.672 8.551 2.352 1.676 3.023.672 6.886-2.348 8.566-3.015 1.68-6.875.672-8.55-2.352zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M56.645 44.492c-.348-.512-.864-.68-1.38-.68H38.728c-.516 1.188-1.204 2.376-1.723 3.567-1.375 2.715-2.926 5.773-4.305 8.32v.168c-.863 1.528-2.41 2.38-4.133 2.38s-3.273-.852-4.136-2.38c-.516-.847-2.239-4.074-4.477-8.32-.691-1.188-1.207-2.379-1.723-3.567H9.27c-.516 0-1.204.34-1.375.852L.14 60.793a2.327 2.327 0 0 0 0 1.527c.343.512.863.68 1.379.68h46.34c.515 0 1.203-.34 1.374-.848l7.754-15.965c0-.68 0-1.187-.343-1.695zm0 0" fill="#039"/><path d="M28.39 0c-9.472 0-17.222 7.64-17.222 16.98 0 5.606 6.2 18.852 11.367 29.04 2.41 4.753 4.309 8.32 4.48 8.32.344.508.86.847 1.376.847.52 0 1.035-.34 1.379-.847 0 0 1.894-3.567 4.48-8.32 5.168-10.188 11.367-23.434 11.367-29.04C45.617 7.64 37.867 0 28.391 0zm0 27.168c-4.304 0-7.921-3.223-8.265-7.469v-.851c0-4.582 3.79-8.32 8.441-8.32 4.305 0 7.922 3.226 8.266 7.472v.848c0 4.586-3.789 8.32-8.441 8.32zm0 0" fill="#efce4a"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#c60" stroke="#c60" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#c60" stroke="#c60" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M30.871 11.793h2.602c.867 0 1.39-.691 1.39-1.387v-9.02C34.863.52 34.168 0 33.473 0H30.87c-.867 0-1.387.695-1.387 1.387v8.847c-.172.868.52 1.559 1.387 1.559zm21.336 18.906v2.602c0 .867.691 1.386 1.387 1.386h9.02c.866 0 1.386-.69 1.386-1.386v-2.602c0-.867-.695-1.387-1.387-1.387h-9.02c-.695-.175-1.386.52-1.386 1.387zM33.301 64c.867 0 1.386-.695 1.386-1.387v-9.02c0-.866-.69-1.386-1.386-1.386h-2.602c-.867 0-1.387.691-1.387 1.387v9.02c0 .866.692 1.386 1.387 1.386zM1.387 34.687h9.02c.866 0 1.386-.69 1.386-1.386v-2.602c0-.867-.691-1.387-1.387-1.387h-9.02C.52 29.313 0 30.005 0 30.7v2.602c0 .695.695 1.386 1.387 1.386zM47.176 18.56c.52.52 1.562.52 2.082 0l6.418-6.418c.52-.52.52-1.563 0-2.082L53.94 8.324c-.52-.52-1.562-.52-2.082 0l-6.418 6.418c-.52.52-.52 1.563 0 2.082zM53.94 55.5l1.735-1.734c.52-.52.52-1.559 0-2.078l-6.418-6.418c-.52-.524-1.563-.524-2.082 0l-1.735 1.734c-.52.52-.52 1.559 0 2.082L51.86 55.5c.52.523 1.387.523 2.082 0zm-41.629 0 6.418-6.414c.524-.523.524-1.563 0-2.082l-1.734-1.734c-.52-.524-1.558-.524-2.082 0L8.5 51.687c-.523.52-.523 1.56 0 2.079l1.734 1.734c.692.523 1.559.523 2.079 0zm2.602-36.941c.523.52 1.563.52 2.082 0l1.734-1.735c.524-.52.524-1.562 0-2.082l-6.418-6.418c-.519-.52-1.558-.52-2.078 0L8.5 10.06c-.523.52-.523 1.562 0 2.082zm0 0" fill="#fea500"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M30.906 0h3.746v2.496h-3.746zm31.532 26.066c.156-2.027 0-4.058-.465-5.93h-8.899v-3.745h7.18a15.572 15.572 0 0 0-2.031-2.496l-8.895-8.899-1.406 14.047c-.313 3.59-2.027 6.871-4.527 9.367v6.242h4.996c2.808 0 5.308 1.094 7.18 2.657 2.808-1.72 4.84-4.371 6.09-7.336h-8.587v-3.75h9.364zM34.651 2.652V7.18h-3.746V2.652H19.98l1.563 15.922c.312 2.813 1.559 5.465 3.746 7.34 2.027 1.871 4.836 2.965 7.648 2.965 2.81 0 5.461-1.094 7.649-2.965 2.027-1.875 3.434-4.527 3.746-7.34l1.563-15.922zm5.153 12.176h-4.996v6.09h-3.746v-6.09h-4.997v-3.746h13.739zm0 0"/><path d="M48.234 38.242h-8.742v-7.336c-2.027 1.094-4.37 1.563-6.71 1.563-2.344 0-4.684-.625-6.716-1.563v7.336h-8.738a7.33 7.33 0 0 0-7.336 7.34v4.68c5.774.937 10.145 5.933 10.145 11.863V64h23.57v-1.875c0-6.555 5.309-11.863 11.863-12.02v-4.523a7.33 7.33 0 0 0-7.336-7.34zm0 0"/><path d="M55.727 53.855c-4.528 0-8.274 3.747-8.274 8.27V64h6.402v-4.996h3.747V64H64v-1.875c0-4.68-3.746-8.27-8.273-8.27zm-37.93-34.968L16.39 4.84l-8.899 8.898a15.342 15.342 0 0 0-2.027 2.496h7.18v3.746H3.746c-.625 1.875-.781 3.903-.469 5.934h9.524v3.746H4.215c1.094 2.965 3.277 5.617 6.086 7.336 1.875-1.719 4.527-2.656 7.183-2.656h4.993v-6.242a13.914 13.914 0 0 1-4.68-9.211zM8.273 53.855C3.746 53.855 0 57.602 0 62.125V64h6.398v-4.996h3.747V64h6.402v-1.875c0-4.68-3.746-8.27-8.274-8.27zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#a03537"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#369"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M16.223 21.805.09 55.844l3.012 3.015 20.035-20.035c-.711-1.594-.532-3.543.886-4.96 1.774-1.774 4.43-1.774 6.204 0 1.773 1.769 1.773 4.429 0 6.202-1.243 1.243-3.368 1.594-4.965.887L5.23 60.984 8.242 64l34.04-16.133L49.73 27.48 36.61 14.36zm46.625-4.075L46.184 1.062c-1.418-1.417-3.547-1.417-4.965 0L37.32 4.966c-1.422 1.418-1.422 3.543 0 4.965l16.664 16.664c1.418 1.418 3.543 1.418 4.965 0l3.899-3.903c1.418-1.418 1.418-3.543 0-4.96zm0 0" fill="#fea500"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M46.168 13.516c1.793-.711 3.766-.891 5.738-.891V.008c-8.605-.18-16.851 3.554-22.23 10.308-2.153 2.844-4.125 5.864-5.559 9.243l-4.12 10.128c-1.079 3.024-2.333 6.223-3.767 9.067a31.916 31.916 0 0 1-3.945 6.754c-1.254 1.777-3.047 3.199-5.02 4.09-2.152 1.066-4.66 1.597-7.171 1.597v12.797c8.605.18 16.851-3.554 22.23-10.308 1.613-2.309 3.227-4.797 4.485-7.286l3.406-8h14.879v-12.62h-9.86c.715-1.954 1.793-3.731 3.047-5.508.895-1.602 2.153-2.844 3.407-3.91 1.613-1.422 3.046-2.313 4.48-2.844zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M46.168 13.516c1.793-.711 3.766-.891 5.738-.891V.008c-8.605-.18-16.851 3.554-22.23 10.308-2.153 2.844-4.125 5.864-5.559 9.243l-4.12 10.128c-1.079 3.024-2.333 6.223-3.767 9.067a31.916 31.916 0 0 1-3.945 6.754c-1.254 1.777-3.047 3.199-5.02 4.09-2.152 1.066-4.66 1.597-7.171 1.597v12.797c8.605.18 16.851-3.554 22.23-10.308 1.613-2.309 3.227-4.797 4.485-7.286l3.406-8h14.879v-12.62h-9.86c.715-1.954 1.793-3.731 3.047-5.508.895-1.602 2.153-2.844 3.407-3.91 1.613-1.422 3.046-2.313 4.48-2.844zm0 0" fill="#d10407"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><path d="M65.75 44.957S76.094 21.352 44.36.051c0 0 12.972 16.152 6.312 34.433 0 0-23.32-16.328-35.067-28.222 0 0 14.727 20.945 19.989 25.207 0 0-8.77-4.438-28.93-21.657 0 0 23.316 30.176 34.188 36.387 0 0-16.657 11.184-40.852-4.613 0 0 12.8 22.363 39.625 22.363 12.098 0 15.605-6.21 21.566-6.21 6.137 0 9.993 6.21 9.993 6.21 3.507-8.875-5.434-18.992-5.434-18.992zm0 0" fill="#fa2a22"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="63" width="60" xmlns="http://www.w3.org/2000/svg"><path d="M57.96 53.09 37.532 32.832l-.527-.523 3.7-3.844.526-.524c4.754 1.747 10.391.875 14.266-2.968 2.816-2.793 4.227-6.637 3.875-10.653 0-.523-.355-.875-.707-1.047-.352-.175-.879 0-1.23.348l-6.516 6.461-6.871-1.57-1.758-6.813 6.516-6.46c.351-.348.351-.872.351-1.223-.176-.348-.527-.7-1.058-.7-4.051-.347-7.922 1.051-10.743 3.844-3.87 3.844-4.93 9.43-2.992 14.145l-.527.523-5.285 5.067-9.86-9.782-.355-.347c.176-.176.176-.352.355-.7 2.114.348 5.809-3.668 8.98-6.812L18.337 0c-4.227 4.191-7.219 6.984-6.867 8.906-.88.524-1.762 1.047-2.465 1.57L7.77 11.7c-.88.875-1.407 1.922-1.582 2.969-.176.176-.176.352-.352.523l-.531 1.051v.172l-.528.7c-.351.35-.527.698-.703 1.222a.378.378 0 0 1-.351.352l-.18.171c-.176.524-.527.875-.703 1.399-.176.523-.527 1.222-.703 1.922v.347c0 .176-.176.524-.176.7l-.176.875c-.176.523-.176 1.046-.176 1.57v3.144l.176.696v.351c0 .348.176.524.176.871l.527 1.575c.176.523.703.87 1.235.87.351 0 .527-.171.703-.347s.351-.352.351-.7l.176-1.745c0-.176 0-.348.176-.524 0-.175 0-.351.176-.351l.175-.696s0-.175.176-.175c0 0 0-.176.18-.176 0-.172.176-.348.176-.348.175-.351.175-.523.351-.699 0-.176.176-.176.176-.348.176-.351.352-.527.527-.875l.352-.523c0-.176.176-.176.352-.352l.18-.172c.175-.351.527-.523.878-.875l.176-.171c.176-.176.527-.352.703-.528.176 0 .176-.172.355-.172.176 0 .176-.175.352-.175.176-.176.352-.348.703-.348l.528-.352.53-.175c.177-.172.352-.172.528-.172s.176 0 .352-.176c0 0 .175 0 .175-.176.176 0 .176 0 .352-.172h.176c.176.172.351.524.531.696l9.684 9.605L2.488 51.52c-2.64 2.617-2.992 6.285-.351 8.906 2.64 2.617 6.164 1.918 8.629-.7l18.136-19.035.356.348 20.426 20.258a5.883 5.883 0 0 0 8.277 0 5.763 5.763 0 0 0 0-8.207zM7.95 57.629c-.884.875-2.47.875-3.348 0-.88-.871-.88-2.445 0-3.316.878-.876 2.464-.876 3.347 0a2.134 2.134 0 0 1 0 3.316zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#4d1b9b"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M12.726.224c-2.801 0-5.202 2.302-5.202 5.2 0 2.8 2.302 5.2 5.202 5.2.9 0 1.698-.199 2.4-.599l2.099 2.101-6 6c-.999.999-.999 2.7 0 3.698l.2.202L23.524 9.925c.702.4 1.602.601 2.4.601 2.801 0 5.202-2.302 5.202-5.2 0-2.8-2.302-5.2-5.202-5.2-2.798 0-5.199 2.3-5.199 5.2 0 .898.2 1.698.601 2.4l-2.101 2.098-2.1-2.098c.399-.702.6-1.6.6-2.4.2-2.8-2.2-5.102-5-5.102zm0 3.001c1.199 0 2.2 1 2.2 2.2 0 1.2-1.001 2.2-2.2 2.2a2.22 2.22 0 0 1-2.2-2.2c0-1.3.998-2.2 2.2-2.2zm13.3 0c1.199 0 2.2 1 2.2 2.2 0 1.2-1.001 2.2-2.2 2.2a2.22 2.22 0 0 1-2.2-2.2c0-1.3.998-2.2 2.2-2.2zm-22.902 10.3c-.198 0-.398 0-.598.1-.2 0-.4.101-.601.2-.2.1-.3.2-.5.301-.1.098-.301.199-.4.3-.099.1-.2.299-.3.4-.1.1-.2.299-.3.5-.1.199-.1.3-.2.498v.1c0 .202-.1.4-.1.602v17.8c0 .198 0 .4.1.598 0 .201.1.4.2.601.1.2.2.3.3.501.1.098.201.3.3.4.099.098.3.199.4.3.1.1.3.198.5.299.2.1.299.1.5.201h.1c.2 0 .401.098.599.098h32.602c.198 0 .398 0 .598-.098.2 0 .4-.1.601-.201.2-.1.3-.199.5-.3.1-.1.301-.2.4-.299.099-.1.2-.302.3-.4.1-.1.2-.302.3-.5.1-.202.1-.3.2-.501v-.1c0-.2.1-.4.1-.6V16.526c0-.201 0-.4-.1-.601 0-.199-.1-.4-.2-.599-.1-.201-.2-.302-.3-.5-.1-.101-.201-.3-.3-.4-.099-.101-.3-.202-.4-.3-.1-.1-.3-.201-.5-.302-.2-.098-.299-.098-.5-.199h-.1c-.2 0-.401-.1-.599-.1h-11.9l-3.002 3.001h11.802c0 1.6 1.298 2.999 2.999 2.999v11.8c-1.6 0-2.999 1.3-2.999 3h-26.6c0-1.6-1.3-3-3.001-3v-11.9c1.6 0 3.001-1.299 3.001-3h3.4l2.998-3zm16.301 5.9c-3.3 0-5.9 3-5.9 6.699 0 2.1.899 4 2.2 5.2h7.3c1.401-1.2 2.2-3.1 2.2-5.2.1-3.698-2.601-6.7-5.8-6.7zm-11.9 4.5c-.8 0-1.499.7-1.499 1.5s.7 1.499 1.498 1.499c.801 0 1.5-.7 1.5-1.5s-.699-1.499-1.5-1.499zm23.7 0c-.8 0-1.501.7-1.501 1.5s.702 1.499 1.5 1.499c.801 0 1.501-.7 1.501-1.5s-.7-1.499-1.5-1.499zm0 0" fill="#83ad51" stroke="#83ad51" stroke-miterlimit="10" stroke-width=".25" transform="matrix(1.6973 0 0 1.70894 .53 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M51.227 30.453c.687.688 1.89.688 2.574-.172.687-.691.687-1.722.172-2.582l-4.805-5.676a1.33 1.33 0 0 1-.34-.863l-.687-10.664c0-1.035-.86-1.723-1.887-1.723-1.031 0-1.719.688-1.887 1.723v.172l-.687 11.7v.171c0 .688.172 1.375.855 1.89l.344.344zm0 0"/><path d="M46.426 0C36.645 0 28.41 6.367 25.32 15.14c1.887.172 3.774.688 5.664 1.204 2.403-6.192 8.407-10.668 15.442-10.668 9.094 0 16.469 7.398 16.469 16.52 0 8.257-6.004 15.136-13.899 16.343.172 1.031.172 2.234.172 3.266 0 .863 0 1.722-.172 2.582 11.152-1.203 19.734-10.668 19.734-22.02C68.73 10.152 58.777 0 46.426 0zm0 0"/><path d="M42.648 38.71h-2.914c-.515-2.41-1.375-4.644-2.746-6.71l2.059-2.066c.687-.688.687-1.891 0-2.579l-2.059-2.066c-.687-.687-1.886-.687-2.574 0l-2.059 2.066c-2.058-1.378-4.289-2.41-6.69-2.753v-2.926c0-1.031-.86-1.89-1.888-1.89H20.86c-1.027 0-1.886.859-1.886 1.89v2.926c-2.403.515-4.633 1.375-6.692 2.753l-1.886-2.238c-.688-.687-1.887-.687-2.575 0l-2.058 2.067c-.688.687-.688 1.89 0 2.578l1.886 2.066c-1.37 2.063-2.402 4.3-2.746 6.711H2.16c-1.031 0-1.89.86-1.89 1.89v2.926c0 1.032.859 1.891 1.89 1.891h2.742c.516 2.41 1.375 4.645 2.746 6.711l-1.886 1.89c-.688.692-.688 1.895 0 2.583l2.058 2.066c.688.688 1.887.688 2.575 0l2.058-1.894c2.059 1.375 4.29 2.41 6.692 2.753v2.754c0 1.032.859 1.891 1.886 1.891h2.918c1.028 0 1.887-.86 1.887-1.89v-2.755c2.402-.515 4.633-1.378 6.691-2.753l1.887 2.066c.688.687 1.887.687 2.574 0l2.059-2.066c.687-.688.687-1.891 0-2.579l-2.059-2.066c1.371-2.066 2.403-4.3 2.746-6.71h2.914c1.032 0 1.887-.86 1.887-1.892V40.43c0-.86-.855-1.72-1.887-1.72zM29.094 48.86c-3.774 3.785-9.95 3.785-13.723 0-3.777-3.786-3.777-9.977 0-13.762 3.774-3.785 9.95-3.785 13.723 0 3.949 3.785 3.777 9.976 0 13.761zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="58" xmlns="http://www.w3.org/2000/svg"><path d="M57.938 50.598c-.325 1.437-1.29 2.394-2.415 3.511-14.785 15.325-40.34 12.45-51.109-5.906-2.89-4.95-4.5-10.055-4.34-15.8.324-7.184 2.735-13.407 7.235-18.997C12.613 6.703 19.363 2.234 28.043.796 30.129.48 32.219.32 34.309 0v5.906l-4.34.477C17.754 8.14 7.469 18.355 6.664 30.168c-.48 7.34 1.45 14.047 6.43 19.633 1.93 2.394 4.18 4.469 6.91 5.746.805.32 1.77.476 2.574.637-7.715-4.47-12.215-11.172-13.664-19.793-.965-5.746.16-11.332 3.375-16.278C20.49 7.5 34.793 5.426 44.594 9.578c-.801 1.754-1.606 3.512-2.41 5.106-1.926-.317-3.856-.957-5.786-.957-5.625-.32-10.605.957-14.785 4.949-10.61 9.734-7.875 24.738 2.41 31.445 3.215 2.234 6.75 3.828 10.606 4.625.965.32 1.93 0 3.055-.156-.16-.16-.32-.16-.48-.16-4.825-.957-9.325-2.555-13.18-5.907-3.86-3.351-6.75-7.503-7.235-12.77-.48-7.503 2.414-13.566 8.84-17.398 5.625-3.511 11.574-3.511 17.52-.636 3.374 1.593 5.785 4.148 7.714 7.34-1.765.957-3.375 1.757-4.98 2.554-1.45-1.437-2.735-3.031-4.34-3.992-7.555-5.105-18.164-.316-18.965 9.102-.324 4.629 1.606 8.14 4.82 11.332 3.856 3.511 8.52 4.789 13.66 5.425 4.985.641 9.965.48 14.95-.16.965-.16 1.445.48 1.93 1.278 0-.16 0 0 0 0zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M60.89 0H10.974C10.238 0 9.69.547 9.69 1.281v3.84c0 .73.73 1.277 1.282 1.277h46.629v46.813c0 .55.73 1.281 1.277 1.281h3.84A1.25 1.25 0 0 0 64 53.211V3.109C64 1.281 62.719 0 60.89 0zm0 0"/><path d="M49.922 12.8H1.282C.546 12.8 0 13.349 0 14.079v48.64C0 63.27.73 64 1.281 64h48.64c.548 0 1.278-.547 1.278-1.281v-48.64c0-.731-.547-1.278-1.277-1.278zm-27.43 43.52c0 .547-.73 1.282-1.281 1.282H7.863c-.73 0-1.281-.551-1.281-1.282v-3.84c0-.55.73-1.28 1.281-1.28h13.164c.735 0 1.282.55 1.282 1.28v3.84zm0-12.8c0 .55-.73 1.28-1.281 1.28H7.863c-.73 0-1.281-.55-1.281-1.28v-3.84c0-.73.73-1.282 1.281-1.282h13.164c.735 0 1.282.551 1.282 1.282v3.84zm22.309 12.8c0 .547-.551 1.282-1.281 1.282H30.172c-.73 0-1.281-.551-1.281-1.282v-3.84c0-.55.55-1.28 1.28-1.28H43.52c.55 0 1.28.55 1.28 1.28zm0-12.8c0 .55-.551 1.28-1.281 1.28H30.172c-.73 0-1.281-.55-1.281-1.28v-3.84c0-.73.55-1.282 1.28-1.282H43.52c.55 0 1.28.551 1.28 1.282zm0-12.801c0 .55-.551 1.281-1.281 1.281H7.68a1.25 1.25 0 0 1-1.282-1.281V20.48c0-.73.735-1.28 1.282-1.28h35.84c.73 0 1.28.55 1.28 1.28zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="37" xmlns="http://www.w3.org/2000/svg"><path d="M21.914 13.86V0h-9.039c-.187.547-.371 1.094-.371 1.824-.184.363-.184.547-.184.91-.922 5.106-3.691 8.754-8.3 10.758-1.293.547-2.582.73-3.875.547v11.125h6.64c.184 15.68.184 23.883.184 24.25v.91c.926 6.93 4.43 10.942 10.886 12.582 2.583.73 5.348 1.094 8.301 1.094 3.688-.184 7.196-.73 10.7-1.824v-13.13a101.367 101.367 0 0 0-5.536 1.645c-3.136.91-5.902.364-8.117-1.824-.183-.367-.55-.73-.55-1.094a23.898 23.898 0 0 1-.555-5.105V25.164h14.386V14.04h-14.57zm0 0" fill="#4065aa"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="52"><path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1f7244;stroke-opacity:1;stroke-miterlimit:10" d="M0 1.5h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 7.4h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 13.3h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 19.2h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 25.1h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44" transform="matrix(1.9091 0 0 1.92593 0 .385)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="65" xmlns="http://www.w3.org/2000/svg"><path d="M43.223.492V13.66h-1.32c-.825-3-1.65-5.168-2.473-6.5-.825-1.332-2.145-2.336-3.797-3.168-.824-.5-2.473-.668-4.781-.668h-3.63v37.512c0 2.5.165 4 .329 4.668.328.668.824 1.168 1.648 1.668s1.817.664 3.301.664h1.648v1.336H9.074v-1.336h1.649c1.32 0 2.476-.332 3.3-.832.66-.332 1.153-.832 1.485-1.668.328-.5.328-2 .328-4.5V3.324h-3.461c-3.3 0-5.61.668-7.098 2.004-1.976 2-3.297 4.664-3.957 8.332H0V.492zm0 0" fill="#7291a1"/><path d="M65 14.828V28h-1.32c-.825-3-1.649-5.168-2.473-6.504-.828-1.332-2.145-2.332-3.797-3.168-.824-.5-2.472-.664-4.785-.664h-3.629v37.508c0 2.5.168 4.004.332 4.668.328.668.824 1.168 1.649 1.668.824.5 1.816.668 3.3.668h1.649v1.332H30.684v-1.332h1.652c1.32 0 2.473-.336 3.297-.836.66-.332 1.156-.832 1.488-1.664.328-.5.328-2.004.328-4.504V17.664h-3.465c-3.3 0-5.609.664-7.093 2-1.98 2-3.301 4.668-3.961 8.336h-1.317V14.828zm0 0" fill="#36454d"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><path d="M66.387 64c-4.18-36.844 4.875-26.973 4.875-26.973-9.926-11.988-14.973.528-17.414 12.164C52.628 30.504 46.883-.523 23.898.008c12.364 0 13.059 25.383 11.84 43.719-10.797-19.391-35-17.98-35-17.98s18.11-.708 18.11 38.077h47.539zm0 0" fill="#7faf4a"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#666"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#d5006e"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M3.121 5.453v2.363L.227 8.91V6.547zm0 23.637v2.363L.227 32.727v-2.364zm0 24v2.547l-2.894 1.09v-2.364zm-.363-39.817v2.18L.406 16.547v-2.184zm0 8v2.18L.406 24.547v-2.184zm0 16v2.18L.406 40.547v-2.184zm0 8v2.18L.406 48.547v-2.184zm7.414-38.546v2.91L6.375 11.09V8.363zm0 24v2.726L6.375 34.91v-2.726zm0 24.183v2.727l-3.797 1.636v-2.726zm-.184-39.82v2.547l-3.07 1.273v-2.547zm0 8v2.547l-3.07 1.273v-2.726zm0 16v2.547l-3.07 1.273v-2.726zm0 8v2.547l-3.07 1.273v-2.547zm7.414-38.543V12l-5.062 2v-3.273zm0 24V36l-5.062 2v-3.273zm0 23.816v3.453l-5.062 2v-3.453zm-.18-39.453V20l-4.16 1.637v-2.91zm0 8v2.906l-4.16 1.637v-2.906zm0 16v2.906l-4.16 1.637v-2.906zm0 7.637v3.09l-4.16 1.636v-2.91zm8.133-40.184v4.547l-6.144 2.543V11.09zm0 24v4.547l-6.144 2.363v-4.546zm0 23.82v4.544l-6.144 2.546V58.91zm-.359-39.456v4.183l-5.242 2.18v-4.18zm0 8v4l-5.242 2.183v-4.183zm0 16v4.183l-5.242 2v-4.183zm0 7.636v4.184l-5.242 2v-4zm8.496-40.726v5.816l-6.87 2.73v-5.82zm0 24v5.816l-6.87 2.73v-5.82zm0 23.816v5.82L26.622 64v-5.816zm-.363-39.09v4.91l-5.785 2.364v-4.91zm0 7.637v4.91l-5.785 2.363v-4.91zm0 16.363v4.91l-5.965 2.18v-4.906zm0 7.637v4.91l-5.785 2.363v-4.91zm8.68-43.273v8l-7.414 3.09V8.362c2.53-1.636 5.062-2.726 7.414-3.636zm0 8.726v6.91l-7.414 3.09v-6.906zm0 7.82v6.91l-7.414 3.09v-6.91zm0 7.817V36l-7.414 3.09v-6.906zm0 7.82V44l-7.414 3.09V40zm0 8v6.906l-7.414 3.274V48zm0 7.817v7.457c-2.895 1.09-5.426 2.18-7.414 3.27V56zM79.773 4.91v56c-4.699-3.094-10.668-4.726-17.535-4.726-5.785 0-12.297 1.27-19.527 3.632v-7.632c3.797-1.457 7.957-2.547 12.656-3.274V30.727c-3.797.546-8.137 1.82-12.656 4v-5.274a48.751 48.751 0 0 1 12.656-3.816V7.817C51.391 8.546 47.051 10 42.711 12V4.184C49.039 1.454 55.367 0 61.51 0c6.512.184 12.657 1.816 18.262 4.91zM72.36 9.816c-3.07-1.632-6.687-2.363-10.847-2.363h-1.446v18.184h1.63c3.613 0 7.23.547 10.663 1.816zm0 22.73c-3.254-1.456-6.867-2.183-10.664-2.183h-1.629v18.184h1.63c3.976 0 7.41.543 10.663 1.453zm4.883 30.727V62H76.7v-.184h1.266V62h-.543v1.273zm.903 0v-1.457h.363l.543 1.094.543-1.094h.18v1.457h-.18V62l-.543 1.09h-.184L78.328 62v1.273zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="71" xmlns="http://www.w3.org/2000/svg"><path d="M42.602 10.79V7.132C42.602 3.293 39.324 0 35.5 0s-7.102 3.293-7.102 7.133v3.656H.184V64h70.632V10.79zm21.117 46.077H7.28V17.738h21.117v3.473h14.204v-3.473h21.117zm0 0"/><path d="M24.941 32c0 2.02-1.628 3.656-3.64 3.656S17.66 34.02 17.66 32s1.628-3.656 3.64-3.656S24.94 29.98 24.94 32zM21.3 39.133c-3.823 0-7.1 3.289-7.1 7.129v3.66h14.198v-3.66c0-4.024-3.093-7.13-7.097-7.13zm17.84-10.79H56.8v7.13H39.14zm0 14.446H56.8v7.133H39.14zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="71" xmlns="http://www.w3.org/2000/svg"><path d="M42.602 10.79V7.132C42.602 3.293 39.324 0 35.5 0s-7.102 3.293-7.102 7.133v3.656H.184V64h70.632V10.79zm21.117 46.077H7.28V17.738h21.117v3.473h14.204v-3.473h21.117zm0 0"/><path d="M24.941 32c0 2.02-1.628 3.656-3.64 3.656S17.66 34.02 17.66 32s1.628-3.656 3.64-3.656S24.94 29.98 24.94 32zM21.3 39.133c-3.823 0-7.1 3.289-7.1 7.129v3.66h14.198v-3.66c0-4.024-3.093-7.13-7.097-7.13zm17.84-10.79H56.8v7.13H39.14zm0 14.446H56.8v7.133H39.14zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#4d1b9b"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#4d1b9b"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#039"><path d="m31.793 20.445 11.852 8.008V.246L31.793 8.254V4.25c0-2.184-1.797-4.004-3.953-4.004H3.953C1.797.246 0 2.066 0 4.25v20.2c0 2.183 1.797 4.003 3.953 4.003H27.84c2.156 0 3.953-1.82 3.953-4.004zm18.32 7.278v12.011c0 4.368 3.59 8.008 7.903 8.008 4.308 0 7.902-3.64 7.902-8.008V27.723c0-4.368-3.594-8.004-7.902-8.004-4.313 0-7.903 3.636-7.903 8.004zm0 0"/><path d="M70.047 39.734c0 6.73-5.387 12.008-11.852 12.008-6.648 0-11.855-5.457-11.855-12.008h-3.953A15.96 15.96 0 0 0 54.242 55.2v8.555h7.903v-8.555A15.955 15.955 0 0 0 74 39.734zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zm0 0" fill="#265db4" stroke="#265db4" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M37.8 64.8c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><g fill="#265db4" stroke="#265db4"><path d="M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M18.785 46.805c3.504-1.43 7.164-2.387 10.985-2.547V34.07H16.398c.16 4.297.954 8.754 2.387 12.735zM11.461 7.64c1.594 1.593 3.504 2.867 5.254 3.98 1.594-3.66 3.664-7.164 6.21-10.348-4.3 1.274-8.12 3.344-11.464 6.368zm33.434 9.554c-3.5 1.43-7.165 2.387-10.985 2.547V29.93h13.375c-.16-4.297-.957-8.754-2.39-12.735zM29.93 19.582c-3.82-.316-7.64-1.113-10.985-2.547A43.883 43.883 0 0 0 16.56 29.93h13.37zm-9.551-6.207c3.023 1.273 6.207 1.91 9.55 2.227V0h-.316L27.86 1.91c-3.343 3.344-5.73 7.324-7.48 11.465zM47.285 33.91H33.91v10.188c3.82.32 7.64 1.117 10.985 2.55 1.433-3.824 2.23-8.28 2.39-12.738zM33.75 15.762c3.344-.32 6.527-.957 9.555-2.23-1.91-4.298-4.457-8.118-7.485-11.622L34.547.16h-.637zm18.629-8.278c-3.344-2.867-7.324-5.097-11.621-6.21a45.734 45.734 0 0 1 6.207 10.347c1.91-1.113 3.66-2.387 5.414-4.137zm-22.45 40.754c-3.343.32-6.527.957-9.55 2.23 1.91 4.137 4.297 8.118 7.324 11.462l1.59 1.75h.477zM12.419 30.09c.16-5.094 1.273-9.871 2.707-14.649-2.39-1.273-4.457-2.863-6.687-4.933l-.16-.16C3.503 15.602.32 22.449 0 30.09zm38.848 3.82c-.16 5.094-1.278 9.871-2.707 14.649 2.386 1.273 4.457 2.863 6.683 4.933l.32.32C60.34 48.56 63.523 41.712 64 34.07c-.32-.16-12.734-.16-12.734-.16zm3.82-23.402c-1.91 1.91-4.3 3.504-6.688 4.933 1.75 4.618 2.707 9.555 2.707 14.649H63.68c-.477-7.64-3.5-14.488-8.438-19.742zm-2.867 45.851c-1.594-1.593-3.504-2.867-5.254-3.98a45.734 45.734 0 0 1-6.207 10.348c4.297-1.274 8.277-3.344 11.46-6.368zM8.598 53.492c1.91-1.91 4.297-3.504 6.527-4.933-1.75-4.618-2.707-9.555-2.867-14.649H0c.477 7.64 3.504 14.488 8.277 19.742zm8.117-1.113c-1.91 1.113-3.66 2.547-5.254 3.98 3.344 2.864 7.324 5.094 11.625 6.207-2.707-3.023-4.777-6.367-6.371-10.187zm26.59-1.754c-3.028-1.273-6.211-1.91-9.555-2.227V64h.48l1.75-1.91c3.184-3.344 5.57-7.485 7.325-11.465zm0 0" fill="#bababa"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg> |
| 1 | ||
| 1 | <svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M16.223 21.805.09 55.844l3.012 3.015 20.035-20.035c-.711-1.594-.532-3.543.886-4.96 1.774-1.774 4.43-1.774 6.204 0 1.773 1.769 1.773 4.429 0 6.202-1.243 1.243-3.368 1.594-4.965.887L5.23 60.984 8.242 64l34.04-16.133L49.73 27.48 36.61 14.36zm46.625-4.075L46.184 1.062c-1.418-1.417-3.547-1.417-4.965 0L37.32 4.966c-1.422 1.418-1.422 3.543 0 4.965l16.664 16.664c1.418 1.418 3.543 1.418 4.965 0l3.899-3.903c1.418-1.418 1.418-3.543 0-4.96zm0 0" fill="#fea500"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#6190aa;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#6190aa;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#6190aa;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.143 25.867c-.5 0-1.1-.2-1.502-.6l-8.298-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-6.901 6.9 6.9 6.9c.8.8.8 2.098 0 3-.5.4-1.1.6-1.6.6zm25.101 0c-.503 0-1.102-.2-1.502-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.4c-.4.4-.999.6-1.5.6zm-16.7 4.1c-.202 0-.403 0-.7-.1-1.102-.399-1.7-1.5-1.3-2.599l8.398-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1.1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="78" xmlns="http://www.w3.org/2000/svg"><path d="M60.387 42.926c.734.367 1.648.738 2.383 1.293 2.382 1.48 4.585 2.957 6.785 4.437.367.184.734.371 1.101.184 2.567-.555 5.317.926 6.051 3.512.55 1.664.734 3.328.918 5.18.184 2.03.184 4.25.367 6.468-3.668-.926-7.152-2.035-9.719-4.805-.734-.742-.918-1.851-1.468-2.773-.184-.188-.368-.555-.551-.555A61.383 61.383 0 0 1 52.5 50.875c-.184-.188-.734-.188-.918 0-4.398 2.219-9.168 3.14-14.121 2.773-6.602-.37-12.652-2.59-18.52-5.363-.918-.555-2.02-1.11-2.937-1.664 4.586-2.219 6.605-5.914 6.605-10.906-1.101-.184-2.203-.184-3.12-.371-.184.926-.184 2.035-.368 3.144-1.098 4.067-4.95 6.285-9.168 5.176C4.453 42.184-.5 35.714.051 29.43c.367-4.621 4.402-7.578 8.8-6.47.735.185 1.47.368 2.204.74.734.366 1.28.738 2.015 1.109.551-.926.918-2.036 1.47-2.957.183-.188 0-.555 0-.743V6.32h.183c.183.184.367.371.55.739 1.649 2.406 3.118 4.808 5.133 6.843 2.383 2.215 4.77 4.067 7.887 4.805.918.184 1.648.184 2.566-.371 3.485-2.219 6.97-2.402 10.637-.555.184.188.55.188.918.188 5.133-1.297 9.902-3.512 13.754-7.395 1.652-1.664 2.934-3.699 3.852-6.101.367-1.11.917-2.035 1.468-3.145.547-.922 1.282-1.476 2.2-1.293 1.101.184 1.648.926 1.832 1.848.367 1.851.734 3.699.917 5.547.184 3.328.184 6.656.184 9.984-.184 6.84-.918 13.68-3.3 20.149-1.102 1.296-2.016 3.328-2.934 5.363zm-9.172-.371c.734-1.11.734-2.22.183-3.328-.914-1.664-2.382-2.957-4.214-3.696H47l.547 1.11c.183.367.367.738.367 1.293.367 1.48.184 2.218-1.281 2.773-.918.371-1.836.555-2.57.738-4.399.739-8.985.739-13.387.555h-1.281c0 .184.183.184.183.184 4.399 1.48 8.8 2.59 13.387 2.96 1.648.184 3.484.184 5.133-.37.55-.184.918 0 1.285.183 4.953 3.328 10.27 5.73 16.137 7.211 1.101.371 1.101.371 1.652-.738.183-.188.183-.371.367-.555-4.953-3.7-10.453-6.285-16.324-8.32zm-15.586-17.38c-.184 2.22.184 3.145 1.465 3.516 1.101.368 2.386 0 2.754-.925.367-.555.367-1.293.367-2.036-.184-.554-.735-1.109-1.102-1.664 2.2.188 3.485 1.48 4.219 3.516.547-1.852-.367-5.547-3.117-7.395-2.938-1.851-6.973-1.48-9.356 1.11-2.382 2.586-2.566 6.469-.734 9.426 1.836 2.773 5.504 3.699 7.152 2.957-2.015-.739-3.3-2.215-3.484-4.434 0-1.664.55-2.96 1.836-4.07zm-13.387 7.028c-3.3-1.848-4.035-5.363-1.652-7.027-.367 1.48 0 2.406 1.101 2.96.735.368 2.016.184 2.383-.741.367-.555.551-1.293.367-1.848s-.734-.926-1.101-1.664c.918.37 1.652.555 2.387.926.546-1.664.546-1.664 0-2.403-1.653-1.48-3.485-2.035-5.504-1.48-2.2.554-3.485 2.035-4.035 4.25-.184.926-.368 1.851.183 2.773l1.652 2.774c.184.187.184.554.551.554.914.743 2.2.926 3.668.926zM2.984 29.984c0 .926.368 2.036 1.102 2.957 1.098 1.293 2.934 1.293 4.219 0 1.832-1.847 1.648-4.992-.367-6.656-.551-.555-1.286-.738-2.02-.555-1.832.188-3.117 1.852-2.934 4.254zm0 0" fill="#3c3"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="49" xmlns="http://www.w3.org/2000/svg"><path d="M4.524 3.224v10.102h8.5v2.598h-8.5v13.7h-3.9V.626h13.301v2.598zm14.402 26.3V.826h3.7v28.7zm0 0" fill="#d10407" stroke="#d10407" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(2.10753 0 0 2.07742 0 .079)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#666;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#666;stroke-opacity:1;stroke-miterlimit:10" d="M11.241 25.867c-.498 0-1.1-.2-1.5-.6l-8.398-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-6.901 6.9 6.9 6.9c.8.8.8 2.098 0 3-.5.4-.998.6-1.499.6zm25 0c-.5 0-1.099-.2-1.499-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.4c-.4.4-.898.6-1.5.6zm-16.698 4.1c-.2 0-.402 0-.7-.1-1.1-.399-1.7-1.5-1.3-2.599l8.399-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M46.406 0c6.367 4.457 10.078 16.043 10.078 24.781 0 2.317-.351 4.457-.882 6.418-.532-5.172-3.008-9.629-6.895-12.48 2.121 2.851 3.36 6.418 3.36 10.34 0 9.625-7.782 17.468-17.329 17.468-3.89 0-5.836-.71-8.664-2.851 5.836 0 9.547-5.883 14.5-5.883 0 0-.71-2.852-4.422-2.852-3.715 0-1.945 2.852-8.664 2.852S17.41 33.695 17.41 30.484c0-3.207 4.774-5.527 5.836-4.457 1.059-1.07 0-2.851 0-2.851l8.664-5.883h-2.832c-12.906 0-5.48-9.094-2.828-11.59-4.598 0-7.426 4.281-8.664 5.883-.707-.356-5.832-.356-7.246 0-.707.18-1.594-1.066-2.3-2.492-1.06-1.961-1.946-4.637-1.946-6.242-3.711 3.746-3.004 9.27-1.59 11.41l-.176.18C1.676 19.253.262 24.601.262 30.483.262 49.024 15.113 64 33.5 64s33.238-13.547 33.238-32.09V29.06C66.563 11.766 55.602 2.852 46.406 0zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.143 25.867c-.5 0-1.1-.2-1.502-.6l-8.298-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-6.901 6.9 6.9 6.9c.8.8.8 2.098 0 3-.5.4-1.1.6-1.6.6zm25.101 0c-.503 0-1.102-.2-1.502-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.4c-.4.4-.999.6-1.5.6zm-16.7 4.1c-.202 0-.403 0-.7-.1-1.102-.399-1.7-1.5-1.3-2.599l8.398-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1.1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#55486d"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M26.555 0h15.89v16h-15.89zm0 48h15.89v16h-15.89zM.015 48h15.887v16H.016zm53.083 0h15.886v16H53.098zM37.207 29.273V18.727h-5.414v10.546H5.25v16h5.418V34.727h21.125v10.546h5.414V34.727h21.125v10.546h5.418v-16zm0 0" fill="#999"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.143 25.867c-.5 0-1.1-.2-1.502-.6l-8.298-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-6.901 6.9 6.9 6.9c.8.8.8 2.098 0 3-.5.4-1.1.6-1.6.6zm25.101 0c-.503 0-1.102-.2-1.502-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.4c-.4.4-.999.6-1.5.6zm-16.7 4.1c-.202 0-.403 0-.7-.1-1.102-.399-1.7-1.5-1.3-2.599l8.398-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1.1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M4.059 10.39h40.254c2.109 0 3.69-1.613 3.69-3.761 0-2.149-1.581-3.758-3.69-3.758H4.059c-2.11 0-3.692 1.61-3.692 3.758 0 2.152 1.582 3.762 3.692 3.762zm0 19.891h40.254c2.109 0 3.69-1.613 3.69-3.765 0-2.149-1.581-3.762-3.69-3.762H4.059c-2.11 0-3.692 1.613-3.692 3.762 0 2.148 1.582 3.765 3.692 3.765zm19.336 10.57H4.059c-2.11 0-3.692 1.614-3.692 3.762 0 2.149 1.582 3.766 3.692 3.766h19.336c2.109 0 3.69-1.617 3.69-3.766 0-2.148-1.581-3.761-3.69-3.761zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M70.68 9.496c-2.813-1.434-6.504-3.582-7.91-6.629C62.77 1.254 61.54 0 59.957 0c-1.582 0-2.812 1.254-2.812 2.867v38.52c-2.989-1.614-8.614-1.075-12.833 1.433-6.68 3.766-9.492 10.93-6.68 15.766 2.813 4.84 10.723 5.914 17.4 2.152 4.573-2.687 7.738-6.988 7.913-11.289V16.305c9.492 0 15.29 3.941 13.18 13.437-.352 1.793-1.05 3.403-1.754 5.195-.355.54-.355 1.254.176 1.793.527.536 1.402.356 2.11-.359 3.515-3.582 5.796-8.242 5.976-13.437-.18-6.805-6.508-10.75-11.953-13.438zm0 0"/></svg> |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg> |
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> | |
| 1 | 2 |
| 1 | ||
| 1 | <svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg> |
| 1 | ||
| 1 | <svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg> |
| 1 | .tagmark { | |
| 2 | -fx-fill: gray; | |
| 3 | } | |
| 4 | .anytag { | |
| 5 | -fx-fill: crimson; | |
| 6 | } | |
| 7 | .paren { | |
| 8 | -fx-fill: firebrick; | |
| 9 | -fx-font-weight: bold; | |
| 10 | } | |
| 11 | .attribute { | |
| 12 | -fx-fill: darkviolet; | |
| 13 | } | |
| 14 | .avalue { | |
| 15 | -fx-fill: black; | |
| 16 | } | |
| 1 | 17 | |
| 18 | .comment { | |
| 19 | -fx-fill: teal; | |
| 20 | } |
| 1 | # Building | |
| 2 | ||
| 3 | The lexicon files are retrieved from SymSpell in the parent directory: | |
| 4 | ||
| 5 | svn export \ | |
| 6 | https://github.com/wolfgarbe/SymSpell/trunk/SymSpell.FrequencyDictionary/ lexicons | |
| 7 | ||
| 8 | The lexicons and bigrams are both space-separated, but parsing a | |
| 9 | tab-delimited file is easier, so change them to tab-separated files. | |
| 1 | 10 |
| 1 | # Overview | |
| 2 | ||
| 3 | Lexicons in this directory are meant to relate to a particular subject | |
| 4 | (medicine, chemistry, math, sports, and such), extend the main lexicon, | |
| 5 | or not be in common use. | |
| 6 | ||
| 1 | 7 |
| 1 | 1 | |
| 2 | 'aight | |
| 3 | ain't | |
| 4 | amn't | |
| 5 | aren't | |
| 6 | can't | |
| 7 | 'cause | |
| 8 | couldn't | |
| 9 | couldn't've | |
| 10 | could've | |
| 11 | daren't | |
| 12 | daresn't | |
| 13 | dasn't | |
| 14 | didn't | |
| 15 | doesn't | |
| 16 | don't | |
| 17 | dunno | |
| 18 | d'ye | |
| 19 | e'er | |
| 20 | everybody's | |
| 21 | everyone's | |
| 22 | g'day | |
| 23 | gimme | |
| 24 | giv'n | |
| 25 | gonna | |
| 26 | gon't | |
| 27 | gotta | |
| 28 | hadn't | |
| 29 | had've | |
| 30 | hasn't | |
| 31 | haven't | |
| 32 | he'd | |
| 33 | he'll | |
| 34 | he's | |
| 35 | he've | |
| 36 | how'd | |
| 37 | howdy | |
| 38 | how'll | |
| 39 | how're | |
| 40 | how's | |
| 41 | how've | |
| 42 | i'd | |
| 43 | i'dn't've | |
| 44 | i'd've | |
| 45 | i'll | |
| 46 | i'm | |
| 47 | i'm'a | |
| 48 | imma | |
| 49 | innit | |
| 50 | isn't | |
| 51 | it'd | |
| 52 | it'll | |
| 53 | it's | |
| 54 | i've | |
| 55 | let's | |
| 56 | ma'am | |
| 57 | mayn't | |
| 58 | may've | |
| 59 | methinks | |
| 60 | mightn't | |
| 61 | might've | |
| 62 | mustn't | |
| 63 | mustn't've | |
| 64 | must've | |
| 65 | needn't | |
| 66 | ne'er | |
| 67 | o'clock | |
| 68 | o'er | |
| 69 | ol' | |
| 70 | oughtn't | |
| 71 | shalln't | |
| 72 | shan't | |
| 73 | she'd | |
| 74 | she'll | |
| 75 | she's | |
| 76 | shouldn't | |
| 77 | shouldn't've | |
| 78 | should've | |
| 79 | somebody's | |
| 80 | someone's | |
| 81 | something's | |
| 82 | so're | |
| 83 | that'd | |
| 84 | that'll | |
| 85 | that're | |
| 86 | that's | |
| 87 | there'd | |
| 88 | there'll | |
| 89 | there're | |
| 90 | there's | |
| 91 | these'd | |
| 92 | these'll | |
| 93 | these're | |
| 94 | these've | |
| 95 | they'd | |
| 96 | they'll | |
| 97 | they're | |
| 98 | they've | |
| 99 | this's | |
| 100 | those're | |
| 101 | those've | |
| 102 | 'tis | |
| 103 | to've | |
| 104 | 'twas | |
| 105 | 'twouldn't | |
| 106 | wanna | |
| 107 | wasn't | |
| 108 | we'd | |
| 109 | we'd've | |
| 110 | we'll | |
| 111 | we're | |
| 112 | weren't | |
| 113 | we've | |
| 114 | what'd | |
| 115 | what'll | |
| 116 | what're | |
| 117 | what's | |
| 118 | what've | |
| 119 | when'd | |
| 120 | when'll | |
| 121 | when's | |
| 122 | where'd | |
| 123 | where'll | |
| 124 | where're | |
| 125 | where's | |
| 126 | where've | |
| 127 | which'd | |
| 128 | which'll | |
| 129 | which're | |
| 130 | which's | |
| 131 | which've | |
| 132 | who'd | |
| 133 | who'd've | |
| 134 | who'll | |
| 135 | who're | |
| 136 | who's | |
| 137 | who've | |
| 138 | why'd | |
| 139 | why'll | |
| 140 | why're | |
| 141 | why's | |
| 142 | willn't | |
| 143 | won't | |
| 144 | wouldn't | |
| 145 | wouldn't've | |
| 146 | would've | |
| 147 | y'all | |
| 148 | y'all'd've | |
| 149 | y'all're | |
| 150 | you'd | |
| 151 | you'dn't've | |
| 152 | you'll | |
| 153 | you're | |
| 154 | you've | |
| 155 |
| 1 | analytics 130337 | |
| 2 | hotspot 130022 | |
| 3 | instantiation 130000 | |
| 4 | onboarding 129953 | |
| 5 | biometric 129795 | |
| 6 | anamorphic 129777 | |
| 7 | benchmarking 129772 | |
| 8 | cybersecurity 129769 | |
| 9 | barcode 129757 | |
| 10 | splitter 129755 | |
| 11 | keychain 129719 | |
| 12 | crowdfunding 129696 | |
| 13 | polymorphism 129688 | |
| 14 | automata 129666 | |
| 15 | shockwave 129658 | |
| 16 | profiler 129648 | |
| 17 | kerning 129646 | |
| 18 | nanometer 129630 | |
| 19 | meridiem 129624 | |
| 20 | influencer 129618 | |
| 21 | passcode 129617 | |
| 22 | sexting 129607 | |
| 23 | cryptology 129606 | |
| 24 | biometrics 129606 | |
| 25 | bitcoin 129599 | |
| 26 | specular 129598 | |
| 27 | accelerometer 129588 | |
| 28 | googolplex 129583 | |
| 29 | grayscale 129576 | |
| 30 | ascender 129571 | |
| 31 | pixelated 129569 | |
| 32 | rockstar 129565 | |
| 33 | ragdoll 129564 | |
| 34 | cyberattack 129564 | |
| 35 | cryptanalysis 129562 | |
| 36 | ransomware 129553 | |
| 37 | crowdsourcing 129552 | |
| 38 | hackathon 129551 | |
| 39 | audiobook 129544 | |
| 40 | degauss 129543 | |
| 41 | attenuator 129540 | |
| 42 | jetpack 129538 | |
| 43 | packrat 129536 | |
| 44 | backlight 129535 | |
| 45 | bootable 129530 | |
| 46 | octothorpe 129529 | |
| 47 | newsfeed 129525 | |
| 48 | extranet 129523 | |
| 49 | failover 129516 | |
| 50 | cyberbullying 129516 | |
| 51 | neumann 129515 | |
| 52 | capacitive 129514 | |
| 53 | backlit 129511 | |
| 54 | millimicron 129507 | |
| 55 | inductor 129505 | |
| 56 | workgroup 129502 | |
| 57 | journaling 129500 | |
| 58 | middleware 129499 | |
| 59 | spooler 129497 | |
| 60 | clamshell 129495 | |
| 61 | wireframe 129494 | |
| 62 | modularity 129493 | |
| 63 | strikethrough 129489 | |
| 64 | petabyte 129487 | |
| 65 | jughead 129482 | |
| 66 | acyclic 129482 | |
| 67 | gearhead 129478 | |
| 68 | stateful 129473 | |
| 69 | submenu 129467 | |
| 70 | pseudorandom 129463 | |
| 71 | earbuds 129461 | |
| 72 | narrowband 129460 | |
| 73 | recordable 129457 | |
| 74 | unallocated 129455 | |
| 75 | mappable 129455 | |
| 76 | chipset 129454 | |
| 77 | multicast 129447 | |
| 78 | loopback 129444 | |
| 79 | pixelate 129441 | |
| 80 | cryptographic 129441 | |
| 81 | pixelation 129438 | |
| 82 | autocorrect 129438 | |
| 83 | teraflop 129437 | |
| 84 | digitizer 129436 | |
| 85 | tunnelling 129434 | |
| 86 | deduplication 129434 | |
| 87 | subwoofer 129433 | |
| 88 | touchpad 129429 | |
| 89 | namespace 129428 | |
| 90 | microcontroller 129428 | |
| 91 | geolocation 129428 | |
| 92 | telepresence 129427 | |
| 93 | driverless 129426 | |
| 94 | photolithography 129425 | |
| 95 | multiphase 129425 | |
| 96 | verifier 129424 | |
| 97 | robocall 129424 | |
| 98 | autofocus 129424 | |
| 99 | kilobit 129422 | |
| 100 | hacktivist 129419 | |
| 101 | geocache 129415 | |
| 102 | rasterize 129412 | |
| 103 | plaintext 129411 | |
| 104 | pipelining 129411 | |
| 105 | technobabble 129409 | |
| 106 | defragment 129409 | |
| 107 | connectionless 129409 | |
| 108 | homomorphic 129407 | |
| 109 | demodulator 129406 | |
| 110 | datagram 129406 | |
| 111 | activex 129406 | |
| 112 | normalisation 129404 | |
| 113 | blackhole 129402 | |
| 114 | cyberstalker 129401 | |
| 115 | multifunction 129400 | |
| 116 | undirected 129397 | |
| 117 | ciphertext 129397 | |
| 118 | superspeed 129396 | |
| 119 | spacebar 129395 | |
| 120 | cyberwar 129395 | |
| 121 | borderless 129395 | |
| 122 | transcode 129393 | |
| 123 | cyberbully 129393 | |
| 124 | multimeter 129392 | |
| 125 | dropship 129391 | |
| 126 | yottabyte 129390 | |
| 127 | infector 129390 | |
| 128 | superclass 129389 | |
| 129 | tooltip 129388 | |
| 130 | dereference 129387 | |
| 131 | combinator 129386 | |
| 132 | milliwatt 129385 | |
| 133 | cyberstalking 129384 | |
| 134 | subfolder 129383 | |
| 135 | wideband 129382 | |
| 136 | noncontiguous 129382 | |
| 137 | ferroelectric 129382 | |
| 138 | cybersquatting 129378 | |
| 139 | autofill 129378 | |
| 140 | trackpad 129376 | |
| 141 | associatively 129376 | |
| 142 | luggable 129374 | |
| 143 | seamonkey 129373 | |
| 144 | defragmentation 129373 | |
| 145 | starcraft 129371 | |
| 146 | obliquing 129371 | |
| 147 | leadless 129371 | |
| 148 | greeking 129371 | |
| 149 | upgradeable 129370 | |
| 150 | radiosity 129370 | |
| 151 | transcoding 129369 | |
| 152 | quintillionth 129369 | |
| 153 | bitmapped 129369 | |
| 154 | subdirectory 129368 | |
| 155 | degausser 129368 | |
| 156 | curtiss 129368 | |
| 157 | scunthorpe 129367 | |
| 158 | undelete 129365 | |
| 159 | gigaflops 129365 | |
| 160 | darknet 129365 | |
| 161 | zettabyte 129364 | |
| 162 | topologies 129363 | |
| 163 | spidering 129363 | |
| 164 | photorealism 129363 | |
| 165 | multithreading 129363 | |
| 166 | deallocate 129363 | |
| 167 | mersenne 129362 | |
| 168 | machinima 129361 | |
| 169 | satisfiable 129360 | |
| 170 | laserjet 129360 | |
| 171 | multicore 129359 | |
| 172 | microblog 129359 | |
| 173 | megaflops 129359 | |
| 174 | homeomorphic 129359 | |
| 175 | microblogging 129358 | |
| 176 | kilobaud 129358 | |
| 177 | cyberwarfare 129358 | |
| 178 | microarchitecture 129357 | |
| 179 | autosave 129357 | |
| 180 | wirelessly 129356 | |
| 181 | sneakernet 129355 | |
| 182 | textbox 129354 | |
| 183 | obfuscator 129354 | |
| 184 | microkernel 129353 | |
| 185 | substring 129352 | |
| 186 | macroinstruction 129352 | |
| 187 | endianness 129352 | |
| 188 | indexable 129351 | |
| 189 | backtick 129351 | |
| 190 | unshielded 129350 | |
| 191 | cleartext 129350 | |
| 192 | autocomplete 129349 | |
| 193 | abandonware 129349 | |
| 194 | hacktivism 129348 | |
| 195 | antikythera 129348 | |
| 196 | stereolithography 129347 | |
| 197 | photorealistic 129347 | |
| 198 | macrovision 129347 | |
| 199 | greasemonkey 129347 | |
| 200 | geotagging 129347 | |
| 201 | disassembler 129346 | |
| 202 | spacewar 129345 | |
| 203 | pluggable 129345 | |
| 204 | kilobits 129345 | |
| 205 | webcomic 129344 | |
| 206 | unfollow 129344 | |
| 207 | photosensor 129344 | |
| 208 | petaflop 129344 | |
| 209 | garageband 129344 | |
| 210 | truetype 129343 | |
| 211 | subnetwork 129342 | |
| 212 | backpropagation 129342 | |
| 213 | supercomputing 129340 | |
| 214 | smartwatch 129340 | |
| 215 | unbundled 129339 | |
| 216 | smilies 129339 | |
| 217 | milliamp 129339 | |
| 218 | bytecode 129339 | |
| 219 | trackpoint 129337 | |
| 220 | slipstreaming 129337 | |
| 221 | monospace 129337 | |
| 222 | memoization 129337 | |
| 223 | scaleable 129336 | |
| 224 | respawn 129335 | |
| 225 | multicasting 129335 | |
| 226 | geocacher 129335 | |
| 227 | workgroups 129334 | |
| 228 | ferrofluid 129334 | |
| 229 | smartdrive 129333 | |
| 230 | subsampling 129332 | |
| 231 | rasterization 129332 | |
| 232 | guiltware 129332 | |
| 233 | defragger 129332 | |
| 234 | satisfiability 129331 | |
| 235 | activision 129331 | |
| 236 | subdirectories 129330 | |
| 237 | segfault 129330 | |
| 238 | flamebait 129330 | |
| 239 | framebuffer 129329 | |
| 240 | defragging 129329 | |
| 241 | decompiler 129329 | |
| 242 | unshift 129328 | |
| 243 | memristor 129328 | |
| 244 | zebibyte 129327 | |
| 245 | semiprime 129327 | |
| 246 | rotoscoping 129327 | |
| 247 | hypertransport 129327 | |
| 248 | smartmedia 129326 | |
| 249 | grayware 129326 | |
| 250 | defragmenting 129326 | |
| 251 | defragmenter 129326 | |
| 252 | repagination 129325 | |
| 253 | subnetting 129324 | |
| 254 | skeuomorphism 129324 | |
| 255 | screencast 129324 | |
| 256 | stylesheet 129323 | |
| 257 | superintelligence 129322 | |
| 258 | multitenancy 129322 | |
| 259 | datastore 129322 | |
| 260 | autoplay 129322 | |
| 261 | repaginate 129321 | |
| 262 | macbook 129321 | |
| 263 | geotagged 129321 | |
| 264 | baudrate 129321 | |
| 265 | transmeta 129320 | |
| 266 | screwless 129320 | |
| 267 | nameserver 129320 | |
| 268 | interexchange 129320 | |
| 269 | geocoding 129319 | |
| 270 | downloader 129319 | |
| 271 | autodiscovery 129319 | |
| 272 | extortion 65752 | |
| 273 | emoji 65684 | |
| 274 | googol 65618 | |
| 275 | backside 65388 | |
| 276 | fibre 65387 | |
| 277 | metre 65333 | |
| 278 | royale 65173 | |
| 279 | radix 65093 | |
| 280 | hotdog 65091 | |
| 281 | lecher 65062 | |
| 282 | uptime 65009 | |
| 283 | unbound 64979 | |
| 284 | eniac 64975 | |
| 285 | synaptic 64966 | |
| 286 | voxel 64926 | |
| 287 | selfie 64917 | |
| 288 | uplink 64887 | |
| 289 | fanboy 64857 | |
| 290 | defrag 64849 | |
| 291 | nondisclosure 64839 | |
| 292 | qubit 64828 | |
| 293 | yippie 64821 | |
| 294 | gearhead 64819 | |
| 295 | subnet 64818 | |
| 296 | endian 64798 | |
| 297 | bezier 64797 | |
| 298 | reallocation 64796 | |
| 299 | telephonic 64789 | |
| 300 | mosfet 64777 | |
| 301 | mutex 64775 | |
| 302 | inkjet 64772 | |
| 303 | gobbing 64768 | |
| 304 | shader 64766 | |
| 305 | ultralight 64755 | |
| 306 | hackers 64746 | |
| 307 | pacman 64742 | |
| 308 | unlink 64741 | |
| 309 | undock 64740 | |
| 310 | understroke 64738 | |
| 311 | beginners 64736 | |
| 312 | photoscope 64731 | |
| 313 | gantt 64725 | |
| 314 | programmers 64722 | |
| 315 | todays 64720 | |
| 316 | moores 64716 | |
| 317 | fullscreen 64715 | |
| 318 | moveless 64708 | |
| 319 | reformatted 64704 | |
| 320 | deallocate 64704 | |
| 321 | laserdisc 64702 | |
| 322 | macos 64700 | |
| 323 | nonactive 64697 | |
| 324 | nonadjacent 64696 | |
| 325 | hotfix 64695 | |
| 326 | keylogger 64694 | |
| 327 | geotag 64691 | |
| 328 | oreilly 64681 | |
| 329 | exabit 64678 | |
| 330 | jailbroken 64677 | |
| 331 | fuzzer 64676 | |
| 332 | noninteractive 64673 | |
| 333 | multifactor 64672 | |
| 334 | letterspacing 64671 | |
| 335 | preinstall 64669 | |
| 336 | multiboot 64666 | |
| 337 | runescape 64665 | |
| 338 | micropayment 64664 | |
| 339 | numpad 64663 | |
| 340 | preinstalled 64661 | |
| 341 | jailbreaking 64660 | |
| 342 | attend 2158 | |
| 343 | withstand 1809 | |
| 344 | transpire 1116 | |
| 345 | reading 1110 | |
| 346 | texture 1065 | |
| 347 | capitalize 832 | |
| 348 | calling 779 | |
| 349 | unfold 767 | |
| 350 | starboard 679 | |
| 351 | commode 625 | |
| 352 | doing 594 | |
| 353 | textbook 499 | |
| 354 | unease 378 | |
| 355 | unpack 358 | |
| 356 | keycard 231 | |
| 357 | mainspring 207 | |
| 358 | grr 180 | |
| 359 | geocaching 167 | |
| 360 | microbus 160 | |
| 361 | mp3 147 | |
| 362 | svg 139 | |
| 363 | shifted 128 | |
| 364 | texted 127 | |
| 365 | towheaded 118 | |
| 366 | mineshaft 115 | |
| 367 | nonparty 95 | |
| 368 | crossbite 80 | |
| 369 | resignedness 69 | |
| 370 | msrp 61 | |
| 371 | inbreak 53 | |
| 372 | nanocomposite 44 | |
| 373 | md5 44 | |
| 374 | neomorphic 41 | |
| 375 | superstrain 28 | |
| 376 | lifers 27 | |
| 377 | multination 26 | |
| 378 | smartwatch 22 | |
| 379 | antilibration 22 | |
| 380 | zapf 20 | |
| 381 | mp4 20 | |
| 1 | 382 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite; | |
| 3 | ||
| 4 | import javafx.embed.swing.JFXPanel; | |
| 5 | import org.junit.jupiter.api.extension.BeforeAllCallback; | |
| 6 | import org.junit.jupiter.api.extension.ExtensionContext; | |
| 7 | import org.testfx.osgi.service.TestFx; | |
| 8 | ||
| 9 | import java.util.concurrent.Semaphore; | |
| 10 | ||
| 11 | import static javafx.application.Platform.runLater; | |
| 12 | import static javax.swing.SwingUtilities.invokeLater; | |
| 13 | ||
| 14 | /** | |
| 15 | * Blocks all unit tests until JavaFX is ready. | |
| 16 | */ | |
| 17 | public class AwaitFxExtension implements BeforeAllCallback { | |
| 18 | /** | |
| 19 | * Prevent {@link RuntimeException} for internal graphics not initialized yet. | |
| 20 | * | |
| 21 | * @param context Provided by the {@link TestFx} framework. | |
| 22 | * @throws InterruptedException Could not acquire semaphore. | |
| 23 | */ | |
| 24 | @Override | |
| 25 | public void beforeAll( final ExtensionContext context ) | |
| 26 | throws InterruptedException { | |
| 27 | final var semaphore = new Semaphore( 0 ); | |
| 28 | ||
| 29 | invokeLater( () -> { | |
| 30 | // Prepare JavaFX toolkit and environment. | |
| 31 | new JFXPanel(); | |
| 32 | runLater( semaphore::release ); | |
| 33 | } ); | |
| 34 | ||
| 35 | semaphore.acquire(); | |
| 36 | } | |
| 37 | } | |
| 1 | 38 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.definition; | |
| 3 | ||
| 4 | import com.keenwrite.editors.definition.DefinitionEditor; | |
| 5 | import com.keenwrite.editors.definition.yaml.YamlTreeTransformer; | |
| 6 | import com.keenwrite.editors.markdown.MarkdownEditor; | |
| 7 | import com.keenwrite.preferences.Workspace; | |
| 8 | import com.keenwrite.preview.HtmlPreview; | |
| 9 | import com.panemu.tiwulfx.control.dock.DetachableTabPane; | |
| 10 | import javafx.application.Application; | |
| 11 | import javafx.beans.property.SimpleObjectProperty; | |
| 12 | import javafx.event.Event; | |
| 13 | import javafx.event.EventHandler; | |
| 14 | import javafx.scene.Node; | |
| 15 | import javafx.scene.Scene; | |
| 16 | import javafx.scene.control.ColorPicker; | |
| 17 | import javafx.scene.control.SplitPane; | |
| 18 | import javafx.scene.control.Tooltip; | |
| 19 | import javafx.scene.control.TreeItem; | |
| 20 | import javafx.stage.Stage; | |
| 21 | import org.testfx.framework.junit5.Start; | |
| 22 | ||
| 23 | import static com.keenwrite.util.FontLoader.initFonts; | |
| 24 | ||
| 25 | //@ExtendWith(ApplicationExtension.class) | |
| 26 | public class TreeViewTest extends Application { | |
| 27 | private final SimpleObjectProperty<Node> mTextEditor = | |
| 28 | new SimpleObjectProperty<>(); | |
| 29 | ||
| 30 | private final EventHandler<TreeItem.TreeModificationEvent<Event>> mTreeHandler = | |
| 31 | event -> refresh( mTextEditor.get() ); | |
| 32 | ||
| 33 | private void refresh( final Node node ) { | |
| 34 | throw new RuntimeException( "Derp: " + node ); | |
| 35 | } | |
| 36 | ||
| 37 | public static void main( final String[] args ) { | |
| 38 | initFonts(); | |
| 39 | launch( args ); | |
| 40 | } | |
| 41 | ||
| 42 | public void start( final Stage stage ) { | |
| 43 | onStart( stage ); | |
| 44 | } | |
| 45 | ||
| 46 | @Start | |
| 47 | private void onStart( final Stage stage ) { | |
| 48 | final var workspace = new Workspace(); | |
| 49 | final var mainPane = new SplitPane(); | |
| 50 | ||
| 51 | final var transformer = new YamlTreeTransformer(); | |
| 52 | final var editor = new DefinitionEditor( transformer ); | |
| 53 | ||
| 54 | final var tabPane1 = new DetachableTabPane(); | |
| 55 | tabPane1.addTab( "Editor", editor ); | |
| 56 | ||
| 57 | final var tabPane2 = new DetachableTabPane(); | |
| 58 | final var tab21 = tabPane2.addTab( "Picker", new ColorPicker() ); | |
| 59 | final var tab22 = tabPane2.addTab( "Editor", | |
| 60 | new MarkdownEditor( workspace ) ); | |
| 61 | tab21.setTooltip( new Tooltip( "Colour Picker" ) ); | |
| 62 | tab22.setTooltip( new Tooltip( "Text Editor" ) ); | |
| 63 | ||
| 64 | final var tabPane3 = new DetachableTabPane(); | |
| 65 | tabPane3.addTab( "Preview", new HtmlPreview( workspace ) ); | |
| 66 | ||
| 67 | editor.addTreeChangeHandler( mTreeHandler ); | |
| 68 | ||
| 69 | mainPane.getItems().addAll( tabPane1, tabPane2, tabPane3 ); | |
| 70 | ||
| 71 | final var scene = new Scene( mainPane ); | |
| 72 | stage.setScene( scene ); | |
| 73 | ||
| 74 | stage.show(); | |
| 75 | } | |
| 76 | } | |
| 1 | 77 |
| 1 | package com.keenwrite.editors.markdown; | |
| 2 | ||
| 3 | import com.keenwrite.AwaitFxExtension; | |
| 4 | import com.keenwrite.preferences.Workspace; | |
| 5 | import org.junit.jupiter.api.Test; | |
| 6 | import org.junit.jupiter.api.extension.ExtendWith; | |
| 7 | import org.testfx.framework.junit5.ApplicationExtension; | |
| 8 | ||
| 9 | import java.util.regex.Pattern; | |
| 10 | ||
| 11 | import static java.util.regex.Pattern.compile; | |
| 12 | import static javafx.application.Platform.runLater; | |
| 13 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 14 | import static org.junit.jupiter.api.Assertions.assertTrue; | |
| 15 | ||
| 16 | @ExtendWith( {ApplicationExtension.class, AwaitFxExtension.class} ) | |
| 17 | public class MarkdownEditorTest { | |
| 18 | private static final String[] WORDS = new String[]{ | |
| 19 | "Italicize", | |
| 20 | "English's", | |
| 21 | "foreign", | |
| 22 | "words", | |
| 23 | "based", | |
| 24 | "on", | |
| 25 | "popularity,", | |
| 26 | "like", | |
| 27 | "_bête_", | |
| 28 | "_noire_", | |
| 29 | "and", | |
| 30 | "_Weltanschauung_", | |
| 31 | "but", | |
| 32 | "not", | |
| 33 | "résumé.", | |
| 34 | "Don't", | |
| 35 | "omit", | |
| 36 | "accented", | |
| 37 | "characters!", | |
| 38 | "Cœlacanthe", | |
| 39 | "L'Haÿ-les-Roses", | |
| 40 | "Mühlfeldstraße", | |
| 41 | "Da̱nx̱a̱laga̱litła̱n", | |
| 42 | }; | |
| 43 | ||
| 44 | private static final String TEXT = String.join( " ", WORDS ); | |
| 45 | ||
| 46 | private static final Pattern REGEX = compile( | |
| 47 | "[^\\p{Mn}\\p{Me}\\p{L}\\p{N}'-]+" ); | |
| 48 | ||
| 49 | /** | |
| 50 | * Test that the {@link MarkdownEditor} can retrieve a word at the caret | |
| 51 | * position, regardless of whether the caret is at the beginning, middle, or | |
| 52 | * end of the word. | |
| 53 | */ | |
| 54 | @Test | |
| 55 | public void test_CaretWord_GetISO88591Word_WordSelected() { | |
| 56 | runLater( () -> { | |
| 57 | final var editor = createMarkdownEditor(); | |
| 58 | ||
| 59 | for( int i = 0; i < WORDS.length; i++ ) { | |
| 60 | final var word = WORDS[ i ]; | |
| 61 | final var len = word.length(); | |
| 62 | final var expected = REGEX.matcher( word ).replaceAll( "" ); | |
| 63 | ||
| 64 | for( int j = 0; j < len; j++ ) { | |
| 65 | editor.moveTo( offset( i ) + j ); | |
| 66 | final var actual = editor.getCaretWordText(); | |
| 67 | assertEquals( expected, actual ); | |
| 68 | } | |
| 69 | } | |
| 70 | } ); | |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Test that the {@link MarkdownEditor} can make a word bold. | |
| 75 | */ | |
| 76 | @Test | |
| 77 | public void test_CaretWord_SetWordBold_WordIsBold() { | |
| 78 | final var index = 20; | |
| 79 | final var editor = createMarkdownEditor(); | |
| 80 | ||
| 81 | editor.moveTo( offset( index ) ); | |
| 82 | editor.bold(); | |
| 83 | assertTrue( editor.getText().contains( "**" + WORDS[ index ] + "**" ) ); | |
| 84 | } | |
| 85 | ||
| 86 | /** | |
| 87 | * Returns the document offset for a string at the given index. | |
| 88 | */ | |
| 89 | private static int offset( final int index ) { | |
| 90 | assert 0 <= index && index < WORDS.length; | |
| 91 | int offset = 0; | |
| 92 | ||
| 93 | for( int i = 0; i < index; i++ ) { | |
| 94 | offset += WORDS[ i ].length(); | |
| 95 | } | |
| 96 | ||
| 97 | // Add the index to compensate for one space between words. | |
| 98 | return offset + index; | |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Returns an instance of {@link MarkdownEditor} pre-populated with | |
| 103 | * {@link #TEXT}. | |
| 104 | * | |
| 105 | * @return A new {@link MarkdownEditor} instance, ready for unit tests. | |
| 106 | */ | |
| 107 | private MarkdownEditor createMarkdownEditor() { | |
| 108 | final var workspace = new Workspace(); | |
| 109 | final var editor = new MarkdownEditor( workspace ); | |
| 110 | editor.setText( TEXT ); | |
| 111 | return editor; | |
| 112 | } | |
| 113 | } | |
| 1 | 114 |
| 1 | /* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.flexmark; | |
| 3 | ||
| 4 | import com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension; | |
| 5 | import com.vladsch.flexmark.ext.definition.DefinitionExtension; | |
| 6 | import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension; | |
| 7 | import com.vladsch.flexmark.ext.superscript.SuperscriptExtension; | |
| 8 | import com.vladsch.flexmark.ext.tables.TablesExtension; | |
| 9 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 10 | import com.vladsch.flexmark.parser.Parser; | |
| 11 | import com.vladsch.flexmark.util.data.MutableDataSet; | |
| 12 | import com.vladsch.flexmark.util.misc.Extension; | |
| 13 | import org.junit.jupiter.api.Test; | |
| 14 | ||
| 15 | import java.util.ArrayList; | |
| 16 | import java.util.List; | |
| 17 | ||
| 18 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 19 | ||
| 20 | /** | |
| 21 | * Test that basic styles for conversion exports as expected. | |
| 22 | */ | |
| 23 | public class ParserTest { | |
| 24 | ||
| 25 | @Test | |
| 26 | void test_Conversion_InlineStyles_ExportedAsHtml() { | |
| 27 | final var md = "*emphasis* _emphasis_ **strong**"; | |
| 28 | ||
| 29 | final var extensions = createExtensions(); | |
| 30 | final var options = new MutableDataSet(); | |
| 31 | final var parser = Parser | |
| 32 | .builder( options ) | |
| 33 | .extensions( extensions ) | |
| 34 | .build(); | |
| 35 | final var renderer = HtmlRenderer | |
| 36 | .builder( options ) | |
| 37 | .extensions( extensions ) | |
| 38 | .build(); | |
| 39 | ||
| 40 | final var document = parser.parse( md ); | |
| 41 | final var actual = renderer.render( document ); | |
| 42 | final var expected = | |
| 43 | "<p><em>emphasis</em> <em>emphasis</em> <strong>strong</strong></p>\n"; | |
| 44 | ||
| 45 | assertEquals( expected, actual ); | |
| 46 | } | |
| 47 | ||
| 48 | private List<Extension> createExtensions() { | |
| 49 | final var extensions = new ArrayList<Extension>(); | |
| 50 | ||
| 51 | extensions.add( DefinitionExtension.create() ); | |
| 52 | extensions.add( StrikethroughSubscriptExtension.create() ); | |
| 53 | extensions.add( SuperscriptExtension.create() ); | |
| 54 | extensions.add( TablesExtension.create() ); | |
| 55 | extensions.add( FencedDivExtension.create() ); | |
| 56 | ||
| 57 | return extensions; | |
| 58 | } | |
| 59 | } | |
| 1 | 60 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | import org.junit.jupiter.api.Test; | |
| 5 | import org.junit.jupiter.api.Timeout; | |
| 6 | ||
| 7 | import java.io.File; | |
| 8 | import java.io.IOException; | |
| 9 | import java.util.concurrent.Semaphore; | |
| 10 | import java.util.function.Consumer; | |
| 11 | ||
| 12 | import static java.io.File.createTempFile; | |
| 13 | import static java.nio.file.Files.write; | |
| 14 | import static java.nio.file.StandardOpenOption.APPEND; | |
| 15 | import static java.nio.file.StandardOpenOption.CREATE; | |
| 16 | import static java.util.concurrent.TimeUnit.SECONDS; | |
| 17 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 18 | ||
| 19 | /** | |
| 20 | * Responsible for testing that the {@link FileWatchService} fires the | |
| 21 | * expected {@link FileEvent} when the system raises state changes. | |
| 22 | */ | |
| 23 | class FileWatchServiceTest { | |
| 24 | /** | |
| 25 | * Test that modifying a file produces a {@link FileEvent}. | |
| 26 | * | |
| 27 | * @throws IOException Could not create watcher service. | |
| 28 | * @throws InterruptedException Could not join on watcher service thread. | |
| 29 | */ | |
| 30 | @Test | |
| 31 | @Timeout( value = 5, unit = SECONDS ) | |
| 32 | void test_SingleFile_Write_Notified() throws | |
| 33 | IOException, InterruptedException { | |
| 34 | final var text = "arbitrary text to write"; | |
| 35 | final var file = createTemporaryFile(); | |
| 36 | final var service = new FileWatchService( file ); | |
| 37 | final var thread = new Thread( service ); | |
| 38 | final var semaphor = new Semaphore( 0 ); | |
| 39 | final var listener = createListener( ( f ) -> { | |
| 40 | semaphor.release(); | |
| 41 | assertEquals( file, f ); | |
| 42 | } ); | |
| 43 | ||
| 44 | thread.start(); | |
| 45 | service.addListener( listener ); | |
| 46 | write( file.toPath(), text.getBytes(), CREATE, APPEND ); | |
| 47 | semaphor.acquire(); | |
| 48 | service.stop(); | |
| 49 | thread.join(); | |
| 50 | } | |
| 51 | ||
| 52 | private FileModifiedListener createListener( final Consumer<File> action ) { | |
| 53 | return fileEvent -> action.accept( fileEvent.getFile() ); | |
| 54 | } | |
| 55 | ||
| 56 | private File createTemporaryFile() throws IOException { | |
| 57 | final var prefix = getClass().getPackageName(); | |
| 58 | final var file = createTempFile( prefix, null, null ); | |
| 59 | file.deleteOnExit(); | |
| 60 | return file; | |
| 61 | } | |
| 62 | } | |
| 1 | 63 |
| 1 | package com.keenwrite.io; | |
| 2 | ||
| 3 | import org.junit.jupiter.api.Test; | |
| 4 | ||
| 5 | import java.io.File; | |
| 6 | ||
| 7 | import static com.keenwrite.io.MediaTypeExtension.valueFrom; | |
| 8 | import static org.apache.commons.io.FilenameUtils.getExtension; | |
| 9 | import static org.junit.jupiter.api.Assertions.*; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for testing that {@link MediaTypeSniffer} can return the | |
| 13 | * correct IANA-defined {@link MediaType} for known file types. | |
| 14 | */ | |
| 15 | class MediaTypeSnifferTest { | |
| 16 | ||
| 17 | @Test | |
| 18 | void test_Read_KnownFileTypes_MediaTypeReturned() | |
| 19 | throws Exception { | |
| 20 | final var clazz = getClass(); | |
| 21 | final var pkgName = clazz.getPackageName(); | |
| 22 | final var dir = pkgName.replace( '.', '/' ); | |
| 23 | ||
| 24 | final var urls = clazz.getClassLoader().getResources( dir + "/images" ); | |
| 25 | assertTrue( urls.hasMoreElements() ); | |
| 26 | ||
| 27 | while( urls.hasMoreElements() ) { | |
| 28 | final var url = urls.nextElement(); | |
| 29 | final var path = new File( url.toURI().getPath() ); | |
| 30 | final var files = path.listFiles(); | |
| 31 | assertNotNull( files ); | |
| 32 | ||
| 33 | for( final var image : files ) { | |
| 34 | final var media = MediaTypeSniffer.getMediaType( image ); | |
| 35 | final var actualExtension = valueFrom( media ).getExtension(); | |
| 36 | final var expectedExtension = getExtension( image.toString() ); | |
| 37 | assertEquals( expectedExtension, actualExtension ); | |
| 38 | } | |
| 39 | } | |
| 40 | } | |
| 41 | } | |
| 1 | 42 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.io; | |
| 3 | ||
| 4 | import org.junit.jupiter.api.Test; | |
| 5 | ||
| 6 | import java.net.URI; | |
| 7 | import java.util.Map; | |
| 8 | ||
| 9 | import static com.keenwrite.io.HttpFacade.httpGet; | |
| 10 | import static com.keenwrite.io.MediaType.*; | |
| 11 | import static org.junit.jupiter.api.Assertions.*; | |
| 12 | ||
| 13 | /** | |
| 14 | * Test that {@link MediaType} instances can be queried and return reliable | |
| 15 | * results. | |
| 16 | */ | |
| 17 | public class MediaTypeTest { | |
| 18 | /** | |
| 19 | * Test that {@link MediaType#equals(String, String)} is case insensitive. | |
| 20 | */ | |
| 21 | @Test | |
| 22 | public void test_Equality_IgnoreCase_Success() { | |
| 23 | final var mediaType = TEXT_PLAIN; | |
| 24 | assertTrue( mediaType.equals( "TeXt", "Plain" ) ); | |
| 25 | assertEquals( "text/plain", mediaType.toString() ); | |
| 26 | } | |
| 27 | ||
| 28 | /** | |
| 29 | * Test that {@link MediaType#fromFilename(String)} can lookup by file name. | |
| 30 | */ | |
| 31 | @Test | |
| 32 | public void test_FilenameExtensions_Supported_Success() { | |
| 33 | final var map = Map.of( | |
| 34 | "jpeg", IMAGE_JPEG, | |
| 35 | "png", IMAGE_PNG, | |
| 36 | "svg", IMAGE_SVG_XML, | |
| 37 | "md", TEXT_MARKDOWN, | |
| 38 | "Rmd", TEXT_R_MARKDOWN, | |
| 39 | "txt", TEXT_PLAIN, | |
| 40 | "yml", TEXT_YAML | |
| 41 | ); | |
| 42 | ||
| 43 | map.forEach( ( k, v ) -> assertEquals( v, fromFilename( "f." + k ) ) ); | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Test that remote fetches will pull and identify the type of resource | |
| 48 | * based on the HTTP Content-Type header (or shallow decoding). | |
| 49 | */ | |
| 50 | @Test | |
| 51 | public void test_HttpRequest_Supported_Success() { | |
| 52 | //@formatter:off | |
| 53 | final var map = Map.of( | |
| 54 | "https://stackoverflow.com/robots.txt", TEXT_PLAIN, | |
| 55 | "https://place-hold.it/300x500", IMAGE_GIF, | |
| 56 | "https://placekitten.com/g/200/300", IMAGE_JPEG, | |
| 57 | "https://upload.wikimedia.org/wikipedia/commons/9/9f/Vimlogo.svg", IMAGE_SVG_XML, | |
| 58 | "https://kroki.io//graphviz/svg/eNpLyUwvSizIUHBXqPZIzcnJ17ULzy_KSanlAgB1EAjQ", IMAGE_SVG_XML | |
| 59 | ); | |
| 60 | //@formatter:on | |
| 61 | ||
| 62 | map.forEach( ( k, v ) -> { | |
| 63 | try( var response = httpGet( new URI( k ) ) ) { | |
| 64 | assertEquals( v, response.getMediaType() ); | |
| 65 | } catch( Exception e ) { | |
| 66 | fail(); | |
| 67 | } | |
| 68 | } ); | |
| 69 | } | |
| 70 | } | |
| 1 | 71 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown; | |
| 3 | ||
| 4 | import com.keenwrite.AwaitFxExtension; | |
| 5 | import com.keenwrite.Caret; | |
| 6 | import com.keenwrite.preferences.Workspace; | |
| 7 | import com.keenwrite.preview.HtmlPreview; | |
| 8 | import com.keenwrite.processors.Processor; | |
| 9 | import com.keenwrite.processors.ProcessorContext; | |
| 10 | import com.keenwrite.processors.markdown.extensions.ImageLinkExtension; | |
| 11 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 12 | import com.vladsch.flexmark.parser.Parser; | |
| 13 | import javafx.stage.Stage; | |
| 14 | import org.junit.jupiter.api.Test; | |
| 15 | import org.junit.jupiter.api.extension.ExtendWith; | |
| 16 | import org.testfx.framework.junit5.ApplicationExtension; | |
| 17 | import org.testfx.framework.junit5.Start; | |
| 18 | ||
| 19 | import java.io.File; | |
| 20 | import java.net.URI; | |
| 21 | import java.net.URL; | |
| 22 | import java.nio.file.Path; | |
| 23 | import java.nio.file.Paths; | |
| 24 | import java.util.HashMap; | |
| 25 | import java.util.List; | |
| 26 | import java.util.Map; | |
| 27 | ||
| 28 | import static com.keenwrite.constants.Constants.DOCUMENT_DEFAULT; | |
| 29 | import static com.keenwrite.ExportFormat.NONE; | |
| 30 | import static java.lang.String.format; | |
| 31 | import static javafx.application.Platform.runLater; | |
| 32 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 33 | import static org.junit.jupiter.api.Assertions.assertNotNull; | |
| 34 | import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; | |
| 35 | ||
| 36 | /** | |
| 37 | * Responsible for testing that linked images render into HTML according to | |
| 38 | * the {@link ImageLinkExtension} rules. | |
| 39 | */ | |
| 40 | @ExtendWith( {ApplicationExtension.class, AwaitFxExtension.class} ) | |
| 41 | @SuppressWarnings( "SameParameterValue" ) | |
| 42 | public class ImageLinkExtensionTest { | |
| 43 | private static final Workspace sWorkspace = new Workspace( | |
| 44 | getResource( "workspace.xml" ) ); | |
| 45 | ||
| 46 | private static final Map<String, String> IMAGES = new HashMap<>(); | |
| 47 | ||
| 48 | private static final String URI_WEB = "placekitten.com/200/200"; | |
| 49 | private static final String URI_DIRNAME = "images"; | |
| 50 | private static final String URI_FILENAME = "kitten"; | |
| 51 | ||
| 52 | /** | |
| 53 | * Path to use for testing image file name resolution. Note that resources use | |
| 54 | * forward slashes, regardless of OS. | |
| 55 | */ | |
| 56 | private static final String URI_PATH = URI_DIRNAME + '/' + URI_FILENAME; | |
| 57 | ||
| 58 | /** | |
| 59 | * Extension for the first existing image that matches the preferred image | |
| 60 | * extension order. | |
| 61 | */ | |
| 62 | private static final String URI_IMAGE_EXT = ".png"; | |
| 63 | ||
| 64 | /** | |
| 65 | * Relative path to an image that exists. | |
| 66 | */ | |
| 67 | private static final String URI_IMAGE = URI_PATH + URI_IMAGE_EXT; | |
| 68 | ||
| 69 | static { | |
| 70 | addUri( URI_PATH + ".png" ); | |
| 71 | addUri( URI_PATH + ".jpg" ); | |
| 72 | addUri( URI_PATH, getResource( URI_PATH + URI_IMAGE_EXT ) ); | |
| 73 | addUri( "//" + URI_WEB ); | |
| 74 | addUri( "http://" + URI_WEB ); | |
| 75 | addUri( "https://" + URI_WEB ); | |
| 76 | } | |
| 77 | ||
| 78 | private HtmlPreview mPreview; | |
| 79 | ||
| 80 | @Start | |
| 81 | @SuppressWarnings( "unused" ) | |
| 82 | private void start( final Stage stage ) { | |
| 83 | mPreview = new HtmlPreview( sWorkspace ); | |
| 84 | } | |
| 85 | ||
| 86 | private static void addUri( final String actualExpected ) { | |
| 87 | addUri( actualExpected, actualExpected ); | |
| 88 | } | |
| 89 | ||
| 90 | private static void addUri( final String actual, final String expected ) { | |
| 91 | IMAGES.put( toMd( actual ), toHtml( expected ) ); | |
| 92 | } | |
| 93 | ||
| 94 | private static String toMd( final String resource ) { | |
| 95 | return format( "", resource ); | |
| 96 | } | |
| 97 | ||
| 98 | private static String toHtml( final String url ) { | |
| 99 | return format( | |
| 100 | "<p><img src=\"%s\" alt=\"Tooltip\" title=\"Title\" /></p>\n", url ); | |
| 101 | } | |
| 102 | ||
| 103 | /** | |
| 104 | * Test that the key URIs present in the {@link #IMAGES} map are rendered | |
| 105 | * as the value URIs present in the same map. | |
| 106 | */ | |
| 107 | @Test | |
| 108 | void test_ImageLookup_RelativePathWithExtension_ResolvedSuccessfully() { | |
| 109 | final var resource = getResourcePath( URI_IMAGE ); | |
| 110 | final var imagePath = new File( URI_IMAGE ).toPath(); | |
| 111 | final var subpaths = resource.getNameCount() - imagePath.getNameCount(); | |
| 112 | final var subpath = resource.subpath( 0, subpaths ); | |
| 113 | ||
| 114 | // The root component isn't considered part of the path, so add it back. | |
| 115 | final var documentPath = Path.of( | |
| 116 | resource.getRoot().resolve( subpath ).toString(), | |
| 117 | DOCUMENT_DEFAULT.getName() ); | |
| 118 | final var context = createProcessorContext( documentPath ); | |
| 119 | final var extension = ImageLinkExtension.create( context ); | |
| 120 | final var extensions = List.of( extension ); | |
| 121 | final var pBuilder = Parser.builder(); | |
| 122 | final var hBuilder = HtmlRenderer.builder(); | |
| 123 | final var parser = pBuilder.extensions( extensions ).build(); | |
| 124 | final var renderer = hBuilder.extensions( extensions ).build(); | |
| 125 | ||
| 126 | assertNotNull( parser ); | |
| 127 | assertNotNull( renderer ); | |
| 128 | ||
| 129 | for( final var entry : IMAGES.entrySet() ) { | |
| 130 | final var key = entry.getKey(); | |
| 131 | final var node = parser.parse( key ); | |
| 132 | final var expectedHtml = entry.getValue(); | |
| 133 | final var actualHtml = new StringBuilder( 128 ); | |
| 134 | ||
| 135 | runLater( () -> actualHtml.append( renderer.render( node ) ) ); | |
| 136 | ||
| 137 | waitForFxEvents(); | |
| 138 | assertEquals( expectedHtml, actualHtml.toString() ); | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | /** | |
| 143 | * Creates a new {@link ProcessorContext} for the given file name path. | |
| 144 | * | |
| 145 | * @param documentPath Fully qualified path to the file name. | |
| 146 | * @return A context used for creating new {@link Processor} instances. | |
| 147 | */ | |
| 148 | private ProcessorContext createProcessorContext( final Path documentPath ) { | |
| 149 | return new ProcessorContext( | |
| 150 | mPreview, | |
| 151 | new HashMap<>(), | |
| 152 | documentPath, | |
| 153 | null, | |
| 154 | NONE, | |
| 155 | sWorkspace, | |
| 156 | Caret.builder().build() | |
| 157 | ); | |
| 158 | } | |
| 159 | ||
| 160 | private static URL toUrl( final String path ) { | |
| 161 | final var clazz = ImageLinkExtensionTest.class; | |
| 162 | final var packagePath = clazz.getPackageName().replace( '.', '/' ); | |
| 163 | final var resourcePath = '/' + packagePath + '/' + path; | |
| 164 | return clazz.getResource( resourcePath ); | |
| 165 | } | |
| 166 | ||
| 167 | private static URI toUri( final String path ) { | |
| 168 | try { | |
| 169 | return toUrl( path ).toURI(); | |
| 170 | } catch( final Exception ex ) { | |
| 171 | throw new RuntimeException( ex ); | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | private static Path getResourcePath( final String path ) { | |
| 176 | return Paths.get( toUri( path ) ); | |
| 177 | } | |
| 178 | ||
| 179 | private static String getResource( final String path ) { | |
| 180 | return toUri( path ).toString(); | |
| 181 | } | |
| 182 | } | |
| 1 | 183 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.r; | |
| 3 | ||
| 4 | import org.junit.jupiter.api.BeforeAll; | |
| 5 | import org.junit.jupiter.api.Test; | |
| 6 | ||
| 7 | import javax.script.ScriptEngine; | |
| 8 | import javax.script.ScriptEngineManager; | |
| 9 | import javax.script.ScriptException; | |
| 10 | import java.util.Map; | |
| 11 | ||
| 12 | import static java.lang.String.format; | |
| 13 | import static java.util.Map.entry; | |
| 14 | import static java.util.Map.ofEntries; | |
| 15 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 16 | ||
| 17 | /** | |
| 18 | * Test that English pluralization rules produce expected values. | |
| 19 | */ | |
| 20 | public class PluralizeTest { | |
| 21 | private static final ScriptEngine ENGINE = | |
| 22 | (new ScriptEngineManager()).getEngineByName( "Renjin" ); | |
| 23 | ||
| 24 | private static final Map<String, String> PLURAL_MAP = ofEntries( | |
| 25 | entry( "beef", "beefs" ), | |
| 26 | entry( "brother", "brothers" ), | |
| 27 | entry( "child", "children" ), | |
| 28 | entry( "cow", "cows" ), | |
| 29 | entry( "ephemeris", "ephemerides" ), | |
| 30 | entry( "genie", "genies" ), | |
| 31 | entry( "money", "moneys" ), | |
| 32 | entry( "mongoose", "mongooses" ), | |
| 33 | entry( "mythos", "mythoi" ), | |
| 34 | entry( "octopus", "octopuses" ), | |
| 35 | entry( "ox", "oxen" ), | |
| 36 | entry( "soliloquy", "soliloquies" ), | |
| 37 | entry( "trilby", "trilbys" ), | |
| 38 | entry( "wolf", "wolves" ) | |
| 39 | ); | |
| 40 | ||
| 41 | @BeforeAll | |
| 42 | static void setup() throws ScriptException { | |
| 43 | r( "setwd( 'R' );" ); | |
| 44 | r( "source( 'pluralize.R' );" ); | |
| 45 | } | |
| 46 | ||
| 47 | @Test | |
| 48 | @SuppressWarnings("UnnecessaryLocalVariable") | |
| 49 | public void test_Pluralize_SingularForms_PluralForms() | |
| 50 | throws ScriptException { | |
| 51 | for( final var key : PLURAL_MAP.keySet() ) { | |
| 52 | final var expectedSingular = key; | |
| 53 | final var expectedPlural = PLURAL_MAP.get( key ); | |
| 54 | final var actualSingular = pluralize( key, 1 ); | |
| 55 | final var actualPlural = pluralize( key, 2 ); | |
| 56 | ||
| 57 | assertEquals( expectedSingular, actualSingular ); | |
| 58 | assertEquals( expectedPlural, actualPlural ); | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | private String pluralize( final String word, final int count ) | |
| 63 | throws ScriptException { | |
| 64 | return r( format( "pluralize( '%s', %d );", word, count ) ).toString(); | |
| 65 | } | |
| 66 | ||
| 67 | private static Object r( final String code ) throws ScriptException { | |
| 68 | return ENGINE.eval( code ); | |
| 69 | } | |
| 70 | } | |
| 1 | 71 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.sigils; | |
| 3 | ||
| 4 | import javafx.beans.property.SimpleStringProperty; | |
| 5 | import javafx.beans.property.StringProperty; | |
| 6 | import org.junit.jupiter.api.Test; | |
| 7 | ||
| 8 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 9 | ||
| 10 | /** | |
| 11 | * Responsible for simulating R variable injection. | |
| 12 | */ | |
| 13 | class RSigilOperatorTest { | |
| 14 | ||
| 15 | private final SigilOperator mOperator = createRSigilOperator(); | |
| 16 | ||
| 17 | /** | |
| 18 | * Test that a key name becomes an R variable. | |
| 19 | */ | |
| 20 | @Test | |
| 21 | void test_Entoken_KeyName_Tokenized() { | |
| 22 | final var expected = "v$a$b$c$d"; | |
| 23 | final var actual = mOperator.entoken( "{{a.b.c.d}}" ); | |
| 24 | assertEquals( expected, actual ); | |
| 25 | } | |
| 26 | ||
| 27 | /** | |
| 28 | * Test that a key name becomes a viable R expression. | |
| 29 | */ | |
| 30 | @Test | |
| 31 | void test_Apply_KeyName_Expression() { | |
| 32 | final var expected = "`r#x(v$a$b$c$d)`"; | |
| 33 | final var actual = mOperator.apply( "v$a$b$c$d" ); | |
| 34 | assertEquals( expected, actual ); | |
| 35 | } | |
| 36 | ||
| 37 | private StringProperty createToken( final String token ) { | |
| 38 | return new SimpleStringProperty( token ); | |
| 39 | } | |
| 40 | ||
| 41 | private Tokens createRTokens() { | |
| 42 | return createTokens( "x(", ")" ); | |
| 43 | } | |
| 44 | ||
| 45 | private Tokens createYamlTokens() { | |
| 46 | return createTokens( "{{", "}}" ); | |
| 47 | } | |
| 48 | ||
| 49 | private Tokens createTokens( final String began, final String ended ) { | |
| 50 | return new Tokens( createToken( began ), createToken( ended ) ); | |
| 51 | } | |
| 52 | ||
| 53 | private YamlSigilOperator createYamlSigilOperator() { | |
| 54 | return new YamlSigilOperator( createYamlTokens() ); | |
| 55 | } | |
| 56 | ||
| 57 | private RSigilOperator createRSigilOperator() { | |
| 58 | return new RSigilOperator( createRTokens(), createYamlSigilOperator() ); | |
| 59 | } | |
| 60 | } | |
| 1 | 61 |
| 1 | /* | |
| 2 | * Copyright 2020-2021 White Magic Software, Ltd. | |
| 3 | * | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * | |
| 9 | * o Redistributions of source code must retain the above copyright | |
| 10 | * notice, this list of conditions and the following disclaimer. | |
| 11 | * | |
| 12 | * o Redistributions in binary form must reproduce the above copyright | |
| 13 | * notice, this list of conditions and the following disclaimer in the | |
| 14 | * documentation and/or other materials provided with the distribution. | |
| 15 | * | |
| 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 27 | */ | |
| 28 | package com.keenwrite.tex; | |
| 29 | ||
| 30 | import com.whitemagicsoftware.tex.DefaultTeXFont; | |
| 31 | import com.whitemagicsoftware.tex.TeXEnvironment; | |
| 32 | import com.whitemagicsoftware.tex.TeXFormula; | |
| 33 | import com.whitemagicsoftware.tex.TeXLayout; | |
| 34 | import com.whitemagicsoftware.tex.graphics.AbstractGraphics2D; | |
| 35 | import com.whitemagicsoftware.tex.graphics.SvgDomGraphics2D; | |
| 36 | import com.whitemagicsoftware.tex.graphics.SvgGraphics2D; | |
| 37 | import org.apache.batik.transcoder.TranscoderException; | |
| 38 | import org.junit.jupiter.api.Test; | |
| 39 | import org.xml.sax.SAXException; | |
| 40 | ||
| 41 | import javax.imageio.ImageIO; | |
| 42 | import java.awt.image.BufferedImage; | |
| 43 | import java.io.ByteArrayInputStream; | |
| 44 | import java.io.File; | |
| 45 | import java.io.IOException; | |
| 46 | import java.nio.file.Path; | |
| 47 | import java.text.ParseException; | |
| 48 | ||
| 49 | import static com.keenwrite.dom.DocumentParser.parse; | |
| 50 | import static com.keenwrite.preview.SvgRasterizer.*; | |
| 51 | import static java.lang.System.getProperty; | |
| 52 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 53 | ||
| 54 | /** | |
| 55 | * Test that TeX rasterization produces a readable image. | |
| 56 | */ | |
| 57 | public class TeXRasterizationTest { | |
| 58 | private static final String EQUATION = | |
| 59 | "G_{\\mu \\nu} = \\frac{8 \\pi G}{c^4} T_{{\\mu \\nu}}"; | |
| 60 | ||
| 61 | private static final String DIR_TEMP = getProperty( "java.io.tmpdir" ); | |
| 62 | ||
| 63 | private static final long FILESIZE = 12364; | |
| 64 | ||
| 65 | /** | |
| 66 | * Test that an equation can be converted to a raster image and the | |
| 67 | * final raster image size corresponds to the input equation. This is | |
| 68 | * a simple way to verify that the rasterization process is correct, | |
| 69 | * albeit if any aspect of the SVG algorithm changes (such as padding | |
| 70 | * around the equation), it will cause this test to fail, which is a bit | |
| 71 | * misleading. | |
| 72 | */ | |
| 73 | @Test | |
| 74 | public void test_Rasterize_SimpleFormula_CorrectImageSize() | |
| 75 | throws IOException, ParseException, TranscoderException { | |
| 76 | final var g = new SvgGraphics2D(); | |
| 77 | ||
| 78 | drawGraphics( g ); | |
| 79 | verifyImage( rasterizeString( g.toString() ) ); | |
| 80 | } | |
| 81 | ||
| 82 | /** | |
| 83 | * Test that an SVG document object model can be parsed and rasterized into | |
| 84 | * an image. | |
| 85 | */ | |
| 86 | @Test | |
| 87 | public void getTest_SvgDomGraphics2D_InputElement_OutputRasterizedImage() | |
| 88 | throws IOException, SAXException, ParseException, TranscoderException { | |
| 89 | final var g = new SvgGraphics2D(); | |
| 90 | drawGraphics( g ); | |
| 91 | ||
| 92 | final var expectedSvg = g.toString(); | |
| 93 | final var bytes = expectedSvg.getBytes(); | |
| 94 | final var doc = parse( new ByteArrayInputStream( bytes ) ); | |
| 95 | final var actualSvg = toSvg( doc.getDocumentElement() ); | |
| 96 | ||
| 97 | verifyImage( rasterizeString( actualSvg ) ); | |
| 98 | } | |
| 99 | ||
| 100 | /** | |
| 101 | * Test that an SVG image from a DOM element can be rasterized. | |
| 102 | * | |
| 103 | * @throws IOException Could not write the image. | |
| 104 | */ | |
| 105 | @Test | |
| 106 | public void test_SvgDomGraphics2D_InputDom_OutputRasterizedImage() | |
| 107 | throws IOException, ParseException, TranscoderException { | |
| 108 | final var g = new SvgDomGraphics2D(); | |
| 109 | ||
| 110 | drawGraphics( g ); | |
| 111 | verifyImage( rasterize( g.toDom() ) ); | |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * Asserts that the given image matches an expected file size. | |
| 116 | * | |
| 117 | * @param image The image to check against the file size. | |
| 118 | * @throws IOException Could not write the image. | |
| 119 | */ | |
| 120 | private void verifyImage( final BufferedImage image ) throws IOException { | |
| 121 | final var file = export( image, "dom.png" ); | |
| 122 | assertEquals( FILESIZE, file.length() ); | |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * Creates an SVG string for the default equation and font size. | |
| 127 | */ | |
| 128 | private void drawGraphics( final AbstractGraphics2D g ) { | |
| 129 | final var size = 100f; | |
| 130 | final var texFont = new DefaultTeXFont( size ); | |
| 131 | final var env = new TeXEnvironment( texFont ); | |
| 132 | g.scale( size, size ); | |
| 133 | ||
| 134 | final var formula = new TeXFormula( EQUATION ); | |
| 135 | final var box = formula.createBox( env ); | |
| 136 | final var layout = new TeXLayout( box, size ); | |
| 137 | ||
| 138 | g.initialize( layout.getWidth(), layout.getHeight() ); | |
| 139 | box.draw( g, layout.getX(), layout.getY() ); | |
| 140 | } | |
| 141 | ||
| 142 | @SuppressWarnings( "SameParameterValue" ) | |
| 143 | private File export( final BufferedImage image, final String filename ) | |
| 144 | throws IOException { | |
| 145 | final var path = Path.of( DIR_TEMP, filename ); | |
| 146 | final var file = path.toFile(); | |
| 147 | ImageIO.write( image, "png", file ); | |
| 148 | file.deleteOnExit(); | |
| 149 | return file; | |
| 150 | } | |
| 151 | } | |
| 1 | 152 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import org.junit.jupiter.api.Test; | |
| 5 | ||
| 6 | import java.util.ArrayList; | |
| 7 | import java.util.Arrays; | |
| 8 | import java.util.Collections; | |
| 9 | ||
| 10 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 11 | ||
| 12 | /** | |
| 13 | * Responsible for testing the http://www.davekoelle.com/alphanum.html | |
| 14 | * implementation. | |
| 15 | */ | |
| 16 | class AlphanumComparatorTest { | |
| 17 | ||
| 18 | /** | |
| 19 | * Test that a randomly sorted list containing a mix of alphanumeric | |
| 20 | * characters ("chunks") will be sorted according to numeric and alphabetic | |
| 21 | * order. | |
| 22 | */ | |
| 23 | @Test | |
| 24 | public void test_Sort_UnsortedList_SortedAlphanumerically() { | |
| 25 | final var expected = Arrays.asList( | |
| 26 | "10X Radonius", | |
| 27 | "20X Radonius", | |
| 28 | "20X Radonius Prime", | |
| 29 | "30X Radonius", | |
| 30 | "40X Radonius", | |
| 31 | "200X Radonius", | |
| 32 | "1000X Radonius Maximus", | |
| 33 | "Allegia 6R Clasteron", | |
| 34 | "Allegia 50 Clasteron", | |
| 35 | "Allegia 50B Clasteron", | |
| 36 | "Allegia 51 Clasteron", | |
| 37 | "Allegia 500 Clasteron", | |
| 38 | "Alpha 2", | |
| 39 | "Alpha 2A", | |
| 40 | "Alpha 2A-900", | |
| 41 | "Alpha 2A-8000", | |
| 42 | "Alpha 100", | |
| 43 | "Alpha 200", | |
| 44 | "Callisto Morphamax", | |
| 45 | "Callisto Morphamax 500", | |
| 46 | "Callisto Morphamax 600", | |
| 47 | "Callisto Morphamax 700", | |
| 48 | "Callisto Morphamax 5000", | |
| 49 | "Callisto Morphamax 6000 SE", | |
| 50 | "Callisto Morphamax 6000 SE2", | |
| 51 | "Callisto Morphamax 7000", | |
| 52 | "Xiph Xlater 5", | |
| 53 | "Xiph Xlater 40", | |
| 54 | "Xiph Xlater 50", | |
| 55 | "Xiph Xlater 58", | |
| 56 | "Xiph Xlater 300", | |
| 57 | "Xiph Xlater 500", | |
| 58 | "Xiph Xlater 2000", | |
| 59 | "Xiph Xlater 5000", | |
| 60 | "Xiph Xlater 10000" | |
| 61 | ); | |
| 62 | final var actual = new ArrayList<>( expected ); | |
| 63 | ||
| 64 | Collections.shuffle( actual ); | |
| 65 | actual.sort( new AlphanumComparator<>() ); | |
| 66 | assertEquals( expected, actual ); | |
| 67 | } | |
| 68 | } | |
| 1 | 69 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.util; | |
| 3 | ||
| 4 | import org.junit.jupiter.api.Test; | |
| 5 | ||
| 6 | import java.util.List; | |
| 7 | import java.util.ListIterator; | |
| 8 | import java.util.NoSuchElementException; | |
| 9 | ||
| 10 | import static org.junit.jupiter.api.Assertions.*; | |
| 11 | ||
| 12 | /** | |
| 13 | * Tests the {@link CyclicIterator} class. | |
| 14 | */ | |
| 15 | public class CyclicIteratorTest { | |
| 16 | /** | |
| 17 | * Test that the {@link CyclicIterator} can move forwards and backwards | |
| 18 | * through a {@link List}. | |
| 19 | */ | |
| 20 | @Test | |
| 21 | public void test_Directions_NextPreviousCycles_Success() { | |
| 22 | final var list = List.of( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ); | |
| 23 | final var iterator = createCyclicIterator( list ); | |
| 24 | ||
| 25 | // Test forwards through the iterator. | |
| 26 | for( int i = 0; i < list.size(); i++ ) { | |
| 27 | assertTrue( iterator.hasNext() ); | |
| 28 | assertEquals( i, iterator.next() ); | |
| 29 | } | |
| 30 | ||
| 31 | // Loop to the first item. | |
| 32 | iterator.next(); | |
| 33 | ||
| 34 | // Test backwards through the iterator. | |
| 35 | for( int i = list.size() - 1; i >= 0; i-- ) { | |
| 36 | assertTrue( iterator.hasPrevious() ); | |
| 37 | assertEquals( i, iterator.previous() ); | |
| 38 | } | |
| 39 | } | |
| 40 | ||
| 41 | /** | |
| 42 | * Test that the {@link CyclicIterator} returns the last element when | |
| 43 | * the very first API call is to {@link ListIterator#previous()}. | |
| 44 | */ | |
| 45 | @Test | |
| 46 | public void test_Direction_FirstPrevious_ReturnsLastElement() { | |
| 47 | final var list = List.of( 1, 2, 3, 4, 5, 6, 7 ); | |
| 48 | final var iterator = createCyclicIterator( list ); | |
| 49 | ||
| 50 | assertEquals( iterator.previous(), list.get( list.size() - 1 ) ); | |
| 51 | } | |
| 52 | ||
| 53 | @Test | |
| 54 | public void test_Empty_Next_Exception() { | |
| 55 | final var iterator = createCyclicIterator( List.of() ); | |
| 56 | assertThrows( NoSuchElementException.class, iterator::next ); | |
| 57 | } | |
| 58 | ||
| 59 | @Test | |
| 60 | public void test_Empty_Previous_Exception() { | |
| 61 | final var iterator = createCyclicIterator( List.of() ); | |
| 62 | assertThrows( NoSuchElementException.class, iterator::previous ); | |
| 63 | } | |
| 64 | ||
| 65 | private <T> CyclicIterator<T> createCyclicIterator( final List<T> list ) { | |
| 66 | return new CyclicIterator<>( list ); | |
| 67 | } | |
| 68 | } | |
| 1 | 69 |
| 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 34" width="68px" height="34px" fill="#005a9c"> | |
| 2 | <desc>W3C</desc> | |
| 3 | <path d="m16.117 1.006 5.759 19.58 5.759-19.58h4.17 11.444v1.946l-5.879 10.128c2.065.663 3.627 1.868 4.686 3.615 1.059 1.748 1.589 3.799 1.589 6.155 0 2.914-.775 5.363-2.324 7.348s-3.555 2.978-6.017 2.978c-1.854 0-3.469-.589-4.845-1.767-1.377-1.178-2.396-2.773-3.058-4.786l3.256-1.35c.477 1.218 1.106 2.178 1.887 2.879.781.702 1.701 1.052 2.76 1.052 1.112 0 2.052-.622 2.82-1.866.768-1.245 1.152-2.74 1.152-4.489 0-1.933-.411-3.429-1.231-4.488-.954-1.244-2.45-1.867-4.489-1.867h-1.588v-1.906l5.56-9.612h-6.712l-.382.65-8.163 27.548h-.397l-5.958-19.937-5.957 19.937h-.397l-9.53-32.168h4.17l5.759 19.58 3.892-13.185-1.906-6.395z"/> | |
| 4 | <path d="m64.92 1.006c-.819 0-1.554.295-2.111.861-.591.6-.92 1.376-.92 2.178s.313 1.545.887 2.128c.583.591 1.334.912 2.145.912.793 0 1.562-.321 2.161-.903.574-.557.887-1.3.887-2.136 0-.811-.321-1.57-.878-2.136-.584-.592-1.344-.904-2.171-.904zm2.643 3.065c0 .701-.271 1.351-.768 1.832-.524.507-1.174.777-1.892.777-.675 0-1.342-.278-1.84-.785s-.777-1.157-.777-1.849.287-1.368.802-1.891c.481-.49 1.131-.751 1.84-.751.726 0 1.376.271 1.883.785.49.489.752 1.147.752 1.882zm-2.559-1.807h-1.3v3.445h.65v-1.469h.642l.701 1.469h.726l-.769-1.57c.498-.102.785-.439.785-.929 0-.625-.472-.946-1.435-.946zm-.118.422c.608 0 .886.169.886.591 0 .405-.278.549-.87.549h-.549v-1.14z"/> | |
| 5 | <path d="m59.807.825.676 4.107-2.391 4.575s-.918-1.941-2.443-3.015c-1.285-.905-2.122-1.102-3.431-.832-1.681.347-3.587 2.357-4.419 4.835-.995 2.965-1.005 4.4-1.04 5.718-.056 2.113.277 3.362.277 3.362s-1.452-2.686-1.438-6.62c.009-2.808.451-5.354 1.75-7.867 1.143-2.209 2.842-3.535 4.35-3.691 1.559-.161 2.791.59 3.743 1.403 1 .854 2.01 2.721 2.01 2.721z"/> | |
| 6 | <path d="m60.102 24.063s-1.057 1.889-1.715 2.617c-.659.728-1.837 2.01-3.292 2.651s-2.218.762-3.656.624c-1.437-.138-2.772-.97-3.24-1.317s-1.664-1.369-2.34-2.322-1.733-2.859-1.733-2.859.589 1.91.958 2.721c.212.467.864 1.894 1.789 3.136.863 1.159 2.539 3.154 5.086 3.604 2.547.451 4.297-.693 4.73-.97s1.346-1.042 1.924-1.66c.603-.645 1.174-1.468 1.49-1.962.231-.36.607-1.092.607-1.092z"/> | |
| 7 | </svg> | |
| 1 | 8 |
| 1 | #define 1617524430813_width 72 | |
| 2 | #define 1617524430813_height 48 | |
| 3 | static char 1617524430813_bits[] = { | |
| 4 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, | |
| 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 6 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, | |
| 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 9 | 0x00, 0x00, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, | |
| 10 | 0x10, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0xAA, 0x78, 0x80, 0x07, | |
| 11 | 0xC0, 0xFF, 0x3F, 0xE0, 0x41, 0x44, 0x78, 0x80, 0x0F, 0xC0, 0xFF, 0x3F, | |
| 12 | 0xF8, 0x67, 0x18, 0xF0, 0x80, 0x07, 0xE0, 0xFF, 0x3F, 0xF8, 0xE7, 0x00, | |
| 13 | 0xE0, 0x00, 0x0F, 0xE0, 0xFF, 0x1F, 0xFC, 0xFF, 0x00, 0xF0, 0x01, 0x0F, | |
| 14 | 0xE0, 0x01, 0x1D, 0xFC, 0x7F, 0x00, 0xF0, 0x01, 0x1F, 0xE0, 0x81, 0x0F, | |
| 15 | 0x3E, 0x7C, 0x00, 0xE0, 0x01, 0x1E, 0xE0, 0x81, 0x07, 0x0E, 0x38, 0x00, | |
| 16 | 0xE0, 0x01, 0x17, 0xF0, 0xC0, 0x07, 0x0F, 0x30, 0x00, 0xE0, 0x03, 0x3F, | |
| 17 | 0xF0, 0xC0, 0x03, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x3F, 0xF8, 0xE0, 0x03, | |
| 18 | 0x07, 0x00, 0x00, 0xC0, 0x83, 0x3F, 0x78, 0xF0, 0x01, 0x07, 0x00, 0x00, | |
| 19 | 0xC0, 0x83, 0x3F, 0x78, 0xB0, 0x00, 0x03, 0x00, 0x00, 0x80, 0x87, 0x7F, | |
| 20 | 0x78, 0xF8, 0x03, 0x03, 0x00, 0x00, 0x80, 0x87, 0x7B, 0x78, 0xFC, 0x07, | |
| 21 | 0x03, 0x00, 0x00, 0x80, 0xCF, 0x7B, 0x3C, 0xF8, 0x0F, 0x03, 0x00, 0x00, | |
| 22 | 0x80, 0xC7, 0xE3, 0x3E, 0xC8, 0x1F, 0x02, 0x00, 0x00, 0x00, 0xEF, 0xF2, | |
| 23 | 0x3C, 0x00, 0x1F, 0x02, 0x00, 0x00, 0x00, 0xEF, 0xF1, 0x1C, 0x00, 0x3E, | |
| 24 | 0x02, 0x00, 0x00, 0x00, 0xEF, 0xB1, 0x1F, 0x00, 0x3C, 0x00, 0x00, 0x00, | |
| 25 | 0x00, 0xFE, 0xE1, 0x1F, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xF6, 0xE0, | |
| 26 | 0x17, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xE0, 0x0F, 0x00, 0x3C, | |
| 27 | 0x00, 0x00, 0x00, 0x00, 0x7E, 0xC0, 0x0F, 0x00, 0x7C, 0x00, 0x00, 0x00, | |
| 28 | 0x00, 0xFC, 0xC0, 0x0F, 0x00, 0xFC, 0x00, 0x40, 0x00, 0x00, 0x7C, 0x80, | |
| 29 | 0x07, 0x03, 0xBC, 0x00, 0x60, 0x00, 0x00, 0x7C, 0xC0, 0xC7, 0x01, 0x3E, | |
| 30 | 0x03, 0xE0, 0x00, 0x00, 0x18, 0x80, 0xC7, 0x07, 0x1E, 0x03, 0x70, 0x00, | |
| 31 | 0x00, 0x38, 0x80, 0x82, 0x87, 0x1F, 0x0E, 0x7C, 0x00, 0x00, 0x38, 0x00, | |
| 32 | 0x03, 0xFF, 0x1D, 0xBC, 0x3F, 0x00, 0x00, 0x30, 0x00, 0x03, 0xFF, 0x0F, | |
| 33 | 0xFC, 0x1F, 0x00, 0x00, 0x10, 0x00, 0x03, 0xFC, 0x07, 0xF0, 0x1F, 0x00, | |
| 34 | 0x00, 0x10, 0x00, 0x01, 0xF8, 0x01, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x00, | |
| 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 39 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | |
| 40 | }; | |
| 1 | 41 |
| 1 | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | <keenwrite> | |
| 3 | <workspace> | |
| 4 | <images> | |
| 5 | <order>svg pdf png jpg tiff</order> | |
| 6 | <dir></dir> | |
| 7 | </images> | |
| 8 | </workspace> | |
| 9 | </keenwrite> | |
| 1 | 10 |
| 1 | *.class | |
| 1 | 2 |
| 1 | Sikuli is used for the following purposes: | |
| 2 | ||
| 3 | * Create application videos. | |
| 4 | * Create integration tests. | |
| 5 | ||
| 1 | 6 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # Runs all scripts | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | ||
| 28 | import s01 | |
| 29 | import s02 | |
| 30 | import s03 | |
| 31 | import s04 | |
| 1 | 32 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script introduces the editor and its purpose. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | from sikuli import * | |
| 28 | import sys | |
| 29 | ||
| 30 | if not "../editor.sikuli" in sys.path: | |
| 31 | sys.path.append( "../editor.sikuli" ) | |
| 32 | ||
| 33 | from editor import * | |
| 34 | ||
| 35 | # --------------------------------------------------------------- | |
| 36 | # Fresh start | |
| 37 | # --------------------------------------------------------------- | |
| 38 | rm( app_home + "/variables.yaml" ) | |
| 39 | rm( app_home + "/untitled.md" ) | |
| 40 | rm( dir_home + "/.scrivenvar" ) | |
| 41 | ||
| 42 | # --------------------------------------------------------------- | |
| 43 | # Wait for application to launch | |
| 44 | # --------------------------------------------------------------- | |
| 45 | openApp( "java -jar " + app_bin ) | |
| 46 | ||
| 47 | wait("1594187265140.png", 30) | |
| 48 | ||
| 49 | # Breathing room for video recording. | |
| 50 | wait( 4 ) | |
| 51 | ||
| 52 | # --------------------------------------------------------------- | |
| 53 | # Introduction | |
| 54 | # --------------------------------------------------------------- | |
| 55 | set_typing_speed( 240 ) | |
| 56 | ||
| 57 | heading( "What is this application?" ) | |
| 58 | typer( "Well, this application is a text editor that supports interpolated definitions, ") | |
| 59 | typer( "a few different text formats, real-time preview, spell check ") | |
| 60 | typer( "as you tipe" ) | |
| 61 | wait( 0.5 ) | |
| 62 | recur( 3, backspace ) | |
| 63 | typer( "ype, and R statements." ) | |
| 64 | paragraph() | |
| 65 | wait( 1 ) | |
| 66 | ||
| 67 | # --------------------------------------------------------------- | |
| 68 | # Definition demo | |
| 69 | # --------------------------------------------------------------- | |
| 70 | heading( "What are definitions?" ) | |
| 71 | typer( "Watch. " ) | |
| 72 | wait( .5 ) | |
| 73 | ||
| 74 | # Focus the definition editor. | |
| 75 | click_create() | |
| 76 | recur( 4, tab ) | |
| 77 | ||
| 78 | wait( .5 ) | |
| 79 | rename_definition( "application" ) | |
| 80 | ||
| 81 | insert() | |
| 82 | rename_definition( "title" ) | |
| 83 | ||
| 84 | insert() | |
| 85 | rename_definition( "Scrivenvar" ) | |
| 86 | ||
| 87 | # Set focus to the text editor. | |
| 88 | tab() | |
| 89 | ||
| 90 | typer( "The left-hand pane contains a nested, folder-like structure of names " ) | |
| 91 | typer( "and values that are called *definitions*. " ) | |
| 92 | wait( .5 ) | |
| 93 | typer( "Such definitions can simplify updating documents. " ) | |
| 94 | wait( 1 ) | |
| 95 | ||
| 96 | edit_find( "this application" ) | |
| 97 | typer( "$application.title$" ) | |
| 98 | ||
| 99 | edit_find_next() | |
| 100 | typer( "$application.title$" ) | |
| 101 | ||
| 102 | type( Key.END, Key.CTRL ) | |
| 103 | ||
| 104 | typer( "The right-hand pane shows the result after having substituted definition " ) | |
| 105 | typer( "values into the document." ) | |
| 106 | ||
| 107 | paragraph() | |
| 108 | typer( "Now nobody wants to type definition names all the time. Instead, type any " ) | |
| 109 | typer( "partial definition value followed by `Ctrl+Space`, such as: scr" ) | |
| 110 | wait( 0.5 ) | |
| 111 | autoinsert() | |
| 112 | wait( 1 ) | |
| 113 | typer( ". *Much* better!" ) | |
| 114 | paragraph() | |
| 115 | ||
| 116 | heading( "What is interpolation?" ) | |
| 117 | typer( "Definition values can reference definition names. " ) | |
| 118 | wait( .5 ) | |
| 119 | typer( "The definition names act as placeholders. Substituting placeholders with " ) | |
| 120 | typer( "their definition value is called *interpolation*. Let's see how it works." ) | |
| 121 | wait( 2 ) | |
| 1 | 122 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script demonstrates how to use interpolated strings. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | from sikuli import * | |
| 28 | import sys | |
| 29 | ||
| 30 | if not "../editor.sikuli" in sys.path: | |
| 31 | sys.path.append( "../editor.sikuli" ) | |
| 32 | ||
| 33 | from editor import * | |
| 34 | ||
| 35 | # ----------------------------------------------------------------------------- | |
| 36 | # Open sample chapter. | |
| 37 | # ----------------------------------------------------------------------------- | |
| 38 | file_open() | |
| 39 | type( Key.UP, Key.ALT ) | |
| 40 | wait( 1 ) | |
| 41 | typer( Key.END ) | |
| 42 | wait( 1 ) | |
| 43 | enter() | |
| 44 | wait( 0.5 ) | |
| 45 | enter() | |
| 46 | wait( 1 ) | |
| 47 | ||
| 48 | # ----------------------------------------------------------------------------- | |
| 49 | # Open the corresponding definition file. | |
| 50 | # ----------------------------------------------------------------------------- | |
| 51 | file_open() | |
| 52 | recur( 2, down ) | |
| 53 | wait( 1 ) | |
| 54 | enter() | |
| 55 | wait( 1 ) | |
| 56 | ||
| 57 | # ----------------------------------------------------------------------------- | |
| 58 | # Edit the sample document. | |
| 59 | # ----------------------------------------------------------------------------- | |
| 60 | set_typing_speed( 80 ) | |
| 61 | ||
| 62 | type( Key.HOME, Key.CTRL ) | |
| 63 | recur( 2, down ) | |
| 64 | ||
| 65 | # Grey | |
| 66 | recur( 3, skip_right ) | |
| 67 | autoinsert() | |
| 68 | ||
| 69 | # 34 | |
| 70 | recur( 4, skip_right ) | |
| 71 | autoinsert() | |
| 72 | ||
| 73 | # Central | |
| 74 | recur( 10, skip_right ) | |
| 75 | autoinsert() | |
| 76 | ||
| 77 | # London | |
| 78 | skip_right() | |
| 79 | autoinsert() | |
| 80 | ||
| 81 | # Hatchery | |
| 82 | skip_right() | |
| 83 | autoinsert() | |
| 84 | ||
| 85 | # and Conditioning | |
| 86 | recur( 2, select_word_right ) | |
| 87 | delete() | |
| 88 | ||
| 89 | # Centre | |
| 90 | skip_right() | |
| 91 | autoinsert() | |
| 92 | ||
| 93 | set_typing_speed( 220 ) | |
| 94 | ||
| 95 | typer( " Let's interpolate those four definitions instead!" ) | |
| 96 | wait( 4 ) | |
| 97 | recur( 13, type, Key.BACKSPACE, Key.CTRL ) | |
| 98 | recur( 9, backspace ) | |
| 99 | ||
| 100 | set_typing_speed( 60 ) | |
| 101 | ||
| 102 | typer( "name$" ) | |
| 103 | wait( 2 ) | |
| 104 | ||
| 105 | # Collapse all definitions | |
| 106 | tab() | |
| 107 | recur( 8, typer, Key.LEFT ) | |
| 108 | ||
| 109 | # Expand to city | |
| 110 | recur( 4, typer, Key.RIGHT ) | |
| 111 | ||
| 112 | # Jump to name | |
| 113 | recur( 2, down ) | |
| 114 | recur( 2, typer, Key.RIGHT ) | |
| 115 | ||
| 116 | # Open the text field to show the full value | |
| 117 | typer( Key.F2 ) | |
| 118 | ||
| 119 | # Traverse the text field | |
| 120 | home() | |
| 121 | recur( 16, type, Key.RIGHT, Key.CTRL ) | |
| 122 | esc() | |
| 123 | ||
| 124 | restore_typing_speed() | |
| 125 | ||
| 126 | tab() | |
| 127 | type( Key.HOME, Key.CTRL ) | |
| 128 | edit_find( "Director" ) | |
| 129 | autoinsert() | |
| 130 | ||
| 131 | edit_find_next() | |
| 132 | autoinsert() | |
| 133 | ||
| 134 | edit_find_next() | |
| 135 | typer( Key.RIGHT ) | |
| 136 | recur( 2, delete ) | |
| 137 | autoinsert() | |
| 138 | typer( "'s" ) | |
| 139 | ||
| 140 | edit_find( "Hatcheries" ) | |
| 141 | autoinsert() | |
| 142 | ||
| 143 | # and Conditioning | |
| 144 | recur( 2, select_word_right ) | |
| 145 | delete() | |
| 146 | ||
| 147 | edit_find( "Central" ) | |
| 148 | autoinsert() | |
| 149 | ||
| 150 | skip_right() | |
| 151 | autoinsert() | |
| 152 | ||
| 153 | typer( " How about a different city?" ) | |
| 154 | wait( 2 ) | |
| 155 | recur( 5, type, Key.BACKSPACE, Key.CTRL ) | |
| 156 | wait( 1 ) | |
| 157 | tab() | |
| 158 | typer( Key.F2 ) | |
| 159 | typer( "Seattle" ) | |
| 160 | enter() | |
| 161 | tab() | |
| 162 | wait( 2 ) | |
| 163 | ||
| 164 | type( Key.END, Key.CTRL ) | |
| 165 | paragraph() | |
| 166 | typer( "No?" ) | |
| 167 | paragraph() | |
| 168 | ||
| 169 | tab() | |
| 170 | typer( Key.F2 ) | |
| 171 | typer( "London" ) | |
| 172 | enter() | |
| 173 | ||
| 174 | tab() | |
| 175 | typer( "Organizing definitions is left to your ") | |
| 176 | typer( "doub" ) | |
| 177 | autoinsert() | |
| 178 | typer( " Good imagination." ) | |
| 179 | tab() | |
| 180 | ||
| 181 | # Jump to "char" definition | |
| 182 | home() | |
| 183 | ||
| 184 | # Jump to "char.a.primary.name" definition | |
| 185 | recur( 6, typer, Key.RIGHT ) | |
| 186 | ||
| 187 | # Jump to "char.a.primary.caste" definition | |
| 188 | down() | |
| 189 | typer( Key.RIGHT ) | |
| 190 | ||
| 191 | # Jump to root-level "caste" definition | |
| 192 | recur( 7, down ) | |
| 193 | ||
| 194 | # Reselect "super" | |
| 195 | recur( 5, typer, Key.RIGHT ) | |
| 196 | wait( 2 ) | |
| 197 | ||
| 198 | # Close the window, no save | |
| 199 | type( "w", Key.CTRL ) | |
| 200 | wait( 0.5 ) | |
| 201 | tab() | |
| 202 | wait( 0.5 ) | |
| 203 | typer( Key.SPACE ) | |
| 204 | wait( 1 ) | |
| 1 | 205 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script introduces images and R. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | from sikuli import * | |
| 28 | import sys | |
| 29 | ||
| 30 | if not "../editor.sikuli" in sys.path: | |
| 31 | sys.path.append( "../editor.sikuli" ) | |
| 32 | ||
| 33 | from editor import * | |
| 34 | ||
| 35 | set_typing_speed( 80 ) | |
| 36 | ||
| 37 | file_open() | |
| 38 | type( Key.UP, Key.ALT ) | |
| 39 | wait( 0.5 ) | |
| 40 | home() | |
| 41 | wait( 0.25 ) | |
| 42 | enter() | |
| 43 | wait( 1 ) | |
| 44 | end() | |
| 45 | wait( 0.25 ) | |
| 46 | enter() | |
| 47 | wait( 1 ) | |
| 48 | ||
| 49 | set_typing_speed( 200 ) | |
| 50 | ||
| 51 | paragraph() | |
| 52 | heading( "What text formats are supported?" ) | |
| 53 | ||
| 54 | typer( "Scr" ) | |
| 55 | autoinsert() | |
| 56 | typer( " supports Markdown, R Markdown, XML, and R XML; however, the software " ) | |
| 57 | typer( "architecture enables it to easily add new formats. The following figure " ) | |
| 58 | typer( "depicts the overall architecture: " ) | |
| 59 | paragraph() | |
| 60 | typer( "" ) | |
| 61 | paragraph() | |
| 62 | typer( "Many text editors can only open one type of plain text markup format that is " ) | |
| 63 | typer( "only output as HTML. With a little more effort, text editors could support " ) | |
| 64 | typer( "multiple input and output formats. Scr" ) | |
| 65 | autoinsert() | |
| 66 | typer( " does so and goes one step further by introducing interpolated definitions." ) | |
| 67 | paragraph() | |
| 68 | typer( "Kitten interlude:" ) | |
| 69 | paragraph() | |
| 70 | typer( "" ) | |
| 71 | paragraph() | |
| 72 | ||
| 73 | heading( "What is R?" ) | |
| 74 | typer( "R is a programming language. You might have noticed a few potential grammar " ) | |
| 75 | typer( "problems with direct substitution. Rules for possessive forms, numbers, and " ) | |
| 76 | typer( "other quirks can be tackled using R." ) | |
| 77 | ||
| 78 | # ----------------------------------------------------------------------------- | |
| 79 | # Demo bootstrapping | |
| 80 | # ----------------------------------------------------------------------------- | |
| 81 | ||
| 82 | # Jump to the end | |
| 83 | type( Key.END, Key.CTRL ) | |
| 84 | paragraph() | |
| 85 | ||
| 86 | set_typing_speed( 300 ) | |
| 87 | heading( "How is R used?" ) | |
| 88 | typer( "R must be instructed where to find script files and what ones to load. The " ) | |
| 89 | typer( "*working directory* is the full path to those R files; the *startup script* " ) | |
| 90 | typer( "defines what R files to load. Both preferences must be changed before prose " ) | |
| 91 | typer( "may be processed. Preferences can be opened using either the " ) | |
| 92 | typeln( "**Edit > Preferences** menu or by pressing `Ctrl+Alt+s`. Here goes!" ) | |
| 93 | wait( 2 ) | |
| 94 | ||
| 95 | # ----------------------------------------------------------------------------- | |
| 96 | # Select the R script directory | |
| 97 | # ----------------------------------------------------------------------------- | |
| 98 | ||
| 99 | # Change the working directory by clicking "Browse" | |
| 100 | type( "s", Key.CTRL + Key.ALT ) | |
| 101 | wait("1594592396134.png", 1) | |
| 102 | click("1594592396134.png") | |
| 103 | wait( 0.5 ) | |
| 104 | ||
| 105 | # Navigate to and select the "r" directory | |
| 106 | type( Key.UP, Key.ALT ) | |
| 107 | wait( 0.5 ) | |
| 108 | end() | |
| 109 | wait( 0.5 ) | |
| 110 | enter() | |
| 111 | wait( 0.5 ) | |
| 112 | end() | |
| 113 | wait( 0.5 ) | |
| 114 | type( Key.UP ) | |
| 115 | wait( 0.5 ) | |
| 116 | recur( 2, tab ) | |
| 117 | wait( 0.5 ) | |
| 118 | enter() | |
| 119 | wait( 1 ) | |
| 120 | ||
| 121 | # ----------------------------------------------------------------------------- | |
| 122 | # Set the R startup script instructions | |
| 123 | # ----------------------------------------------------------------------------- | |
| 124 | ||
| 125 | wait("1594593710440.png", 5) | |
| 126 | click("1594593710440.png") | |
| 127 | ||
| 128 | set_typing_speed( 440 ) | |
| 129 | ||
| 130 | typeln( "setwd( '$application.r.working.directory$' )" ) | |
| 131 | typeln( "assign( 'anchor', '$date.anchor$', envir = .GlobalEnv )" ) | |
| 132 | typeln( "source( 'pluralize.R' )" ) | |
| 133 | typeln( "source( 'possessive.R' )" ) | |
| 134 | typeln( "source( 'conversion.R' )" ) | |
| 135 | typeln( "source( 'csv.R' )" ) | |
| 136 | ||
| 137 | wait("1594593794335.png", 3) | |
| 138 | click("1594593794335.png") | |
| 139 | ||
| 140 | paragraph() | |
| 141 | set_typing_speed( 220 ) | |
| 142 | ||
| 143 | typer( "R is now configured. The startup script and other R " ) | |
| 144 | typer( "files can be found in the " ) | |
| 145 | typer( "[repository](https://github.com/DaveJarvis/scrivenvar/tree/master/R). " ) | |
| 146 | wait( 1.5 ) | |
| 147 | ||
| 148 | # Wait for the browser to appear. | |
| 149 | wait("1594594984108.png", 5) | |
| 150 | click("1594594984108.png") | |
| 151 | ||
| 152 | wait( 5 ) | |
| 153 | click("1594689573764.png") | |
| 154 | ||
| 155 | paragraph() | |
| 156 | typer( "Next, we'll see how definitions and R can work together." ) | |
| 157 | wait( 2 ) | |
| 1 | 158 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script demonstrates using R. | |
| 26 | # ----------------------------------------------------------------------------- | |
| 27 | from sikuli import * | |
| 28 | import sys | |
| 29 | ||
| 30 | if not "../editor.sikuli" in sys.path: | |
| 31 | sys.path.append( "../editor.sikuli" ) | |
| 32 | ||
| 33 | from editor import * | |
| 34 | ||
| 35 | set_typing_speed( 220 ) | |
| 36 | ||
| 37 | # ----------------------------------------------------------------------------- | |
| 38 | # Open the demo text. | |
| 39 | # ----------------------------------------------------------------------------- | |
| 40 | file_open() | |
| 41 | type( Key.UP, Key.ALT ) | |
| 42 | wait( 0.5 ) | |
| 43 | end() | |
| 44 | wait( 0.5 ) | |
| 45 | enter() | |
| 46 | wait( 0.5 ) | |
| 47 | down() | |
| 48 | wait( 0.5 ) | |
| 49 | enter() | |
| 50 | wait( 2 ) | |
| 51 | ||
| 52 | # ----------------------------------------------------------------------------- | |
| 53 | # Re-open the corresponding definition file. | |
| 54 | # ----------------------------------------------------------------------------- | |
| 55 | file_open() | |
| 56 | recur( 2, down ) | |
| 57 | wait( 1 ) | |
| 58 | enter() | |
| 59 | wait( 2 ) | |
| 60 | ||
| 61 | # ----------------------------------------------------------------------------- | |
| 62 | # Brief introduction to R | |
| 63 | # ----------------------------------------------------------------------------- | |
| 64 | type( Key.HOME, Key.CTRL ) | |
| 65 | end() | |
| 66 | paragraph() | |
| 67 | ||
| 68 | typer( "## Using R" ) | |
| 69 | paragraph() | |
| 70 | typer( "Insert R code into documents as follows: `r# 1+1`. " ) | |
| 71 | wait( 1.5 ) | |
| 72 | typer( "Notice how the right-hand pane shows the computed result. I'll wait. " ) | |
| 73 | wait( 3 ) | |
| 74 | typer( "The syntax is: open backtick, r#, *computable expression*, close " ) | |
| 75 | typer( "backtick. That expression can be any valid R statement. The status bar " ) | |
| 76 | typer( "will provide clues when an R expression cannot be computed by the " ) | |
| 77 | typer( "editor. `r# glitch`" ) | |
| 78 | wait( 4 ) | |
| 79 | recur( 11, backspace ) | |
| 80 | typer( "Let's swap 34 storeys for a definition value and replace the number " ) | |
| 81 | typer( "according to the Chicago Manual of Style (cms) rules." ) | |
| 82 | ||
| 83 | # ----------------------------------------------------------------------------- | |
| 84 | # Demo pluralization | |
| 85 | # ----------------------------------------------------------------------------- | |
| 86 | set_typing_speed( 80 ) | |
| 87 | ||
| 88 | edit_find( "34" ) | |
| 89 | autoinsert() | |
| 90 | ||
| 91 | edit_find( "x(" ) | |
| 92 | typer( "cms(" ) | |
| 93 | ||
| 94 | edit_find( "storeys." ) | |
| 95 | typer( "34." ) | |
| 96 | autoinsert() | |
| 97 | edit_find( "x(" ) | |
| 98 | typer( "pl( 'storey'," ) | |
| 99 | wait( 4 ) | |
| 100 | ||
| 101 | tab() | |
| 102 | rename_definition( "1" ) | |
| 103 | wait( 4 ) | |
| 104 | rename_definition( "142" ) | |
| 105 | wait( 4 ) | |
| 106 | rename_definition( "34" ) | |
| 107 | wait( 4 ) | |
| 108 | tab() | |
| 109 | ||
| 110 | # ----------------------------------------------------------------------------- | |
| 111 | # Demo possessives (it, her, his, Director) | |
| 112 | # ----------------------------------------------------------------------------- | |
| 113 | type( Key.HOME, Key.CTRL ) | |
| 114 | edit_find( "Director" ) | |
| 115 | autoinsert() | |
| 116 | edit_find_next() | |
| 117 | autoinsert() | |
| 118 | edit_find_next() | |
| 119 | autoinsert() | |
| 120 | type( Key.RIGHT ) | |
| 121 | recur( 2, delete ) | |
| 122 | autoinsert() | |
| 123 | home() | |
| 124 | edit_find( "x(" ) | |
| 125 | typer( "pos(" ) | |
| 126 | wait( 2 ) | |
| 127 | ||
| 128 | tab() | |
| 129 | rename_definition( "Headmistress" ) | |
| 130 | wait( 4 ) | |
| 131 | rename_definition( "Director" ) | |
| 132 | wait( 2 ) | |
| 133 | tab() | |
| 134 | ||
| 135 | type( Key.END, Key.CTRL ) | |
| 136 | paragraph() | |
| 137 | typer( "Other possessives: `r# pos( 'it' )`, `r# pos( 'her' )`, `r# pos( 'his' )`, " ) | |
| 138 | typer( "and `r# pos( 'my' )`." ) | |
| 139 | ||
| 140 | # ----------------------------------------------------------------------------- | |
| 141 | # Demo conversion, including ordinal numbers | |
| 142 | # ----------------------------------------------------------------------------- | |
| 143 | set_typing_speed( 160 ) | |
| 144 | ||
| 145 | paragraph() | |
| 146 | heading( "Date Conversions" ) | |
| 147 | typer( "Mixing R code with definitions invites endless possibilities. " ) | |
| 148 | typer( "Imagine someone racing to the " ) | |
| 149 | typer( "`r#cms( v$location$breeder$storeys, ordinal=TRUE )` floor, whereby that " ) | |
| 150 | typer( "ordinal stems from the Hatchery's storeys' definition. Or how about " ) | |
| 151 | typer( "a complex timeline where dates are expressed in days relative to one " ) | |
| 152 | typer( "point in time. Let's call this the *anchor date* and define it." ) | |
| 153 | ||
| 154 | tab() | |
| 155 | home() | |
| 156 | typer( Key.SPACE ) | |
| 157 | insert() | |
| 158 | rename_definition( "date" ) | |
| 159 | insert() | |
| 160 | rename_definition( "anchor" ) | |
| 161 | insert() | |
| 162 | rename_definition( "1969-10-29" ) | |
| 163 | tab() | |
| 164 | ||
| 165 | paragraph() | |
| 166 | typer( "Next, set an R variable named `now` to the current date" ) | |
| 167 | typer( "`r# now = format( Sys.time(), '%Y-%m-%d' ); ''`--- the empty single quotes " ) | |
| 168 | typer( "prevent the date from appearing in the output document. " ) | |
| 169 | ||
| 170 | paragraph() | |
| 171 | typer( "We set the anchor date to `r# annal()`, which was " ) | |
| 172 | typer( "`r# elapsed( 0, days( v$date$anchor, format( Sys.time(), '%Y-%m-%d' ) ) )` " ) | |
| 173 | typer( "ago from `r# format( as.Date( now ), '%B %d, %Y' )`. " ) | |
| 174 | ||
| 175 | # ----------------------------------------------------------------------------- | |
| 176 | # Demo CSV file import | |
| 177 | # ----------------------------------------------------------------------------- | |
| 178 | paragraph() | |
| 179 | heading( "Tabular Data" ) | |
| 180 | typer( "The following table shows average Canadian lifespans by birth " ) | |
| 181 | typer( "year and sex:" ) | |
| 182 | paragraph() | |
| 183 | typer( "`r# csv2md( '../data.csv', total=FALSE )`" ) | |
| 184 | paragraph() | |
| 185 | typer( "Calling `csv2md` converts the comma-separated values in the spreadsheet " ) | |
| 186 | typer( "to a table formatted using Markdown. The HTML preview pane changes the " ) | |
| 187 | typer( "appearance of the resulting table. Using `../data.csv` instructs R to " ) | |
| 188 | typer( "open `data.csv` from one directory above the *working directory*." ) | |
| 189 | ||
| 190 | # ----------------------------------------------------------------------------- | |
| 191 | # Demo HTML export | |
| 192 | # ----------------------------------------------------------------------------- | |
| 193 | paragraph() | |
| 194 | heading( "Export" ) | |
| 195 | typer( "Retrieve the output HTML by using the **Edit > Copy HTML** menu. Let's " ) | |
| 196 | typer( "peek at the output." ) | |
| 197 | wait( 2 ) | |
| 198 | ||
| 199 | type( "e", Key.ALT ) | |
| 200 | wait( 0.5 ) | |
| 201 | down() | |
| 202 | wait( 0.25 ) | |
| 203 | enter() | |
| 204 | wait( 0.25 ) | |
| 205 | ||
| 206 | type( "a", Key.CTRL ) | |
| 207 | wait( 0.25 ) | |
| 208 | type( "v", Key.CTRL ) | |
| 209 | wait( 5 ) | |
| 210 | ||
| 211 | set_typing_speed( 40 ) | |
| 212 | ||
| 213 | # Jump to page bottom (should already be there, but just in case) | |
| 214 | type( Key.END, Key.CTRL ) | |
| 215 | recur( 3, typer, Key.PAGE_UP ) | |
| 216 | type( Key.HOME, Key.CTRL ) | |
| 217 | wait( 3 ) | |
| 218 | ||
| 219 | set_typing_speed( 220 ) | |
| 220 | type( "z", Key.CTRL ) | |
| 221 | type( Key.END, Key.CTRL ) | |
| 222 | ||
| 223 | paragraph() | |
| 224 | typer( "That's all for now, thank you!" ) | |
| 225 | wait( 5 ) | |
| 226 | ||
| 227 | # Delete the anchor date. | |
| 228 | tab() | |
| 229 | end() | |
| 230 | recur( 2, type, Key.UP ) | |
| 231 | delete() | |
| 232 | tab() | |
| 1 | 233 |
| 1 | from sikuli import * | |
| 2 | ||
| 3 | import sys | |
| 4 | import os | |
| 5 | ||
| 6 | def set_class_path(): | |
| 7 | path_script = getBundlePath() | |
| 8 | dir_script = os.path.dirname( path_script ) | |
| 9 | path_lib = dir_script + "/keycast/build/libs/keycast.jar" | |
| 10 | ||
| 11 | sys.path.append( path_lib ) | |
| 12 | ||
| 13 | def launch(): | |
| 14 | from com.whitemagicsoftware.keycast import KeyCast | |
| 15 | kc = KeyCast() | |
| 16 | kc.show() | |
| 17 | ||
| 18 | def main(): | |
| 19 | set_class_path() | |
| 20 | launch() | |
| 21 | ||
| 22 | ||
| 23 | if __name__ == "__main__": | |
| 24 | main() | |
| 1 | 25 |
| 1 | # ----------------------------------------------------------------------------- | |
| 2 | # Copyright 2020 White Magic Software, Ltd. | |
| 3 | # | |
| 4 | # Permission is hereby granted, free of charge, to any person obtaining a | |
| 5 | # copy of this software and associated documentation files (the | |
| 6 | # "Software"), to deal in the Software without restriction, including | |
| 7 | # without limitation the rights to use, copy, modify, merge, publish, | |
| 8 | # distribute, sublicense, and/or sell copies of the Software, and to | |
| 9 | # permit persons to whom the Software is furnished to do so, subject to | |
| 10 | # the following conditions: | |
| 11 | # | |
| 12 | # The above copyright notice and this permission notice shall be included | |
| 13 | # in all copies or substantial portions of the Software. | |
| 14 | # | |
| 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| 16 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| 17 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
| 18 | # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| 19 | # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
| 20 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 21 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 22 | # ----------------------------------------------------------------------------- | |
| 23 | ||
| 24 | # ----------------------------------------------------------------------------- | |
| 25 | # This script contains helper functions used by the other scripts. | |
| 26 | # | |
| 27 | # Do not run this script. | |
| 28 | # ----------------------------------------------------------------------------- | |
| 29 | ||
| 30 | from sikuli import * | |
| 31 | import sys | |
| 32 | import os | |
| 33 | from os.path import expanduser | |
| 34 | ||
| 35 | dir_home = expanduser( "~" ) | |
| 36 | app_home = dir_home + "/bin" | |
| 37 | app_bin = app_home + "/scrivenvar.jar" | |
| 38 | ||
| 39 | wpm_typing_speed = 80 | |
| 40 | ||
| 41 | # ----------------------------------------------------------------------------- | |
| 42 | # Try to delete the file pointed to by the path variable. If there is no such | |
| 43 | # file, this will silently ignore the exception. | |
| 44 | # ----------------------------------------------------------------------------- | |
| 45 | def rm( path ): | |
| 46 | try: | |
| 47 | os.remove( path ) | |
| 48 | except: | |
| 49 | print "Ignored" | |
| 50 | ||
| 51 | # ----------------------------------------------------------------------------- | |
| 52 | # Changes the current typing speed, where speed is given in words per minute. | |
| 53 | # ----------------------------------------------------------------------------- | |
| 54 | def set_typing_speed( wpm ): | |
| 55 | global wpm_typing_speed | |
| 56 | wpm_typing_speed = wpm | |
| 57 | ||
| 58 | # ----------------------------------------------------------------------------- | |
| 59 | # Creates a delay between keystrokes to emulate typing at a particular speed. | |
| 60 | # ----------------------------------------------------------------------------- | |
| 61 | def random_wait(): | |
| 62 | from time import sleep | |
| 63 | from random import uniform | |
| 64 | cpm = wpm_typing_speed * 5.1 | |
| 65 | cps = cpm / 60.0 | |
| 66 | ms_per_char = 1000.0 / cps | |
| 67 | ms_per_stroke = ms_per_char / 2.0 | |
| 68 | ||
| 69 | noise = uniform( 0, ms_per_stroke / 2 ) | |
| 70 | duration = (ms_per_stroke + noise ) / 1000 | |
| 71 | ||
| 72 | sleep( duration ) | |
| 73 | ||
| 74 | # ----------------------------------------------------------------------------- | |
| 75 | # Repeats a function call, f, n times. | |
| 76 | # ----------------------------------------------------------------------------- | |
| 77 | def recur( n, f, *args ): | |
| 78 | for i in range( n ): | |
| 79 | f( *args ) | |
| 80 | random_wait() | |
| 81 | ||
| 82 | # ----------------------------------------------------------------------------- | |
| 83 | # Emulate a typist who is typing in the given text. | |
| 84 | # ----------------------------------------------------------------------------- | |
| 85 | def typer( text ): | |
| 86 | for c in text: | |
| 87 | type( c ) | |
| 88 | random_wait() | |
| 89 | ||
| 90 | # ----------------------------------------------------------------------------- | |
| 91 | # Type a line of text followed by typing the ENTER key. | |
| 92 | # ----------------------------------------------------------------------------- | |
| 93 | def typeln( text ): | |
| 94 | typer( text ) | |
| 95 | enter() | |
| 96 | ||
| 97 | # ----------------------------------------------------------------------------- | |
| 98 | # Injects a definition. | |
| 99 | # ----------------------------------------------------------------------------- | |
| 100 | def autoinsert(): | |
| 101 | type( Key.SPACE, Key.CTRL ) | |
| 102 | random_wait() | |
| 103 | ||
| 104 | # ----------------------------------------------------------------------------- | |
| 105 | # Types the TAB key. | |
| 106 | # ----------------------------------------------------------------------------- | |
| 107 | def tab(): | |
| 108 | typer( Key.TAB ) | |
| 109 | ||
| 110 | # ----------------------------------------------------------------------------- | |
| 111 | # Types the ENTER key. | |
| 112 | # ----------------------------------------------------------------------------- | |
| 113 | def enter(): | |
| 114 | typer( Key.ENTER ) | |
| 115 | ||
| 116 | # ----------------------------------------------------------------------------- | |
| 117 | # Types the ESC key. | |
| 118 | # ----------------------------------------------------------------------------- | |
| 119 | def esc(): | |
| 120 | typer( Key.ESC ) | |
| 121 | ||
| 122 | # ----------------------------------------------------------------------------- | |
| 123 | # Types the DOWN arrow key. | |
| 124 | # ----------------------------------------------------------------------------- | |
| 125 | def down(): | |
| 126 | typer( Key.DOWN ) | |
| 127 | ||
| 128 | # ----------------------------------------------------------------------------- | |
| 129 | # Types the HOME key. | |
| 130 | # ----------------------------------------------------------------------------- | |
| 131 | def home(): | |
| 132 | typer( Key.HOME ) | |
| 133 | ||
| 134 | # ----------------------------------------------------------------------------- | |
| 135 | # Types the END key. | |
| 136 | # ----------------------------------------------------------------------------- | |
| 137 | def end(): | |
| 138 | typer( Key.END ) | |
| 139 | ||
| 140 | # ----------------------------------------------------------------------------- | |
| 141 | # Types the BACKSPACE key. | |
| 142 | # ----------------------------------------------------------------------------- | |
| 143 | def backspace(): | |
| 144 | typer( Key.BACKSPACE ) | |
| 145 | ||
| 146 | # ----------------------------------------------------------------------------- | |
| 147 | # Types the INSERT key, often to insert a new definition. | |
| 148 | # ----------------------------------------------------------------------------- | |
| 149 | def insert(): | |
| 150 | typer( Key.INSERT ) | |
| 151 | ||
| 152 | # ----------------------------------------------------------------------------- | |
| 153 | # Types the DELETE key, often to remove selected text. | |
| 154 | # ----------------------------------------------------------------------------- | |
| 155 | def delete(): | |
| 156 | typer( Key.DELETE ) | |
| 157 | ||
| 158 | # ----------------------------------------------------------------------------- | |
| 159 | # Moves the cursor one word to the right. | |
| 160 | # ----------------------------------------------------------------------------- | |
| 161 | def skip_right(): | |
| 162 | type( Key.RIGHT, Key.CTRL ) | |
| 163 | random_wait() | |
| 164 | ||
| 165 | def select_word_right(): | |
| 166 | type( Key.RIGHT, Key.CTRL + Key.SHIFT ) | |
| 167 | random_wait() | |
| 168 | ||
| 169 | # ----------------------------------------------------------------------------- | |
| 170 | # Types ENTER twice to begin a new paragraph. | |
| 171 | # ----------------------------------------------------------------------------- | |
| 172 | def paragraph(): | |
| 173 | recur( 2, enter ) | |
| 174 | wait( 1.5 ) | |
| 175 | ||
| 176 | # ----------------------------------------------------------------------------- | |
| 177 | # Writes a heading to the document using the given text value as the content. | |
| 178 | # ----------------------------------------------------------------------------- | |
| 179 | def heading( text ): | |
| 180 | typer( "# " + text ) | |
| 181 | paragraph() | |
| 182 | ||
| 183 | # ----------------------------------------------------------------------------- | |
| 184 | # Clicks the "Create" button to add a new definition. | |
| 185 | # ----------------------------------------------------------------------------- | |
| 186 | def click_create(): | |
| 187 | click("1594187923258.png") | |
| 188 | wait( .5 ) | |
| 189 | ||
| 190 | # ----------------------------------------------------------------------------- | |
| 191 | # Changes the text for the actively selected definition. | |
| 192 | # ----------------------------------------------------------------------------- | |
| 193 | def rename_definition( text ): | |
| 194 | typer( Key.F2 ) | |
| 195 | typer( text ) | |
| 196 | enter() | |
| 197 | wait( .5 ) | |
| 198 | ||
| 199 | # ----------------------------------------------------------------------------- | |
| 200 | # Searches for the given text within the document. | |
| 201 | # ----------------------------------------------------------------------------- | |
| 202 | def edit_find( text ): | |
| 203 | type( "f", Key.CTRL ) | |
| 204 | typer( text ) | |
| 205 | enter() | |
| 206 | wait( .25 ) | |
| 207 | esc() | |
| 208 | wait( .5 ) | |
| 209 | ||
| 210 | # ----------------------------------------------------------------------------- | |
| 211 | # Searches for the next occurrence of the previous search term. | |
| 212 | # ----------------------------------------------------------------------------- | |
| 213 | def edit_find_next(): | |
| 214 | typer( Key.F3 ) | |
| 215 | wait( .5 ) | |
| 216 | ||
| 217 | # ----------------------------------------------------------------------------- | |
| 218 | # Opens a dialog for selecting a file. | |
| 219 | # ----------------------------------------------------------------------------- | |
| 220 | def file_open(): | |
| 221 | type( "o", Key.CTRL ) | |
| 222 | wait( 1 ) | |
| 1 | 223 |