| 10 | 10 | .classpath |
| 11 | 11 | .idea |
| 12 | count | |
| 13 | themes | |
| 14 | quotes | |
| 15 | tex | |
| 16 | spell | |
| 12 | count/ | |
| 13 | themes/ | |
| 14 | quotes/ | |
| 15 | tex/ | |
| 16 | spell/ | |
| 17 | 17 | keenwrite.build_artifacts.txt |
| 18 | 18 | todo |
| 19 | tokens | |
| 19 | tokens/ | |
| 20 | 20 |
| 8 | 8 | |
| 9 | 9 | * [JDK 21](https://bell-sw.com/pages/downloads) (Full JDK + JavaFX) |
| 10 | * [Gradle 8.3](https://gradle.org/releases) | |
| 11 | * [Git 2.40.1](https://git-scm.com/downloads) | |
| 10 | * [Gradle 8.5](https://gradle.org/releases) | |
| 11 | * [Git 2.43](https://git-scm.com/downloads) | |
| 12 | 12 | * [warp v0.4.0-alpha](https://github.com/Reisz/warp/releases/tag/v0.4.0) |
| 13 | 13 | |
| ... | ||
| 84 | 84 | ## Binaries |
| 85 | 85 | |
| 86 | Run the `installer` script to build platform-specific binaries, such as: | |
| 86 | Run the `installer.sh` script to build platform-specific binaries, such as: | |
| 87 | 87 | |
| 88 | ./installer -V -o linux | |
| 88 | ./installer.sh -V -o linux | |
| 89 | 89 | |
| 90 | The `installer` script: | |
| 90 | The `installer.sh` script: | |
| 91 | 91 | |
| 92 | 92 | * downloads a JDK; |
| 93 | 93 | * generates a run script; |
| 94 | 94 | * bundles the JDK, run script, and JAR file; and |
| 95 | 95 | * creates a standalone binary, so no installation required. |
| 96 | 96 | |
| 97 | Run `./installer -h` to see all command-line options. | |
| 97 | Run `./installer.sh -h` to see all command-line options. | |
| 98 | 98 | |
| 99 | 99 | # Releases |
| 3 | 3 | <Match> |
| 4 | 4 | <Or> |
| 5 | <Bug code="EI, EI2" /> | |
| 5 | <Bug code="EI, EI2, CT, RV, PI" /> | |
| 6 | 6 | </Or> |
| 7 | 7 | </Match> |
| 9 | 9 | } |
| 10 | 10 | dependencies { |
| 11 | classpath 'org.owasp:dependency-check-gradle:8.4.2' | |
| 12 | classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.1" | |
| 11 | classpath 'org.owasp:dependency-check-gradle:9.0.1' | |
| 12 | classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.4" | |
| 13 | 13 | } |
| 14 | 14 | } |
| 15 | 15 | |
| 16 | 16 | plugins { |
| 17 | 17 | id 'application' |
| 18 | 18 | id 'org.openjfx.javafxplugin' version '0.1.0' |
| 19 | 19 | id 'com.palantir.git-version' version '3.0.0' |
| 20 | id "com.github.spotbugs" version "5.2.1" | |
| 20 | id "com.github.spotbugs" version "5.2.4" | |
| 21 | 21 | } |
| 22 | 22 | |
| ... | ||
| 89 | 89 | |
| 90 | 90 | dependencies { |
| 91 | def v_junit = '5.10.0' | |
| 91 | def v_junit = '5.10.1' | |
| 92 | 92 | def v_flexmark = '0.64.8' |
| 93 | 93 | def v_jackson = '2.15.3' |
| 94 | def v_echosvg = '1.0' | |
| 94 | def v_echosvg = '1.0.1' | |
| 95 | 95 | def v_picocli = '4.7.5' |
| 96 | 96 | |
| 97 | 97 | // JavaFX |
| 98 | implementation 'org.controlsfx:controlsfx:11.1.2' | |
| 98 | implementation 'org.controlsfx:controlsfx:11.2.0' | |
| 99 | 99 | implementation 'org.fxmisc.richtext:richtextfx:0.11.2' |
| 100 | 100 | implementation 'org.fxmisc.flowless:flowless:0.7.2' |
| ... | ||
| 120 | 120 | |
| 121 | 121 | // HTML parsing and rendering |
| 122 | implementation 'org.jsoup:jsoup:1.16.1' | |
| 122 | implementation 'org.jsoup:jsoup:1.17.1' | |
| 123 | 123 | implementation 'org.xhtmlrenderer:flying-saucer-core:9.3.1' |
| 124 | 124 | |
| 125 | 125 | // R |
| 126 | implementation 'org.apache.commons:commons-compress:1.24.0' | |
| 126 | implementation 'org.apache.commons:commons-compress:1.25.0' | |
| 127 | 127 | implementation 'org.codehaus.plexus:plexus-utils:4.0.0' |
| 128 | 128 | implementation 'org.renjin:renjin-script-engine:3.5-beta76' |
| ... | ||
| 137 | 137 | implementation "io.sf.carte:echosvg-gvt:${v_echosvg}" |
| 138 | 138 | implementation "io.sf.carte:echosvg-parser:${v_echosvg}" |
| 139 | implementation "io.sf.carte:echosvg-script:${v_echosvg}" | |
| 140 | 139 | implementation "io.sf.carte:echosvg-svg-dom:${v_echosvg}" |
| 141 | 140 | implementation "io.sf.carte:echosvg-svggen:${v_echosvg}" |
| 142 | 141 | implementation "io.sf.carte:echosvg-transcoder:${v_echosvg}" |
| 143 | 142 | implementation "io.sf.carte:echosvg-util:${v_echosvg}" |
| 144 | 143 | implementation "io.sf.carte:echosvg-xml:${v_echosvg}" |
| 145 | 144 | |
| 146 | 145 | // Misc. |
| 147 | 146 | implementation 'org.ahocorasick:ahocorasick:0.6.3' |
| 148 | implementation 'org.apache.commons:commons-configuration2:2.9.0' | |
| 147 | implementation 'org.apache.commons:commons-lang3:3.14.0' | |
| 149 | 148 | implementation 'com.github.albfernandez:juniversalchardet:2.4.0' |
| 150 | 149 | implementation 'jakarta.validation:jakarta.validation-api:3.0.2' |
| ... | ||
| 171 | 170 | testImplementation 'org.assertj:assertj-core:3.24.2' |
| 172 | 171 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' |
| 172 | } | |
| 173 | ||
| 174 | /** | |
| 175 | * Mozilla's JavaScript engine, Rhino, is used by the SVG rasterizer. | |
| 176 | * The application does not support animated SVG files (the HTML | |
| 177 | * renderer won't honour them), so we can eliminate the dependency from | |
| 178 | * the binaries. | |
| 179 | */ | |
| 180 | configurations { | |
| 181 | all*.exclude group: 'org.mozilla', module: 'rhino' | |
| 173 | 182 | } |
| 174 | 183 | |
| ... | ||
| 251 | 260 | useJUnitPlatform() |
| 252 | 261 | |
| 253 | doFirst { jvmArgs = moduleSecurity } | |
| 262 | doFirst { jvmArgs += moduleSecurity } | |
| 254 | 263 | testLogging { exceptionFormat = 'full' } |
| 264 | } | |
| 265 | ||
| 266 | tasks.withType( JavaCompile ).configureEach { | |
| 267 | options.compilerArgs += "--enable-preview" | |
| 268 | } | |
| 269 | ||
| 270 | tasks.withType( JavaExec ).configureEach { | |
| 271 | jvmArgs += "--enable-preview" | |
| 272 | } | |
| 273 | ||
| 274 | tasks.withType( Test ).configureEach { | |
| 275 | jvmArgs += "--enable-preview" | |
| 255 | 276 | } |
| 256 | 277 | |
| 11 | 11 | * [svg.md](svg.md) -- Resolve issues with some SVG files |
| 12 | 12 | * [metadata.md](metadata.md) -- Document metadata |
| 13 | * [references.md](references.md) -- Captions and cross-references | |
| 13 | 14 | * [typesetting.md](typesetting.md) -- Document typesetting |
| 14 | 15 | * [variables.md](variables.md) -- Variable definitions and interpolation |
| 1 | # Captions and cross-references | |
| 2 | ||
| 3 | Users may define captions and cross-references to tables, figures, | |
| 4 | and equations. Unfortunately, at time of writing, the CommonMark | |
| 5 | specification is frozen. This means cross-references must be implemented | |
| 6 | as an extension to the Markdown specification, leaving the door open for | |
| 7 | differing Markdown rendering libraries and applications to diverge on | |
| 8 | with respect to syntax. | |
| 9 | ||
| 10 | # Syntax | |
| 11 | ||
| 12 | This section describes how to use captions and cross-references within | |
| 13 | Markdown documents. The CommonMark standard details different ways to | |
| 14 | add captions to tables and figures. While those are supported, a more | |
| 15 | consistent syntax is available. | |
| 16 | ||
| 17 | ## Captions | |
| 18 | ||
| 19 | Tables, figures, and equations are captioned using the same syntax. In | |
| 20 | general, a line that starts with a double colon after a blank line will | |
| 21 | result in a caption added to the item immediately proceding it. The | |
| 22 | remainder of this section provides examples. | |
| 23 | ||
| 24 | ### Images | |
| 25 | ||
| 26 | An image caption: | |
| 27 | ||
| 28 | ``` | |
| 29 |  | |
| 30 | ||
| 31 | :: Figure caption text | |
| 32 | ``` | |
| 33 | ||
| 34 | ### Table | |
| 35 | ||
| 36 | A table caption: | |
| 37 | ||
| 38 | ``` | |
| 39 | | a | b | c | | |
| 40 | |---|---|---| | |
| 41 | | 1 | 2 | 3 | | |
| 42 | | 4 | 5 | 6 | | |
| 43 | | 7 | 8 | 9 | | |
| 44 | ||
| 45 | :: Table caption text | |
| 46 | ``` | |
| 47 | ||
| 48 | ### Equation | |
| 49 | ||
| 50 | An equation caption: | |
| 51 | ||
| 52 | ``` | |
| 53 | $$E | mc^2$$ | |
| 54 | ||
| 55 | :: Equation caption | |
| 56 | ``` | |
| 57 | ||
| 58 | # Cross-references | |
| 59 | ||
| 60 | There are two parts to a cross-reference: the anchor name and its references. | |
| 61 | An anchor name must be uniquely defined. Any number of references may refer | |
| 62 | to an anchor name. Anchor names can be associated with any item in the | |
| 63 | document, and are primarily added to captions. | |
| 64 | ||
| 65 | The general syntax for anchor names and references is: | |
| 66 | ||
| 67 | * `{#type-name:label}` (anchor name) | |
| 68 | * `[@type-name:label]` (reference) | |
| 69 | ||
| 70 | The `type-name` can be any alphanumeric value, starting with a letter. | |
| 71 | Type names user-defined categories for the item type. Labels are user-defined | |
| 72 | identifiers that must be unique per item. | |
| 73 | ||
| 74 | Consider the following example: | |
| 75 | ||
| 76 | ``` | |
| 77 | In [@fig:kitten], a cute kitten is shown. | |
| 78 | ||
| 79 |  | |
| 80 | ||
| 81 | :: World's cutest kitten {#fig:kitten} | |
| 82 | ||
| 83 | There is no cuter kitten than the one in [@fig:kitten]. | |
| 84 | ``` | |
| 85 | ||
| 86 | The anchor name uniquely defines where an item in a document is located. Any | |
| 87 | number of references, anywhere in the document, may reference an anchor name. | |
| 88 | There are few restrictions placed on the possible type names and labels. Here | |
| 89 | are a few more anchor name examples: | |
| 90 | ||
| 91 | ``` | |
| 92 | {#fig:cats} | |
| 93 | {#図版:猫} | |
| 94 | {#eq:mass-energy} | |
| 95 | {#eqn:laplace} | |
| 96 | ``` | |
| 97 | ||
| 98 | ## Type names | |
| 99 | ||
| 100 | To avoid duplicating writing the label each time (e.g., Figure, Table, | |
| 101 | Equation), there are a number of predefined labels associated with | |
| 102 | type names. The following table lists the type names and the label | |
| 103 | generated by the typesetting system: | |
| 104 | ||
| 105 | | Type name | English name | |
| 106 | |---|---| | |
| 107 | | algorithm | Algorithm | | |
| 108 | | alg | Algorithm | | |
| 109 | | equation | Equation | | |
| 110 | | eqn | Equation | | |
| 111 | | eq | Equation | | |
| 112 | | figure | Figure | | |
| 113 | | fig | Figure | | |
| 114 | | formula | Formula | | |
| 115 | | listing | Listing | | |
| 116 | | list | Listing | | |
| 117 | | lst | Listing | | |
| 118 | | lyric | Lyrics | | |
| 119 | | music | Score | | |
| 120 | | score | Score | | |
| 121 | | source | Listing | | |
| 122 | | src | Listing | | |
| 123 | | tab | Table | | |
| 124 | | table | Table | | |
| 125 | | tbl | Table | | |
| 126 | ||
| 127 | These values are defined in the theme's `xhtml/xml-references.tex` file. | |
| 128 | ||
| 1 | 129 |
| 62 | 62 | can tweak them to your taste. Accomplish this as follows: |
| 63 | 63 | |
| 64 | 1. Visit the [skin](https://github.com/DaveJarvis/keenwrite/tree/master/src/main/resources/com/keenwrite/skins) repository directory | |
| 64 | 1. Visit the [skin](https://gitlab.com/DaveJarvis/KeenWrite/-/tree/main/src/main/resources/com/keenwrite/skins) repository directory | |
| 65 | 65 | 1. Click one of the files (e.g., `haunted_grey.css`). |
| 66 | 66 | 1. Click **Raw**. |
| 67 | 67 | 1. Copy the entire text. |
| 68 | 68 | 1. Return to `custom.css`. |
| 69 | 69 | 1. Delete the contents. |
| 70 | 70 | 1. Paste the copied text. |
| 71 | 71 | 1. Save the file. |
| 72 | 72 | |
| 73 | 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. | |
| 74 | [markdown.css](https://gitlab.com/DaveJarvis/KeenWrite/-/blob/main/src/main/resources/com/keenwrite/editor/markdown.css), which is also in the repository. | |
| 75 | 75 | |
| 76 | 76 | # Modena |
| ... | ||
| 83 | 83 | # JavaFX CSS |
| 84 | 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 | |
| 85 | The [Java CSS Reference Guide](https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/doc-files/cssref.html) is exhaustive. In addition to showing many | |
| 86 | 86 | differences between JavaFX CSS and W3C CSS, the guide introduces numerous |
| 87 | 87 | helpful functions for manipulating colours and gradients using existing |
| ... | ||
| 97 | 97 | |
| 98 | 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. | |
| 99 | it along. Either open a new issue in the [issue tracker](https://gitlab.com/DaveJarvis/KeenWrite/-/issues) that contains the CSS file or submit a pull request. | |
| 100 | 100 | |
| 101 | 101 | |
| 35 | 35 | 1. Run **install.bat** to download and install the software. |
| 36 | 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>. | |
| 37 | 1. Right-click [localpath.bat](https://gitlab.com/DaveJarvis/KeenWrite/-/raw/main/scripts/localpath.bat). | |
| 38 | 38 | 1. Select **Save Link As** (or similar). |
| 39 | 39 | 1. Save the file to the typesetting software's "root" directory. |
| ... | ||
| 80 | 80 | Install and configure the default theme pack as follows: |
| 81 | 81 | |
| 82 | 1. Download the <a href="https://gitreleases.dev/gh/DaveJarvis/keenwrite-themes/latest/theme-pack.zip">theme-pack.zip</a> archive. | |
| 82 | 1. Download the <a href="https://gitlab.com/DaveJarvis/keenwrite-themes/-/releases/permalink/latest/downloads/theme-pack.zip">theme-pack.zip</a> archive. | |
| 83 | 83 | 1. Extract archive into a known location. |
| 84 | 84 | 1. Start the text editor, if not already running. |
| ... | ||
| 160 | 160 | Here are a few documents that introduce the typesetting system: |
| 161 | 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)) | |
| 162 | * [What is ConTeXt?](https://www.pragma-ade.com/general/manuals/what-is-context.pdf) | |
| 163 | * [A not so short introduction to ConTeXt](https://github.com/contextgarden/not-so-short-introduction-to-context) | |
| 164 | * [Dealing with XML in ConTeXt MKIV](https://pragma-ade.com/general/manuals/xml-mkiv.pdf) | |
| 165 | * [Typographic Programming](https://www.pragma-ade.com/general/manuals/style.pdf) | |
| 166 | 166 | |
| 167 | 167 | The [documentation library](https://wiki.contextgarden.net/Documentation) includes the following gems: |
| 3 | 3 | The application uses the [ConTeXt](https://contextgarden.net) typesetting |
| 4 | 4 | system, the [podman](https://podman.io/) container manager, various |
| 5 | [themes](https://github.com/DaveJarvis/keenwrite-themes/), and numerous | |
| 5 | [themes](https://gitlab.com/DaveJarvis/keenwrite-themes), and numerous | |
| 6 | 6 | fonts to produce high-quality PDF files. The container manager significantly |
| 7 | 7 | reduces the number of manual steps in the installation process. |
| ... | ||
| 15 | 15 | 1. Start the application. |
| 16 | 16 | 1. Click **File → Export As → PDF**. |
| 17 | ||
| 18 | A wizard appears. | |
| 19 | ||
| 20 | ## Windows | |
| 21 | ||
| 22 | ||
| 23 | ## Linux | |
| 24 | ||
| 25 | 17 | |
| 26 | ## macOS | |
| 18 | Follow the steps in the wizard to install the requisite software and | |
| 19 | typesetting themes. | |
| 27 | 20 | |
| 28 | 21 | |
| 20 | 20 | |
| 21 | 21 | readonly OPT_JAVA=$(cat << END_OF_ARGS |
| 22 | -Dprism.order=sw \ | |
| 23 | --enable-preview \ | |
| 22 | 24 | --add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED \ |
| 23 | 25 | --add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED \ |
| 2 | 2 | |
| 3 | 3 | java \ |
| 4 | -Dprism.order=sw \ | |
| 5 | --enable-preview \ | |
| 4 | 6 | --add-opens=javafx.controls/javafx.scene.control=ALL-UNNAMED \ |
| 5 | 7 | --add-opens=javafx.controls/javafx.scene.control.skin=ALL-UNNAMED \ |
| 8 | 8 | readonly RELEASE=$(git describe --abbrev=0 --tags) |
| 9 | 9 | readonly APP_NAME=$(cut -d= -f2 ./src/main/resources/bootstrap.properties) |
| 10 | readonly PATH_TOKEN="tokens/${APP_NAME,,}.pat" | |
| 11 | readonly URL=$(cat tokens/publish.url) | |
| 10 | readonly APP_NAME_LC=${APP_NAME,,} | |
| 11 | readonly PATH_TOKEN="tokens/${APP_NAME_LC}.pat" | |
| 12 | readonly URL=$(cat "tokens/publish.url") | |
| 13 | readonly FILE_VERSION="version.txt" | |
| 12 | 14 | |
| 13 | 15 | # --------------------------------------------------------------------------- |
| 14 | # Publishes a self-extracting installer to the repository. | |
| 16 | # Adds download URLs to a release. | |
| 17 | # | |
| 18 | # $1 - The system (Linux, WIndows, MacOS, Java) | |
| 19 | # --------------------------------------------------------------------------- | |
| 20 | release() { | |
| 21 | local -r OS="${1}" | |
| 22 | local ARCH=" (64-bit, x86)" | |
| 23 | local FILE_PREFIX="${APP_NAME_LC}" | |
| 24 | local FILE_SUFFIX="bin" | |
| 25 | ||
| 26 | case ${OS} in | |
| 27 | MacOS) | |
| 28 | FILE_SUFFIX="app" | |
| 29 | ;; | |
| 30 | Windows) | |
| 31 | FILE_PREFIX="${APP_NAME}" | |
| 32 | FILE_SUFFIX="exe" | |
| 33 | ;; | |
| 34 | Java) | |
| 35 | ARCH="" | |
| 36 | FILE_SUFFIX="jar" | |
| 37 | ;; | |
| 38 | *) | |
| 39 | # Linux, others | |
| 40 | ;; | |
| 41 | esac | |
| 42 | ||
| 43 | local -r BINARY="${FILE_PREFIX}.${FILE_SUFFIX}" | |
| 44 | ||
| 45 | upload "${BINARY}" | |
| 46 | ||
| 47 | glab release upload ${RELEASE} \ | |
| 48 | --assets-links="[{ | |
| 49 | \"name\":\"${APP_NAME} for ${OS}${ARCH}\", | |
| 50 | \"url\":\"https://${APP_NAME_LC}.com/downloads/${BINARY}\", | |
| 51 | \"link_type\":\"other\" | |
| 52 | }]" | |
| 53 | } | |
| 54 | ||
| 55 | # --------------------------------------------------------------------------- | |
| 56 | # Uploads a file to the remote host. | |
| 15 | 57 | # |
| 16 | 58 | # $1 - The relative path to the file to upload. |
| 17 | 59 | # --------------------------------------------------------------------------- |
| 18 | publish() { | |
| 19 | local -r PATH_ARCHIVE="${1}" | |
| 60 | upload() { | |
| 61 | local -r FILENAME="${1}" | |
| 20 | 62 | |
| 21 | if [ -f "${PATH_ARCHIVE}" ]; then | |
| 22 | scp "${PATH_ARCHIVE}" "${URL}" | |
| 63 | if [ -f "${FILENAME}" ]; then | |
| 64 | scp "${FILENAME}" "${URL}" | |
| 23 | 65 | else |
| 24 | echo "Missing ${PATH_ARCHIVE}, continuing." | |
| 66 | echo "Missing ${FILE_BINARY}, continuing." | |
| 25 | 67 | fi |
| 26 | 68 | } |
| 27 | 69 | |
| 28 | 70 | if [ -f "${PATH_TOKEN}" ]; then |
| 29 | 71 | cat "${PATH_TOKEN}" | glab auth login --hostname gitlab.com --stdin |
| 30 | 72 | |
| 31 | publish "${APP_NAME,,}.jar" | |
| 32 | publish "${APP_NAME,,}.bin" | |
| 33 | publish "${APP_NAME,,}.app" | |
| 34 | publish "${APP_NAME}.exe" | |
| 73 | release "Windows" | |
| 74 | release "MacOS" | |
| 75 | release "Linux" | |
| 76 | release "Java" | |
| 77 | ||
| 78 | echo "${RELEASE}" > "${FILE_VERSION}" | |
| 79 | upload "${FILE_VERSION}" | |
| 80 | mv "${FILE_VERSION}" "www/downloads" | |
| 35 | 81 | else |
| 36 | 82 | echo "Create ${PATH_TOKEN} before publishing the release." |
| 24 | 24 | import static com.keenwrite.io.MediaType.TEXT_R_MARKDOWN; |
| 25 | 25 | import static com.keenwrite.processors.ProcessorFactory.createProcessors; |
| 26 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 26 | 27 | import static java.nio.file.Files.readString; |
| 27 | 28 | import static java.nio.file.Files.writeString; |
| ... | ||
| 88 | 89 | // Processors can export binary files. In such cases, processors will |
| 89 | 90 | // return null to prevent further processing. |
| 90 | final var result = | |
| 91 | outputDoc == null ? null : writeString( outputPath, outputDoc ); | |
| 91 | final var result = outputDoc == null | |
| 92 | ? null | |
| 93 | : writeString( outputPath, outputDoc, UTF_8 ); | |
| 92 | 94 | |
| 93 | 95 | future.complete( outputPath ); |
| ... | ||
| 141 | 143 | // directory to scan for files; or there's no extension for globbing. |
| 142 | 144 | if( !concat || parent == null || extension.isBlank() ) { |
| 143 | return readString( inputPath ); | |
| 145 | return readString( inputPath, UTF_8 ); | |
| 144 | 146 | } |
| 145 | 147 | |
| 25 | 25 | |
| 26 | 26 | import static com.keenwrite.constants.Constants.DIAGRAM_SERVER_NAME; |
| 27 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 27 | 28 | |
| 28 | 29 | /** |
| ... | ||
| 293 | 294 | |
| 294 | 295 | private static String read( final Path path ) throws IOException { |
| 295 | return path == null ? "" : Files.readString( path ); | |
| 296 | return path == null ? "" : Files.readString( path, UTF_8 ); | |
| 296 | 297 | } |
| 297 | 298 | |
| 2 | 2 | package com.keenwrite.collections; |
| 3 | 3 | |
| 4 | import com.keenwrite.sigils.PropertyKeyOperator; | |
| 4 | 5 | import com.keenwrite.sigils.SigilKeyOperator; |
| 5 | 6 | |
| 6 | 7 | import java.io.Serial; |
| 7 | 8 | import java.util.HashMap; |
| 8 | 9 | import java.util.Map; |
| 9 | 10 | import java.util.Objects; |
| 10 | 11 | import java.util.concurrent.ConcurrentHashMap; |
| 12 | ||
| 13 | import static java.util.regex.Matcher.quoteReplacement; | |
| 11 | 14 | |
| 12 | 15 | /** |
| ... | ||
| 27 | 30 | |
| 28 | 31 | private transient final SigilKeyOperator mOperator; |
| 32 | ||
| 33 | /** | |
| 34 | * Creates a new interpolating map using the {@link PropertyKeyOperator}. | |
| 35 | */ | |
| 36 | public InterpolatingMap() { | |
| 37 | this( new PropertyKeyOperator() ); | |
| 38 | } | |
| 29 | 39 | |
| 30 | 40 | /** |
| ... | ||
| 73 | 83 | * @return The given value with all embedded key references interpolated. |
| 74 | 84 | */ |
| 75 | public String interpolate( String value ) { | |
| 85 | public String interpolate( final String value ) { | |
| 76 | 86 | assert value != null; |
| 77 | 87 | |
| 78 | 88 | final var matcher = mOperator.match( value ); |
| 89 | final var sb = new StringBuilder( value.length() << 1 ); | |
| 79 | 90 | |
| 80 | 91 | while( matcher.find() ) { |
| 81 | 92 | final var keyName = matcher.group( GROUP_DELIMITED ); |
| 82 | 93 | final var mapValue = get( keyName ); |
| 83 | 94 | |
| 84 | 95 | if( mapValue != null ) { |
| 85 | 96 | final var keyValue = interpolate( mapValue ); |
| 86 | value = value.replace( mOperator.apply( keyName ), keyValue ); | |
| 97 | matcher.appendReplacement( sb, quoteReplacement( keyValue ) ); | |
| 87 | 98 | } |
| 88 | 99 | } |
| 89 | 100 | |
| 90 | return value; | |
| 101 | matcher.appendTail( sb ); | |
| 102 | return sb.toString(); | |
| 91 | 103 | } |
| 92 | 104 | |
| 93 | 105 | @Override |
| 94 | 106 | public boolean equals( final Object o ) { |
| 95 | if( this == o ) { return true; } | |
| 96 | if( o == null || getClass() != o.getClass() ) { return false; } | |
| 97 | if( !super.equals( o ) ) { return false; } | |
| 107 | if( this == o ) { | |
| 108 | return true; | |
| 109 | } | |
| 110 | ||
| 111 | if( o == null || getClass() != o.getClass() ) { | |
| 112 | return false; | |
| 113 | } | |
| 114 | ||
| 115 | if( !super.equals( o ) ) { | |
| 116 | return false; | |
| 117 | } | |
| 118 | ||
| 98 | 119 | final InterpolatingMap that = (InterpolatingMap) o; |
| 99 | 120 | return Objects.equals( mOperator, that.mOperator ); |
| 17 | 17 | import static com.keenwrite.util.FileWalker.walk; |
| 18 | 18 | import static java.lang.System.lineSeparator; |
| 19 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 19 | 20 | import static java.nio.file.Files.readString; |
| 20 | 21 | |
| ... | ||
| 63 | 64 | clue( "Main.status.export.concat", file ); |
| 64 | 65 | |
| 65 | text.append( readString( file ) ) | |
| 66 | text.append( readString( file, UTF_8 ) ) | |
| 66 | 67 | .append( eol ); |
| 67 | 68 | } |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.config; | |
| 6 | ||
| 7 | import com.keenwrite.collections.InterpolatingMap; | |
| 8 | ||
| 9 | import java.io.IOException; | |
| 10 | import java.io.Reader; | |
| 11 | import java.util.*; | |
| 12 | ||
| 13 | import static java.util.Arrays.*; | |
| 14 | ||
| 15 | /** | |
| 16 | * Responsible for reading and interpolating properties files. | |
| 17 | */ | |
| 18 | public class PropertiesConfiguration { | |
| 19 | private static final String VALUE_SEPARATOR = ","; | |
| 20 | ||
| 21 | private final InterpolatingMap mMap = new InterpolatingMap(); | |
| 22 | ||
| 23 | public PropertiesConfiguration() {} | |
| 24 | ||
| 25 | public void read( final Reader reader ) throws IOException { | |
| 26 | final var properties = new Properties(); | |
| 27 | properties.load( reader ); | |
| 28 | ||
| 29 | for( final var name : properties.stringPropertyNames() ) { | |
| 30 | mMap.put( name, properties.getProperty( name ) ); | |
| 31 | } | |
| 32 | ||
| 33 | mMap.interpolate(); | |
| 34 | } | |
| 35 | ||
| 36 | /** | |
| 37 | * Returns the value of a string property. | |
| 38 | * | |
| 39 | * @param property The property key. | |
| 40 | * @param defaultValue The value to return if no property key has been set. | |
| 41 | * @return The property key value, or defaultValue when no key found. | |
| 42 | */ | |
| 43 | public String getString( final String property, final String defaultValue ) { | |
| 44 | assert property != null; | |
| 45 | ||
| 46 | return mMap.getOrDefault( property, defaultValue ); | |
| 47 | } | |
| 48 | ||
| 49 | /** | |
| 50 | * Returns the value of a string property. | |
| 51 | * | |
| 52 | * @param property The property key. | |
| 53 | * @param defaultValue The value to return if no property key has been set. | |
| 54 | * @return The property key value, or defaultValue when no key found. | |
| 55 | */ | |
| 56 | public int getInt( final String property, final int defaultValue ) { | |
| 57 | assert property != null; | |
| 58 | ||
| 59 | return parse( mMap.get( property ), defaultValue ); | |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Convert the generic list of property objects into strings. | |
| 64 | * | |
| 65 | * @param property The property value to coerce. | |
| 66 | * @param defaults The values to use should the property be unset. | |
| 67 | * @return The list of properties coerced from objects to strings. | |
| 68 | */ | |
| 69 | public List<String> getList( | |
| 70 | final String property, final List<String> defaults ) { | |
| 71 | assert property != null; | |
| 72 | ||
| 73 | final var value = mMap.get( property ); | |
| 74 | ||
| 75 | return value == null | |
| 76 | ? defaults | |
| 77 | : asList( value.split( VALUE_SEPARATOR ) ); | |
| 78 | } | |
| 79 | ||
| 80 | /** | |
| 81 | * Returns a list of property names that begin with the given prefix. | |
| 82 | * Note that the prefix must be separated from other values with a | |
| 83 | * period. | |
| 84 | * | |
| 85 | * @param prefix The prefix to compare against each property name. When | |
| 86 | * comparing, the prefix value will have a period appended. | |
| 87 | * @return The list of property names that have the given prefix. | |
| 88 | */ | |
| 89 | public Iterator<String> getKeys( final String prefix ) { | |
| 90 | assert prefix != null; | |
| 91 | ||
| 92 | final var result = new HashMap<String, String>(); | |
| 93 | final var prefixDotted = prefix + '.'; | |
| 94 | ||
| 95 | for( final var entry : mMap.entrySet() ) { | |
| 96 | final var key = entry.getKey(); | |
| 97 | ||
| 98 | if( key.startsWith( prefixDotted ) ) { | |
| 99 | final var value = entry.getValue(); | |
| 100 | result.put( key, value ); | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | return result.keySet().iterator(); | |
| 105 | } | |
| 106 | ||
| 107 | private static int parse( final String s, final int defaultValue ) { | |
| 108 | try { | |
| 109 | return s == null || s.isBlank() ? defaultValue : Integer.parseInt( s ); | |
| 110 | } catch( final NumberFormatException e ) { | |
| 111 | return defaultValue; | |
| 112 | } | |
| 113 | } | |
| 114 | } | |
| 1 | 115 |
| 12 | 12 | import com.keenwrite.preferences.LocaleProperty; |
| 13 | 13 | import com.keenwrite.preferences.Workspace; |
| 14 | import com.keenwrite.processors.markdown.extensions.CaretExtension; | |
| 14 | import com.keenwrite.processors.markdown.extensions.caret.CaretExtension; | |
| 15 | 15 | import javafx.beans.binding.Bindings; |
| 16 | 16 | import javafx.beans.property.*; |
| ... | ||
| 122 | 122 | } |
| 123 | 123 | |
| 124 | @SuppressWarnings( "unused" ) | |
| 124 | 125 | private void initTextArea( final StyleClassedTextArea textArea ) { |
| 125 | 126 | textArea.setShowCaret( ON ); |
| ... | ||
| 149 | 150 | } |
| 150 | 151 | |
| 152 | @SuppressWarnings( "unused" ) | |
| 151 | 153 | private void initStyle( final StyleClassedTextArea textArea ) { |
| 152 | 154 | textArea.getStyleClass().add( "markdown" ); |
| 51 | 51 | * @return A lossless, compressed sequence of bytes. |
| 52 | 52 | */ |
| 53 | private static byte[] compress( byte[] source ) { | |
| 53 | private static byte[] compress( final byte[] source ) { | |
| 54 | 54 | final var deflater = new Deflater(); |
| 55 | 55 | deflater.setInput( source ); |
| 182 | 182 | if( !box.getStyle().isInline() ) { |
| 183 | 183 | final var margin = box.getMargin( getLayoutContext() ); |
| 184 | y += margin.top(); | |
| 185 | x += margin.left(); | |
| 184 | y += (int) margin.top(); | |
| 185 | x += (int) margin.left(); | |
| 186 | 186 | } |
| 187 | 187 |
| 119 | 119 | mScrollLockButton.setMargin( new Insets( 1, 0, 0, 0 ) ); |
| 120 | 120 | mScrollLockButton.addActionListener( |
| 121 | e -> fireScrollLockEvent( !mScrollLocked ) | |
| 121 | _ -> fireScrollLockEvent( !mScrollLocked ) | |
| 122 | 122 | ); |
| 123 | 123 |
| 9 | 9 | import static com.keenwrite.io.SysFile.normalize; |
| 10 | 10 | import static com.keenwrite.typesetting.Typesetter.Mutator; |
| 11 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 11 | 12 | import static java.nio.file.Files.deleteIfExists; |
| 12 | 13 | import static java.nio.file.Files.writeString; |
| ... | ||
| 43 | 44 | |
| 44 | 45 | final var document = TEXT_XML.createTempFile( APP_TITLE_ABBR, parent ); |
| 45 | final var sourcePath = writeString( document, xhtml ); | |
| 46 | final var sourcePath = writeString( document, xhtml, UTF_8 ); | |
| 46 | 47 | clue( "Main.status.typeset.setting", "source", sourcePath ); |
| 47 | 48 | |
| 130 | 130 | private static Processor<String> createPdfProcessor( |
| 131 | 131 | final ProcessorContext context ) { |
| 132 | final var pdfp = new PdfProcessor( context ); | |
| 133 | return createXhtmlProcessor( pdfp, context ); | |
| 132 | final var pdfProcessor = new PdfProcessor( context ); | |
| 133 | return createXhtmlProcessor( pdfProcessor, context ); | |
| 134 | 134 | } |
| 135 | 135 |
| 6 | 6 | import com.keenwrite.processors.Processor; |
| 7 | 7 | import com.keenwrite.processors.ProcessorContext; |
| 8 | import com.keenwrite.processors.markdown.extensions.captions.CaptionExtension; | |
| 8 | 9 | import com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension; |
| 9 | 10 | import com.keenwrite.processors.markdown.extensions.r.RInlineExtension; |
| 11 | import com.keenwrite.processors.markdown.extensions.references.CrossReferenceExtension; | |
| 10 | 12 | import com.vladsch.flexmark.ext.definition.DefinitionExtension; |
| 11 | 13 | import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension; |
| ... | ||
| 67 | 69 | extensions.add( TablesExtension.create() ); |
| 68 | 70 | extensions.add( FencedDivExtension.create() ); |
| 71 | extensions.add( CrossReferenceExtension.create() ); | |
| 72 | extensions.add( CaptionExtension.create() ); | |
| 69 | 73 | |
| 70 | 74 | return extensions; |
| 10 | 10 | import com.keenwrite.processors.ProcessorContext; |
| 11 | 11 | import com.keenwrite.processors.VariableProcessor; |
| 12 | import com.keenwrite.processors.markdown.extensions.CaretExtension; | |
| 13 | import com.keenwrite.processors.markdown.extensions.DocumentOutlineExtension; | |
| 14 | import com.keenwrite.processors.markdown.extensions.ImageLinkExtension; | |
| 12 | import com.keenwrite.processors.markdown.extensions.caret.CaretExtension; | |
| 15 | 13 | import com.keenwrite.processors.markdown.extensions.fences.FencedBlockExtension; |
| 14 | import com.keenwrite.processors.markdown.extensions.images.ImageLinkExtension; | |
| 15 | import com.keenwrite.processors.markdown.extensions.outline.DocumentOutlineExtension; | |
| 16 | 16 | import com.keenwrite.processors.markdown.extensions.r.RInlineExtension; |
| 17 | import com.keenwrite.processors.markdown.extensions.tex.TeXExtension; | |
| 17 | import com.keenwrite.processors.markdown.extensions.tex.TexExtension; | |
| 18 | 18 | import com.keenwrite.processors.r.RInlineEvaluator; |
| 19 | 19 | import com.keenwrite.processors.r.RVariableProcessor; |
| ... | ||
| 83 | 83 | |
| 84 | 84 | result.add( ImageLinkExtension.create( context ) ); |
| 85 | result.add( TeXExtension.create( evaluator, context ) ); | |
| 85 | result.add( TexExtension.create( evaluator, context ) ); | |
| 86 | 86 | result.add( FencedBlockExtension.create( processor, evaluator, context ) ); |
| 87 | 87 | |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions; | |
| 3 | ||
| 4 | import com.keenwrite.editors.common.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 java.util.function.Supplier; | |
| 19 | ||
| 20 | import static com.keenwrite.constants.Constants.CARET_ID; | |
| 21 | import static com.keenwrite.processors.markdown.extensions.EmptyNode.EMPTY_NODE; | |
| 22 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 23 | ||
| 24 | /** | |
| 25 | * Responsible for giving most block-level elements a unique identifier | |
| 26 | * attribute. The identifier is used to coordinate scrolling. | |
| 27 | */ | |
| 28 | public class CaretExtension extends HtmlRendererAdapter { | |
| 29 | ||
| 30 | private final Supplier<Caret> mCaret; | |
| 31 | ||
| 32 | private CaretExtension( final ProcessorContext context ) { | |
| 33 | mCaret = context.getCaret(); | |
| 34 | } | |
| 35 | ||
| 36 | public static CaretExtension create( final ProcessorContext context ) { | |
| 37 | return new CaretExtension( context ); | |
| 38 | } | |
| 39 | ||
| 40 | @Override | |
| 41 | public void extend( @NotNull final Builder builder, | |
| 42 | @NotNull final String rendererType ) { | |
| 43 | builder.attributeProviderFactory( | |
| 44 | IdAttributeProvider.createFactory( mCaret ) ); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Responsible for creating the id attribute. This class is instantiated | |
| 49 | * once: for the HTML element containing the {@link Constants#CARET_ID}. | |
| 50 | */ | |
| 51 | public static class IdAttributeProvider implements AttributeProvider { | |
| 52 | private final Supplier<Caret> mCaret; | |
| 53 | private boolean mAdded; | |
| 54 | ||
| 55 | public IdAttributeProvider( final Supplier<Caret> caret ) { | |
| 56 | mCaret = caret; | |
| 57 | } | |
| 58 | ||
| 59 | private static AttributeProviderFactory createFactory( | |
| 60 | final Supplier<Caret> caret ) { | |
| 61 | return new IndependentAttributeProviderFactory() { | |
| 62 | @Override | |
| 63 | public @NotNull AttributeProvider apply( | |
| 64 | @NotNull final LinkResolverContext context ) { | |
| 65 | return new IdAttributeProvider( caret ); | |
| 66 | } | |
| 67 | }; | |
| 68 | } | |
| 69 | ||
| 70 | @Override | |
| 71 | public void setAttributes( @NotNull Node curr, | |
| 72 | @NotNull AttributablePart part, | |
| 73 | @NotNull MutableAttributes attributes ) { | |
| 74 | // Optimization: if a caret is inserted, don't try to find another. | |
| 75 | if( mAdded ) { | |
| 76 | return; | |
| 77 | } | |
| 78 | ||
| 79 | final var caret = mCaret.get(); | |
| 80 | ||
| 81 | // If a table block has been earmarked with an empty node, it means | |
| 82 | // another extension has generated code from an external source. The | |
| 83 | // Markdown processor won't be able to determine the caret position | |
| 84 | // with any semblance of accuracy, so skip the element. This usually | |
| 85 | // happens with tables, but in theory any Markdown generated from an | |
| 86 | // external source (e.g., an R script) could produce text that has no | |
| 87 | // caret position that can be calculated. | |
| 88 | var table = curr; | |
| 89 | ||
| 90 | if( !(curr instanceof TableBlock) ) { | |
| 91 | table = curr.getAncestorOfType( TableBlock.class ); | |
| 92 | } | |
| 93 | ||
| 94 | // The table was generated outside the document | |
| 95 | if( table != null && table.getLastChild() == EMPTY_NODE ) { | |
| 96 | return; | |
| 97 | } | |
| 98 | ||
| 99 | final var outside = caret.isAfterText() ? 1 : 0; | |
| 100 | final var began = curr.getStartOffset(); | |
| 101 | final var ended = curr.getEndOffset() + outside; | |
| 102 | final var prev = curr.getPrevious(); | |
| 103 | ||
| 104 | // If the caret is within the bounds of the current node or the | |
| 105 | // caret is within the bounds of the end of the previous node and | |
| 106 | // the start of the current node, then mark the current node with | |
| 107 | // a caret indicator. | |
| 108 | if( caret.isBetweenText( began, ended ) || | |
| 109 | prev != null && caret.isBetweenText( prev.getEndOffset(), began ) ) { | |
| 110 | // This line empowers synchronizing the text editor with the preview. | |
| 111 | attributes.addValue( AttributeImpl.of( "id", CARET_ID ) ); | |
| 112 | ||
| 113 | // We're done until the user moves the caret (micro-optimization) | |
| 114 | mAdded = true; | |
| 115 | } | |
| 116 | } | |
| 117 | } | |
| 118 | } | |
| 119 | 1 |
| 1 | package com.keenwrite.processors.markdown.extensions; | |
| 2 | ||
| 3 | import com.keenwrite.events.ParseHeadingEvent; | |
| 4 | import com.keenwrite.processors.Processor; | |
| 5 | import com.vladsch.flexmark.ast.Heading; | |
| 6 | import com.vladsch.flexmark.parser.Parser.Builder; | |
| 7 | import com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 8 | import com.vladsch.flexmark.parser.block.NodePostProcessor; | |
| 9 | import com.vladsch.flexmark.parser.block.NodePostProcessorFactory; | |
| 10 | import com.vladsch.flexmark.util.ast.Document; | |
| 11 | import com.vladsch.flexmark.util.ast.Node; | |
| 12 | import com.vladsch.flexmark.util.ast.NodeTracker; | |
| 13 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 14 | import org.jetbrains.annotations.NotNull; | |
| 15 | ||
| 16 | import java.util.regex.Pattern; | |
| 17 | ||
| 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 | ParseHeadingEvent.fire( 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 | } | |
| 72 | 1 |
| 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 | } | |
| 27 | 1 |
| 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 |
| 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.processors.ProcessorContext; | |
| 6 | import com.vladsch.flexmark.ast.Image; | |
| 7 | import com.vladsch.flexmark.html.IndependentLinkResolverFactory; | |
| 8 | import com.vladsch.flexmark.html.LinkResolver; | |
| 9 | import com.vladsch.flexmark.html.renderer.LinkResolverBasicContext; | |
| 10 | import com.vladsch.flexmark.html.renderer.ResolvedLink; | |
| 11 | import com.vladsch.flexmark.util.ast.Node; | |
| 12 | import org.jetbrains.annotations.NotNull; | |
| 13 | ||
| 14 | import java.io.File; | |
| 15 | import java.nio.file.Path; | |
| 16 | import java.util.Optional; | |
| 17 | ||
| 18 | import static com.keenwrite.events.StatusEvent.clue; | |
| 19 | import static com.keenwrite.io.SysFile.toFile; | |
| 20 | import static com.keenwrite.util.ProtocolScheme.getProtocol; | |
| 21 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 22 | import static com.vladsch.flexmark.html.renderer.LinkStatus.VALID; | |
| 23 | ||
| 24 | /** | |
| 25 | * Responsible for ensuring that images can be rendered relative to a path. | |
| 26 | * This allows images to be located virtually anywhere. | |
| 27 | */ | |
| 28 | public class ImageLinkExtension extends HtmlRendererAdapter { | |
| 29 | ||
| 30 | private final ProcessorContext mContext; | |
| 31 | ||
| 32 | private ImageLinkExtension( @NotNull final ProcessorContext context ) { | |
| 33 | mContext = context; | |
| 34 | } | |
| 35 | ||
| 36 | /** | |
| 37 | * Creates an extension capable of using a relative path to embed images. | |
| 38 | * | |
| 39 | * @param context Contains the base directory to search in for images. | |
| 40 | * @return The new {@link ImageLinkExtension}, not {@code null}. | |
| 41 | */ | |
| 42 | public static ImageLinkExtension create( | |
| 43 | @NotNull final ProcessorContext context ) { | |
| 44 | return new ImageLinkExtension( context ); | |
| 45 | } | |
| 46 | ||
| 47 | @Override | |
| 48 | public void extend( @NotNull final Builder builder, | |
| 49 | @NotNull final String rendererType ) { | |
| 50 | builder.linkResolverFactory( new ResolverFactory() ); | |
| 51 | } | |
| 52 | ||
| 53 | private final class ResolverFactory extends IndependentLinkResolverFactory { | |
| 54 | @Override | |
| 55 | public @NotNull LinkResolver apply( | |
| 56 | @NotNull final LinkResolverBasicContext context ) { | |
| 57 | return new ImageLinkResolver(); | |
| 58 | } | |
| 59 | } | |
| 60 | ||
| 61 | private class ImageLinkResolver implements LinkResolver { | |
| 62 | public ImageLinkResolver() { | |
| 63 | } | |
| 64 | ||
| 65 | @NotNull | |
| 66 | @Override | |
| 67 | public ResolvedLink resolveLink( | |
| 68 | @NotNull final Node node, | |
| 69 | @NotNull final LinkResolverBasicContext context, | |
| 70 | @NotNull final ResolvedLink link ) { | |
| 71 | return node instanceof Image ? forImage( link, node ) : link; | |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Algorithm: | |
| 76 | * <ol> | |
| 77 | * <li>Accept remote URLs as valid links.</li> | |
| 78 | * <li>Accept existing readable files as valid links.</li> | |
| 79 | * <li>Accept non-{@link ExportFormat#NONE} exports as valid links.</li> | |
| 80 | * <li>Append the images dir to the edited file's dir (baseDir).</li> | |
| 81 | * <li>Search for images by extension.</li> | |
| 82 | * </ol> | |
| 83 | * | |
| 84 | * @param link The link URL to resolve. | |
| 85 | * @param node The document node containing the URL. | |
| 86 | * @return The {@link ResolvedLink} instance used to render the link. | |
| 87 | */ | |
| 88 | private ResolvedLink forImage( final ResolvedLink link, final Node node ) { | |
| 89 | final var url = link.getUrl(); | |
| 90 | final var protocolScheme = getProtocol( url ); | |
| 91 | ||
| 92 | return protocolScheme.isRemote() | |
| 93 | ? valid( link, url ) | |
| 94 | : resolveImageFile( link, node, url ); | |
| 95 | } | |
| 96 | ||
| 97 | private ResolvedLink resolveImageFile( | |
| 98 | final ResolvedLink link, | |
| 99 | final Node node, | |
| 100 | final String url ) { | |
| 101 | final var userPath = new File( url ); | |
| 102 | ||
| 103 | // If the user specified a fully qualified path name, use it verbatim. | |
| 104 | return readable( userPath ) | |
| 105 | ? valid( link, url ) | |
| 106 | : resolveUnqualifiedImageFile( link, node, url ); | |
| 107 | } | |
| 108 | ||
| 109 | private ResolvedLink resolveUnqualifiedImageFile( | |
| 110 | final ResolvedLink link, | |
| 111 | final Node node, | |
| 112 | final String url ) { | |
| 113 | final var baseDir = getBaseDir(); | |
| 114 | final var fqfn = baseDir.resolve( Path.of( url ) ); | |
| 115 | ||
| 116 | // If the image can be found relative to the base directory, then | |
| 117 | // use the link as is when resolving the path. | |
| 118 | return readable( toFile( fqfn ) ) | |
| 119 | ? valid( link, url ) | |
| 120 | : resolveExtensionlessImageFile( link, node, url ); | |
| 121 | } | |
| 122 | ||
| 123 | private ResolvedLink resolveExtensionlessImageFile( | |
| 124 | final ResolvedLink link, | |
| 125 | final Node node, | |
| 126 | final String url | |
| 127 | ) { | |
| 128 | final var imagePath = new File( url ); | |
| 129 | final var file = resolveImageExtension( imagePath ); | |
| 130 | ||
| 131 | return file.isPresent() && readable( file.get() ) | |
| 132 | ? valid( link, file.get().toString() ) | |
| 133 | : resolveRelativeImageFile( link, node, url ); | |
| 134 | } | |
| 135 | ||
| 136 | private ResolvedLink resolveRelativeImageFile( | |
| 137 | final ResolvedLink link, | |
| 138 | final Node node, | |
| 139 | final String url ) { | |
| 140 | final var baseDir = getBaseDir(); | |
| 141 | ||
| 142 | try { | |
| 143 | // Compute the path to the image file. The base directory should | |
| 144 | // be an absolute path to the file being edited, without an extension. | |
| 145 | final var imagesDir = getImageDir(); | |
| 146 | final var baseImagesDir = baseDir.resolve( imagesDir ); | |
| 147 | final var imagePath = baseImagesDir.resolve( url ); | |
| 148 | final var file = resolveImageExtension( toFile( imagePath ) ); | |
| 149 | ||
| 150 | if( file.isPresent() ) { | |
| 151 | final var resolved = imagesDir.resolve( file.get().toPath() ); | |
| 152 | final var relative = baseDir.relativize( resolved ); | |
| 153 | return valid( link, relative.toString() ); | |
| 154 | } | |
| 155 | ||
| 156 | clue( "Main.status.error.file.missing.near", | |
| 157 | imagePath + ".*", node.getLineNumber() | |
| 158 | ); | |
| 159 | } catch( final Exception ex ) { | |
| 160 | clue( ex ); | |
| 161 | } | |
| 162 | ||
| 163 | return link; | |
| 164 | } | |
| 165 | ||
| 166 | private Optional<File> resolveImageExtension( final File imagePath ) { | |
| 167 | for( final var ext : getImageOrder() ) { | |
| 168 | final var file = new File( imagePath.toString() + '.' + ext ); | |
| 169 | ||
| 170 | if( readable( file ) ) { | |
| 171 | return Optional.of( file ); | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | return Optional.empty(); | |
| 176 | } | |
| 177 | ||
| 178 | private ResolvedLink valid( final ResolvedLink link, final String url ) { | |
| 179 | return link.withStatus( VALID ).withUrl( url ); | |
| 180 | } | |
| 181 | ||
| 182 | private Path getImageDir() { | |
| 183 | return mContext.getImageDir(); | |
| 184 | } | |
| 185 | ||
| 186 | private Iterable<String> getImageOrder() { | |
| 187 | return mContext.getImageOrder(); | |
| 188 | } | |
| 189 | ||
| 190 | private Path getBaseDir() { | |
| 191 | return mContext.getBaseDir(); | |
| 192 | } | |
| 193 | } | |
| 194 | ||
| 195 | private static boolean readable( final File file ) { | |
| 196 | return file.isFile() && file.canRead(); | |
| 197 | } | |
| 198 | } | |
| 199 | 1 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 8 | import com.vladsch.flexmark.parser.InlineParser; | |
| 9 | import com.vladsch.flexmark.util.ast.Block; | |
| 10 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 11 | import org.jetbrains.annotations.NotNull; | |
| 12 | ||
| 13 | /** | |
| 14 | * Responsible for retaining the text node and all child nodes with respect | |
| 15 | * to a caption. The caption can be associated with most items, such as | |
| 16 | * block quotes, tables, math expressions, and images. | |
| 17 | */ | |
| 18 | class CaptionBlock extends Block { | |
| 19 | private final BasedSequence mCaption; | |
| 20 | ||
| 21 | CaptionBlock( final BasedSequence caption ) { | |
| 22 | assert caption != null; | |
| 23 | ||
| 24 | mCaption = caption; | |
| 25 | } | |
| 26 | ||
| 27 | /** | |
| 28 | * Opens the caption. | |
| 29 | * | |
| 30 | * @param writer Where to write the opening tags. | |
| 31 | */ | |
| 32 | void opening( final HtmlWriter writer ) { | |
| 33 | writer.raw( "<span class=\"caption\">" ); | |
| 34 | } | |
| 35 | ||
| 36 | /** | |
| 37 | * Closes the caption. | |
| 38 | * | |
| 39 | * @param writer Where to write the closing tags. | |
| 40 | */ | |
| 41 | void closing( final HtmlWriter writer ) { | |
| 42 | writer.raw( "</span>" ); | |
| 43 | } | |
| 44 | ||
| 45 | void parse( final InlineParser inlineParser ) { | |
| 46 | assert inlineParser != null; | |
| 47 | ||
| 48 | inlineParser.parse( mCaption, this ); | |
| 49 | } | |
| 50 | ||
| 51 | @NotNull | |
| 52 | @Override | |
| 53 | public BasedSequence[] getSegments() { | |
| 54 | return BasedSequence.EMPTY_SEGMENTS; | |
| 55 | } | |
| 56 | } | |
| 1 | 57 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.parser.block.AbstractBlockParserFactory; | |
| 8 | import com.vladsch.flexmark.parser.block.BlockStart; | |
| 9 | import com.vladsch.flexmark.parser.block.MatchedBlockParser; | |
| 10 | import com.vladsch.flexmark.parser.block.ParserState; | |
| 11 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 12 | ||
| 13 | class CaptionBlockParserFactory extends AbstractBlockParserFactory { | |
| 14 | CaptionBlockParserFactory( final DataHolder options ) { | |
| 15 | super( options ); | |
| 16 | } | |
| 17 | ||
| 18 | @Override | |
| 19 | public BlockStart tryStart( | |
| 20 | final ParserState state, | |
| 21 | final MatchedBlockParser matchedBlockParser ) { | |
| 22 | ||
| 23 | final var flush = state.getIndent() == 0; | |
| 24 | final var index = state.getNextNonSpaceIndex(); | |
| 25 | final var line = state.getLine(); | |
| 26 | final var length = line.length(); | |
| 27 | final var text = line.subSequence( index, length ); | |
| 28 | ||
| 29 | return flush && CaptionParser.canParse( text ) | |
| 30 | ? BlockStart.of( new CaptionParser( text ) ).atIndex( length ) | |
| 31 | : BlockStart.none(); | |
| 32 | } | |
| 33 | } | |
| 1 | 34 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.common.MarkdownCustomBlockParserFactory; | |
| 8 | import com.vladsch.flexmark.parser.block.BlockParserFactory; | |
| 9 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 10 | ||
| 11 | class CaptionCustomBlockParserFactory extends MarkdownCustomBlockParserFactory { | |
| 12 | CaptionCustomBlockParserFactory() {} | |
| 13 | ||
| 14 | @Override | |
| 15 | public BlockParserFactory createBlockParserFactory( | |
| 16 | final DataHolder options ) { | |
| 17 | return new CaptionBlockParserFactory( options ); | |
| 18 | } | |
| 19 | } | |
| 1 | 20 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.common.MarkdownRendererExtension; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 9 | import com.vladsch.flexmark.parser.Parser.Builder; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for parsing and rendering {@link CaptionBlock} instances. | |
| 13 | */ | |
| 14 | public final class CaptionExtension extends MarkdownRendererExtension { | |
| 15 | /** | |
| 16 | * Use {@link #create()}. | |
| 17 | */ | |
| 18 | private CaptionExtension() {} | |
| 19 | ||
| 20 | /** | |
| 21 | * Returns a new {@link CaptionExtension}. | |
| 22 | * | |
| 23 | * @return An extension capable of parsing caption syntax. | |
| 24 | */ | |
| 25 | public static CaptionExtension create() { | |
| 26 | return new CaptionExtension(); | |
| 27 | } | |
| 28 | ||
| 29 | @Override | |
| 30 | public void extend( final Builder builder ) { | |
| 31 | builder.customBlockParserFactory( new CaptionCustomBlockParserFactory() ); | |
| 32 | builder.postProcessorFactory( new CaptionPostProcessorFactory() ); | |
| 33 | } | |
| 34 | ||
| 35 | @Override | |
| 36 | protected NodeRendererFactory createNodeRendererFactory() { | |
| 37 | return new CaptionNodeRendererFactory(); | |
| 38 | } | |
| 39 | } | |
| 1 | 40 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.references.CrossReferenceNode; | |
| 8 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 9 | import com.vladsch.flexmark.html.renderer.CoreNodeRenderer; | |
| 10 | import com.vladsch.flexmark.html.renderer.NodeRendererContext; | |
| 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 | ||
| 15 | import java.util.HashSet; | |
| 16 | import java.util.LinkedList; | |
| 17 | import java.util.List; | |
| 18 | import java.util.Set; | |
| 19 | ||
| 20 | /** | |
| 21 | * Responsible for rendering {@link CaptionBlock} instances as HTML (via | |
| 22 | * delegation). | |
| 23 | */ | |
| 24 | class CaptionNodeRenderer extends CoreNodeRenderer { | |
| 25 | CaptionNodeRenderer( final DataHolder options ) { | |
| 26 | super( options ); | |
| 27 | } | |
| 28 | ||
| 29 | @Override | |
| 30 | public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 31 | return new HashSet<>( List.of( | |
| 32 | new NodeRenderingHandler<>( CaptionBlock.class, this::render ) | |
| 33 | ) ); | |
| 34 | } | |
| 35 | ||
| 36 | private void render( | |
| 37 | final CaptionBlock node, | |
| 38 | final NodeRendererContext context, | |
| 39 | final HtmlWriter html ) { | |
| 40 | final var anchors = new LinkedList<Node>(); | |
| 41 | ||
| 42 | html.raw( "<p>" ); | |
| 43 | node.opening( html ); | |
| 44 | ||
| 45 | if( node.hasChildren() ) { | |
| 46 | for( final var child : node.getChildren() ) { | |
| 47 | if( !child.isOrDescendantOfType( CrossReferenceNode.class ) ) { | |
| 48 | context.render( child ); | |
| 49 | } | |
| 50 | else { | |
| 51 | anchors.add( child ); | |
| 52 | } | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | node.closing( html ); | |
| 57 | ||
| 58 | for( final var anchor : anchors ) { | |
| 59 | context.render( anchor ); | |
| 60 | } | |
| 61 | ||
| 62 | html.raw( "</p>" ); | |
| 63 | } | |
| 64 | } | |
| 1 | 65 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.common.MarkdownNodeRendererFactory; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 9 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 10 | ||
| 11 | class CaptionNodeRendererFactory extends MarkdownNodeRendererFactory { | |
| 12 | @Override | |
| 13 | protected NodeRenderer createNodeRenderer( final DataHolder options ) { | |
| 14 | return new CaptionNodeRenderer( options ); | |
| 15 | } | |
| 16 | } | |
| 1 | 17 |
| 1 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 2 | ||
| 3 | import com.vladsch.flexmark.parser.InlineParser; | |
| 4 | import com.vladsch.flexmark.parser.block.AbstractBlockParser; | |
| 5 | import com.vladsch.flexmark.parser.block.BlockContinue; | |
| 6 | import com.vladsch.flexmark.parser.block.ParserState; | |
| 7 | import com.vladsch.flexmark.util.ast.Block; | |
| 8 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 9 | ||
| 10 | class CaptionParser extends AbstractBlockParser { | |
| 11 | private final CaptionBlock mBlock; | |
| 12 | ||
| 13 | CaptionParser( final BasedSequence text ) { | |
| 14 | assert text != null; | |
| 15 | assert text.isNotEmpty(); | |
| 16 | assert text.length() > 2; | |
| 17 | ||
| 18 | final var caption = text.subSequence( 2 ); | |
| 19 | ||
| 20 | mBlock = new CaptionBlock( caption.trim() ); | |
| 21 | } | |
| 22 | ||
| 23 | static boolean canParse( final BasedSequence text ) { | |
| 24 | return text.length() > 3 && | |
| 25 | text.charAt( 0 ) == ':' && | |
| 26 | text.charAt( 1 ) == ':' && | |
| 27 | text.charAt( 2 ) != ':'; | |
| 28 | } | |
| 29 | ||
| 30 | @Override | |
| 31 | public Block getBlock() { | |
| 32 | return mBlock; | |
| 33 | } | |
| 34 | ||
| 35 | @Override | |
| 36 | public BlockContinue tryContinue( final ParserState state ) { | |
| 37 | return BlockContinue.none(); | |
| 38 | } | |
| 39 | ||
| 40 | @Override | |
| 41 | public void parseInlines( final InlineParser inlineParser ) { | |
| 42 | assert inlineParser != null; | |
| 43 | ||
| 44 | mBlock.parse( inlineParser ); | |
| 45 | } | |
| 46 | ||
| 47 | @Override | |
| 48 | public void closeBlock( final ParserState state ) {} | |
| 49 | } | |
| 1 | 50 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.fences.ClosingDivBlock; | |
| 8 | import com.keenwrite.processors.markdown.extensions.fences.OpeningDivBlock; | |
| 9 | import com.vladsch.flexmark.parser.block.NodePostProcessor; | |
| 10 | import com.vladsch.flexmark.util.ast.Node; | |
| 11 | import com.vladsch.flexmark.util.ast.NodeTracker; | |
| 12 | import org.jetbrains.annotations.NotNull; | |
| 13 | ||
| 14 | /** | |
| 15 | * Captions are written most naturally <em>after</em>> the element that they | |
| 16 | * apply to, regardless of whether they are figures, tables, code listings, | |
| 17 | * algorithms, or equations. The typesetting software uses event-based parsing | |
| 18 | * of XML elements, meaning the DOM isn't fully loaded into memory. This means | |
| 19 | * that captions must come <em>before</em> the item being captioned. | |
| 20 | * <p> | |
| 21 | * To reconcile this UX conundrum, we swap captions with the previous node. | |
| 22 | */ | |
| 23 | class CaptionPostProcessor extends NodePostProcessor { | |
| 24 | @Override | |
| 25 | public void process( | |
| 26 | @NotNull final NodeTracker state, | |
| 27 | @NotNull final Node caption ) { | |
| 28 | ||
| 29 | var previous = caption.getPrevious(); | |
| 30 | ||
| 31 | if( previous != null ) { | |
| 32 | swap( previous, caption ); | |
| 33 | } | |
| 34 | } | |
| 35 | ||
| 36 | private void swap( final Node previous, final Node caption ) { | |
| 37 | assert previous != null; | |
| 38 | assert caption != null; | |
| 39 | ||
| 40 | var swap = previous; | |
| 41 | boolean found = true; | |
| 42 | ||
| 43 | if( swap.isOrDescendantOfType( ClosingDivBlock.class ) ) { | |
| 44 | found = false; | |
| 45 | ||
| 46 | while( !found && swap != null ) { | |
| 47 | if( swap.isOrDescendantOfType( OpeningDivBlock.class ) ) { | |
| 48 | found = true; | |
| 49 | } | |
| 50 | else { | |
| 51 | swap = swap.getPrevious(); | |
| 52 | } | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | if( found ) { | |
| 57 | swap.insertBefore( caption ); | |
| 58 | } | |
| 59 | } | |
| 60 | } | |
| 1 | 61 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.captions; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.common.MarkdownPostProcessorFactory; | |
| 8 | import com.vladsch.flexmark.parser.block.NodePostProcessor; | |
| 9 | import com.vladsch.flexmark.util.ast.Document; | |
| 10 | ||
| 11 | class CaptionPostProcessorFactory extends MarkdownPostProcessorFactory { | |
| 12 | CaptionPostProcessorFactory() { | |
| 13 | // The argument isn't used by the Markdown parsing library. | |
| 14 | super( false ); | |
| 15 | ||
| 16 | addNodes( CaptionBlock.class ); | |
| 17 | } | |
| 18 | ||
| 19 | @Override | |
| 20 | protected NodePostProcessor createPostProcessor( final Document document ) { | |
| 21 | return new CaptionPostProcessor(); | |
| 22 | } | |
| 23 | } | |
| 1 | 24 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.caret; | |
| 3 | ||
| 4 | import com.keenwrite.editors.common.Caret; | |
| 5 | import com.keenwrite.processors.ProcessorContext; | |
| 6 | import com.keenwrite.processors.markdown.extensions.common.HtmlRendererAdapter; | |
| 7 | import org.jetbrains.annotations.NotNull; | |
| 8 | ||
| 9 | import java.util.function.Supplier; | |
| 10 | ||
| 11 | import static com.keenwrite.processors.markdown.extensions.caret.IdAttributeProvider.createFactory; | |
| 12 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 13 | ||
| 14 | /** | |
| 15 | * Responsible for giving most block-level elements a unique identifier | |
| 16 | * attribute. The identifier is used to coordinate scrolling. | |
| 17 | */ | |
| 18 | public class CaretExtension extends HtmlRendererAdapter { | |
| 19 | ||
| 20 | private final Supplier<Caret> mCaret; | |
| 21 | ||
| 22 | private CaretExtension( final ProcessorContext context ) { | |
| 23 | mCaret = context.getCaret(); | |
| 24 | } | |
| 25 | ||
| 26 | public static CaretExtension create( final ProcessorContext context ) { | |
| 27 | return new CaretExtension( context ); | |
| 28 | } | |
| 29 | ||
| 30 | @Override | |
| 31 | public void extend( | |
| 32 | @NotNull final Builder builder, | |
| 33 | @NotNull final String rendererType ) { | |
| 34 | builder.attributeProviderFactory( createFactory( mCaret ) ); | |
| 35 | } | |
| 36 | } | |
| 1 | 37 |
| 1 | package com.keenwrite.processors.markdown.extensions.caret; | |
| 2 | ||
| 3 | import com.keenwrite.constants.Constants; | |
| 4 | import com.keenwrite.editors.common.Caret; | |
| 5 | import com.vladsch.flexmark.ext.tables.TableBlock; | |
| 6 | import com.vladsch.flexmark.html.AttributeProvider; | |
| 7 | import com.vladsch.flexmark.html.AttributeProviderFactory; | |
| 8 | import com.vladsch.flexmark.html.IndependentAttributeProviderFactory; | |
| 9 | import com.vladsch.flexmark.html.renderer.AttributablePart; | |
| 10 | import com.vladsch.flexmark.html.renderer.LinkResolverContext; | |
| 11 | import com.vladsch.flexmark.util.ast.Node; | |
| 12 | import com.vladsch.flexmark.util.html.AttributeImpl; | |
| 13 | import com.vladsch.flexmark.util.html.MutableAttributes; | |
| 14 | import org.jetbrains.annotations.NotNull; | |
| 15 | ||
| 16 | import java.util.function.Supplier; | |
| 17 | ||
| 18 | import static com.keenwrite.constants.Constants.CARET_ID; | |
| 19 | import static com.keenwrite.processors.markdown.extensions.common.EmptyNode.EMPTY_NODE; | |
| 20 | ||
| 21 | /** | |
| 22 | * Responsible for creating the id attribute. This class is instantiated | |
| 23 | * once: for the HTML element containing the {@link Constants#CARET_ID}. | |
| 24 | */ | |
| 25 | final class IdAttributeProvider implements AttributeProvider { | |
| 26 | private final Supplier<Caret> mCaret; | |
| 27 | private boolean mAdded; | |
| 28 | ||
| 29 | public IdAttributeProvider( final Supplier<Caret> caret ) { | |
| 30 | mCaret = caret; | |
| 31 | } | |
| 32 | ||
| 33 | static AttributeProviderFactory createFactory( | |
| 34 | final Supplier<Caret> caret ) { | |
| 35 | return new IndependentAttributeProviderFactory() { | |
| 36 | @Override | |
| 37 | public @NotNull AttributeProvider apply( | |
| 38 | @NotNull final LinkResolverContext context ) { | |
| 39 | return new IdAttributeProvider( caret ); | |
| 40 | } | |
| 41 | }; | |
| 42 | } | |
| 43 | ||
| 44 | @Override | |
| 45 | public void setAttributes( | |
| 46 | @NotNull final Node curr, | |
| 47 | @NotNull final AttributablePart part, | |
| 48 | @NotNull final MutableAttributes attributes ) { | |
| 49 | // Optimization: if a caret is inserted, don't try to find another. | |
| 50 | if( mAdded ) { | |
| 51 | return; | |
| 52 | } | |
| 53 | ||
| 54 | final var caret = mCaret.get(); | |
| 55 | ||
| 56 | // If a table block has been earmarked with an empty node, it means | |
| 57 | // another extension has generated code from an external source. The | |
| 58 | // Markdown processor won't be able to determine the caret position | |
| 59 | // with any semblance of accuracy, so skip the element. This usually | |
| 60 | // happens with tables, but in theory any Markdown generated from an | |
| 61 | // external source (e.g., an R script) could produce text that has no | |
| 62 | // caret position that can be calculated. | |
| 63 | var table = curr; | |
| 64 | ||
| 65 | if( !(curr instanceof TableBlock) ) { | |
| 66 | table = curr.getAncestorOfType( TableBlock.class ); | |
| 67 | } | |
| 68 | ||
| 69 | // The table was generated outside the document | |
| 70 | if( table != null && table.getLastChild() == EMPTY_NODE ) { | |
| 71 | return; | |
| 72 | } | |
| 73 | ||
| 74 | final var outside = caret.isAfterText() ? 1 : 0; | |
| 75 | final var began = curr.getStartOffset(); | |
| 76 | final var ended = curr.getEndOffset() + outside; | |
| 77 | final var prev = curr.getPrevious(); | |
| 78 | ||
| 79 | // If the caret is within the bounds of the current node or the | |
| 80 | // caret is within the bounds of the end of the previous node and | |
| 81 | // the start of the current node, then mark the current node with | |
| 82 | // a caret indicator. | |
| 83 | if( caret.isBetweenText( began, ended ) || | |
| 84 | prev != null && caret.isBetweenText( prev.getEndOffset(), began ) ) { | |
| 85 | // This line empowers synchronizing the text editor with the preview. | |
| 86 | attributes.addValue( AttributeImpl.of( "id", CARET_ID ) ); | |
| 87 | ||
| 88 | // We're done until the user moves the caret (micro-optimization) | |
| 89 | mAdded = true; | |
| 90 | } | |
| 91 | } | |
| 92 | } | |
| 1 | 93 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.common; | |
| 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 | /** | |
| 18 | * Use {@link #EMPTY_NODE}. | |
| 19 | */ | |
| 20 | private EmptyNode() {} | |
| 21 | ||
| 22 | @NotNull | |
| 23 | @Override | |
| 24 | public BasedSequence[] getSegments() { | |
| 25 | return BasedSequence.EMPTY_SEGMENTS; | |
| 26 | } | |
| 27 | } | |
| 1 | 28 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.common; | |
| 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 | } | |
| 1 | 22 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.common; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.parser.block.BlockParserFactory; | |
| 8 | import com.vladsch.flexmark.parser.block.CustomBlockParserFactory; | |
| 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 | public abstract class MarkdownCustomBlockParserFactory | |
| 16 | implements CustomBlockParserFactory { | |
| 17 | /** | |
| 18 | * Subclasses must return a new {@link BlockParserFactory} instance. | |
| 19 | * | |
| 20 | * @param options Passed into the new instance constructor. | |
| 21 | * @return The new {@link BlockParserFactory} instance. | |
| 22 | */ | |
| 23 | protected abstract BlockParserFactory createBlockParserFactory( | |
| 24 | DataHolder options ); | |
| 25 | ||
| 26 | @NotNull | |
| 27 | @Override | |
| 28 | public BlockParserFactory apply( @NotNull final DataHolder options ) { | |
| 29 | return createBlockParserFactory( options ); | |
| 30 | } | |
| 31 | ||
| 32 | @Override | |
| 33 | public @Nullable Set<Class<?>> getAfterDependents() { | |
| 34 | return null; | |
| 35 | } | |
| 36 | ||
| 37 | @Override | |
| 38 | public @Nullable Set<Class<?>> getBeforeDependents() { | |
| 39 | return null; | |
| 40 | } | |
| 41 | ||
| 42 | @Override | |
| 43 | public boolean affectsGlobalScope() { | |
| 44 | return false; | |
| 45 | } | |
| 46 | } | |
| 1 | 47 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.common; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 9 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 10 | import org.jetbrains.annotations.NotNull; | |
| 11 | ||
| 12 | public abstract class MarkdownNodeRendererFactory | |
| 13 | implements NodeRendererFactory { | |
| 14 | @NotNull | |
| 15 | @Override | |
| 16 | public NodeRenderer apply( @NotNull final DataHolder options ) { | |
| 17 | return createNodeRenderer( options ); | |
| 18 | } | |
| 19 | ||
| 20 | protected abstract NodeRenderer createNodeRenderer( DataHolder options ); | |
| 21 | } | |
| 1 | 22 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.common; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 8 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 9 | ||
| 10 | /** | |
| 11 | * Provides a default {@link #parserOptions(MutableDataHolder)} implementation. | |
| 12 | */ | |
| 13 | public interface MarkdownParserExtension extends ParserExtension { | |
| 14 | @Override | |
| 15 | default void parserOptions( final MutableDataHolder options ) {} | |
| 16 | } | |
| 1 | 17 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.common; | |
| 6 | ||
| 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 org.jetbrains.annotations.NotNull; | |
| 11 | ||
| 12 | public abstract class MarkdownPostProcessorFactory | |
| 13 | extends NodePostProcessorFactory { | |
| 14 | public MarkdownPostProcessorFactory( final boolean ignored ) { | |
| 15 | super( ignored ); | |
| 16 | } | |
| 17 | ||
| 18 | @NotNull | |
| 19 | @Override | |
| 20 | public NodePostProcessor apply( @NotNull Document document ) { | |
| 21 | return createPostProcessor( document ); | |
| 22 | } | |
| 23 | ||
| 24 | protected abstract NodePostProcessor createPostProcessor( Document document ); | |
| 25 | } | |
| 1 | 26 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.common; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 9 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 10 | import org.jetbrains.annotations.NotNull; | |
| 11 | ||
| 12 | public abstract class MarkdownRendererExtension extends HtmlRendererAdapter | |
| 13 | implements MarkdownParserExtension { | |
| 14 | ||
| 15 | /** | |
| 16 | * Implemented by subclasses to create the {@link NodeRendererFactory} capable | |
| 17 | * of converting nodes created by an extension into HTML elements. | |
| 18 | * | |
| 19 | * @return The {@link NodeRendererFactory} for producing {@link NodeRenderer} | |
| 20 | * instances. | |
| 21 | */ | |
| 22 | protected abstract NodeRendererFactory createNodeRendererFactory(); | |
| 23 | ||
| 24 | /** | |
| 25 | * Adds an extension for HTML document export types. | |
| 26 | * | |
| 27 | * @param builder The document builder. | |
| 28 | * @param rendererType Indicates the document type to be built. | |
| 29 | */ | |
| 30 | @Override | |
| 31 | public void extend( | |
| 32 | @NotNull final Builder builder, | |
| 33 | @NotNull final String rendererType ) { | |
| 34 | if( "HTML".equalsIgnoreCase( rendererType ) ) { | |
| 35 | builder.nodeRendererFactory( createNodeRendererFactory() ); | |
| 36 | } | |
| 37 | } | |
| 38 | } | |
| 1 | 39 |
| 5 | 5 | |
| 6 | 6 | /** |
| 7 | * Responsible for helping to generate a closing div element. | |
| 7 | * Responsible for helping to generate a closing {@code div} element. | |
| 8 | 8 | */ |
| 9 | class ClosingDivBlock extends DivBlock { | |
| 9 | public final class ClosingDivBlock extends DivBlock { | |
| 10 | 10 | @Override |
| 11 | void export( final HtmlWriter html ) { | |
| 11 | void write( final HtmlWriter html ) { | |
| 12 | 12 | html.closeTag( HTML_DIV ); |
| 13 | 13 | } |
| 21 | 21 | * @param html Builds the HTML document to be written. |
| 22 | 22 | */ |
| 23 | abstract void export( HtmlWriter html ); | |
| 23 | abstract void write( HtmlWriter html ); | |
| 24 | 24 | } |
| 25 | 25 |
| 7 | 7 | import com.keenwrite.processors.VariableProcessor; |
| 8 | 8 | import com.keenwrite.processors.markdown.MarkdownProcessor; |
| 9 | import com.keenwrite.processors.markdown.extensions.HtmlRendererAdapter; | |
| 9 | import com.keenwrite.processors.markdown.extensions.common.HtmlRendererAdapter; | |
| 10 | 10 | import com.keenwrite.processors.r.RChunkEvaluator; |
| 11 | 11 | import com.keenwrite.processors.r.RVariableProcessor; |
| ... | ||
| 25 | 25 | |
| 26 | 26 | import static com.keenwrite.Bootstrap.APP_TITLE_LOWERCASE; |
| 27 | import static com.keenwrite.constants.Constants.TEMPORARY_DIRECTORY; | |
| 27 | 28 | import static com.keenwrite.processors.IdentityProcessor.IDENTITY; |
| 28 | 29 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; |
| ... | ||
| 36 | 37 | */ |
| 37 | 38 | public final class FencedBlockExtension extends HtmlRendererAdapter { |
| 38 | private static final String TEMP_DIR = System.getProperty( "java.io.tmpdir" ); | |
| 39 | ||
| 40 | 39 | /** |
| 41 | 40 | * Ensure that the device is always closed to prevent an out-of-resources |
| ... | ||
| 202 | 201 | |
| 203 | 202 | // The URI helps convert backslashes to forward slashes. |
| 204 | final var uri = Path.of( TEMP_DIR, filename ).toUri(); | |
| 203 | final var uri = Path.of( TEMPORARY_DIRECTORY, filename ).toUri(); | |
| 205 | 204 | final var svg = uri.getPath(); |
| 206 | 205 | final var link = context.resolveLink( LINK, svg, false ); |
| 2 | 2 | package com.keenwrite.processors.markdown.extensions.fences; |
| 3 | 3 | |
| 4 | import com.keenwrite.processors.markdown.extensions.HtmlRendererAdapter; | |
| 5 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 6 | import com.vladsch.flexmark.parser.Parser; | |
| 4 | import com.keenwrite.processors.markdown.extensions.common.MarkdownCustomBlockParserFactory; | |
| 5 | import com.keenwrite.processors.markdown.extensions.common.MarkdownRendererExtension; | |
| 6 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 7 | import com.vladsch.flexmark.parser.Parser.Builder; | |
| 7 | 8 | import com.vladsch.flexmark.parser.block.*; |
| 8 | 9 | import com.vladsch.flexmark.util.ast.Block; |
| 9 | 10 | import com.vladsch.flexmark.util.data.DataHolder; |
| 10 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 11 | 11 | import com.vladsch.flexmark.util.html.Attribute; |
| 12 | 12 | import com.vladsch.flexmark.util.html.AttributeImpl; |
| 13 | import org.jetbrains.annotations.NotNull; | |
| 14 | import org.jetbrains.annotations.Nullable; | |
| 15 | 13 | |
| 16 | 14 | import java.util.ArrayList; |
| 17 | import java.util.Set; | |
| 18 | 15 | import java.util.regex.Pattern; |
| 19 | 16 | |
| 20 | import static com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 17 | import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS; | |
| 18 | import static java.util.regex.Pattern.compile; | |
| 21 | 19 | |
| 22 | 20 | /** |
| ... | ||
| 53 | 51 | * </p> |
| 54 | 52 | */ |
| 55 | public class FencedDivExtension extends HtmlRendererAdapter | |
| 56 | implements ParserExtension { | |
| 53 | public class FencedDivExtension extends MarkdownRendererExtension { | |
| 57 | 54 | /** |
| 58 | 55 | * Matches any number of colons at start of line. This will match both the |
| 59 | 56 | * opening and closing fences, with any number of colons. |
| 60 | 57 | */ |
| 61 | private static final Pattern FENCE = Pattern.compile( "^:::.*" ); | |
| 58 | private static final Pattern FENCE = compile( "^:::.*" ); | |
| 62 | 59 | |
| 63 | 60 | /** |
| 64 | 61 | * After a fenced div is detected, this will match the opening fence. |
| 65 | 62 | */ |
| 66 | private static final Pattern FENCE_OPENING = Pattern.compile( | |
| 67 | "^:::+\\s+([\\p{IsAlphabetic}\\p{IsDigit}-_]+|\\{.+})\\s*$" ); | |
| 63 | private static final Pattern FENCE_OPENING = compile( | |
| 64 | "^:::+\\s+([\\p{Alnum}-_]+|\\{.+})\\s*$", | |
| 65 | UNICODE_CHARACTER_CLASS ); | |
| 68 | 66 | |
| 69 | 67 | /** |
| 70 | 68 | * Matches whether extended syntax is being used. |
| 71 | 69 | */ |
| 72 | private static final Pattern ATTR_CSS = Pattern.compile( "\\{(.+)}" ); | |
| 70 | private static final Pattern ATTR_CSS = compile( "\\{(.+)}" ); | |
| 73 | 71 | |
| 74 | 72 | /** |
| 75 | 73 | * Matches either individual CSS definitions (id/class, {@code <d>}) or |
| 76 | 74 | * key/value pairs ({@code <k>} and {@link <v>}). The key/value pair |
| 77 | 75 | * will match optional quotes. |
| 78 | 76 | */ |
| 79 | private static final Pattern ATTR_PAIRS = Pattern.compile( | |
| 77 | private static final Pattern ATTR_PAIRS = compile( | |
| 80 | 78 | "\\s*" + |
| 81 | "(?<d>[#.][\\p{IsAlphabetic}\\p{IsDigit}-_]+[^\\s=])|" + | |
| 82 | "((?<k>[\\p{IsAlphabetic}\\p{IsDigit}-_]+)=" + | |
| 83 | "\"*(?<v>(?<=\")[^\"]+(?=\")|([^\\s]+))\"*)" ); | |
| 79 | "(?<d>[#.][\\p{Alnum}-_]+[^\\s=])|" + | |
| 80 | "((?<k>[\\p{Alnum}-_]+)=" + | |
| 81 | "\"*(?<v>(?<=\")[^\"]+(?=\")|(\\S+))\"*)", | |
| 82 | UNICODE_CHARACTER_CLASS ); | |
| 84 | 83 | |
| 85 | 84 | public static FencedDivExtension create() { |
| 86 | 85 | return new FencedDivExtension(); |
| 87 | } | |
| 88 | ||
| 89 | @Override | |
| 90 | public void parserOptions( final MutableDataHolder options ) { | |
| 91 | 86 | } |
| 92 | 87 | |
| 93 | 88 | @Override |
| 94 | public void extend( final Parser.Builder builder ) { | |
| 95 | builder.customBlockParserFactory( new Factory() ); | |
| 89 | public void extend( final Builder builder ) { | |
| 90 | builder.customBlockParserFactory( new DivBlockParserFactory() ); | |
| 96 | 91 | } |
| 97 | 92 | |
| 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 | 93 | @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 | } | |
| 94 | protected NodeRendererFactory createNodeRendererFactory() { | |
| 95 | return new FencedDivNodeRendererFactory(); | |
| 110 | 96 | } |
| 111 | 97 | |
| 112 | 98 | /** |
| 113 | 99 | * Responsible for creating an instance of {@link ParserFactory}. |
| 114 | 100 | */ |
| 115 | private static class Factory implements CustomBlockParserFactory { | |
| 101 | private static class DivBlockParserFactory | |
| 102 | extends MarkdownCustomBlockParserFactory { | |
| 116 | 103 | @Override |
| 117 | public @NotNull BlockParserFactory apply( | |
| 118 | @NotNull final DataHolder options ) { | |
| 104 | public BlockParserFactory createBlockParserFactory( final DataHolder options ) { | |
| 119 | 105 | return new ParserFactory( options ); |
| 120 | 106 | } |
| 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 | 107 | } |
| 131 | 108 | |
| 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.common.MarkdownNodeRendererFactory; | |
| 5 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 6 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 7 | ||
| 8 | class FencedDivNodeRendererFactory extends MarkdownNodeRendererFactory { | |
| 9 | @Override | |
| 10 | protected NodeRenderer createNodeRenderer( final DataHolder options ) { | |
| 11 | return new FencedDivRenderer(); | |
| 12 | } | |
| 13 | } | |
| 1 | 14 |
| 5 | 5 | import com.vladsch.flexmark.html.renderer.NodeRenderer; |
| 6 | 6 | import com.vladsch.flexmark.html.renderer.NodeRendererContext; |
| 7 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 8 | 7 | import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; |
| 9 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 10 | import org.jetbrains.annotations.NotNull; | |
| 11 | 8 | import org.jetbrains.annotations.Nullable; |
| 12 | 9 | |
| 13 | 10 | import java.util.Set; |
| 14 | 11 | |
| 15 | 12 | /** |
| 16 | * Responsible for rendering opening and closing fenced div blocks as HTMl | |
| 17 | * div elements. | |
| 13 | * Responsible for rendering opening and closing fenced div blocks as HTML | |
| 14 | * {@code div} elements. | |
| 18 | 15 | */ |
| 19 | 16 | class FencedDivRenderer implements NodeRenderer { |
| 17 | @Nullable | |
| 20 | 18 | @Override |
| 21 | public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 19 | public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 22 | 20 | return Set.of( |
| 23 | 21 | new NodeRenderingHandler<>( OpeningDivBlock.class, this::render ), |
| 24 | 22 | new NodeRenderingHandler<>( ClosingDivBlock.class, this::render ) |
| 25 | 23 | ); |
| 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 | 24 | } |
| 36 | 25 | |
| 37 | 26 | /** |
| 38 | * Renders the closing fenced div block as an HTML {@code </div>} element. | |
| 27 | * Renders the fenced div block as an HTML {@code <div></div>} element. | |
| 39 | 28 | */ |
| 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 NodeRendererFactory { | |
| 47 | @Override | |
| 48 | public @NotNull NodeRenderer apply( @NotNull final DataHolder options ) { | |
| 49 | return new FencedDivRenderer(); | |
| 50 | } | |
| 29 | void render( | |
| 30 | final DivBlock node, | |
| 31 | final NodeRendererContext context, | |
| 32 | final HtmlWriter html ) { | |
| 33 | node.write( html ); | |
| 51 | 34 | } |
| 52 | 35 | } |
| 9 | 9 | |
| 10 | 10 | /** |
| 11 | * Responsible for helping to generate an opening div element. | |
| 11 | * Responsible for helping to generate an opening {@code div} element. | |
| 12 | 12 | */ |
| 13 | class OpeningDivBlock extends DivBlock { | |
| 13 | public final class OpeningDivBlock extends DivBlock { | |
| 14 | 14 | private final List<Attribute> mAttributes = new ArrayList<>(); |
| 15 | 15 | |
| 16 | 16 | OpeningDivBlock( final List<Attribute> attributes ) { |
| 17 | 17 | assert attributes != null; |
| 18 | 18 | mAttributes.addAll( attributes ); |
| 19 | 19 | } |
| 20 | 20 | |
| 21 | void export( final HtmlWriter html ) { | |
| 21 | @Override | |
| 22 | void write( final HtmlWriter html ) { | |
| 22 | 23 | mAttributes.forEach( html::attr ); |
| 23 | 24 | html.withAttr().tag( HTML_DIV ); |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.images; | |
| 3 | ||
| 4 | import com.keenwrite.ExportFormat; | |
| 5 | import com.keenwrite.processors.ProcessorContext; | |
| 6 | import com.keenwrite.processors.markdown.extensions.common.HtmlRendererAdapter; | |
| 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 | import java.util.Optional; | |
| 18 | ||
| 19 | import static com.keenwrite.events.StatusEvent.clue; | |
| 20 | import static com.keenwrite.io.SysFile.toFile; | |
| 21 | import static com.keenwrite.util.ProtocolScheme.getProtocol; | |
| 22 | import static com.vladsch.flexmark.html.HtmlRenderer.Builder; | |
| 23 | import static com.vladsch.flexmark.html.renderer.LinkStatus.VALID; | |
| 24 | ||
| 25 | /** | |
| 26 | * Responsible for ensuring that images can be rendered relative to a path. | |
| 27 | * This allows images to be located virtually anywhere. | |
| 28 | */ | |
| 29 | public class ImageLinkExtension extends HtmlRendererAdapter { | |
| 30 | ||
| 31 | private final ProcessorContext mContext; | |
| 32 | ||
| 33 | private ImageLinkExtension( @NotNull final ProcessorContext context ) { | |
| 34 | mContext = context; | |
| 35 | } | |
| 36 | ||
| 37 | /** | |
| 38 | * Creates an extension capable of using a relative path to embed images. | |
| 39 | * | |
| 40 | * @param context Contains the base directory to search in for images. | |
| 41 | * @return The new {@link ImageLinkExtension}, not {@code null}. | |
| 42 | */ | |
| 43 | public static ImageLinkExtension create( | |
| 44 | @NotNull final ProcessorContext context ) { | |
| 45 | return new ImageLinkExtension( context ); | |
| 46 | } | |
| 47 | ||
| 48 | @Override | |
| 49 | public void extend( | |
| 50 | @NotNull final Builder builder, | |
| 51 | @NotNull final String rendererType ) { | |
| 52 | builder.linkResolverFactory( new ResolverFactory() ); | |
| 53 | } | |
| 54 | ||
| 55 | private final class ResolverFactory extends IndependentLinkResolverFactory { | |
| 56 | @NotNull | |
| 57 | @Override | |
| 58 | public LinkResolver apply( | |
| 59 | @NotNull final LinkResolverBasicContext context ) { | |
| 60 | return new ImageLinkResolver(); | |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | private final class ImageLinkResolver implements LinkResolver { | |
| 65 | private ImageLinkResolver() {} | |
| 66 | ||
| 67 | @NotNull | |
| 68 | @Override | |
| 69 | public ResolvedLink resolveLink( | |
| 70 | @NotNull final Node node, | |
| 71 | @NotNull final LinkResolverBasicContext context, | |
| 72 | @NotNull final ResolvedLink link ) { | |
| 73 | return node instanceof Image ? forImage( link, node ) : link; | |
| 74 | } | |
| 75 | ||
| 76 | /** | |
| 77 | * Algorithm: | |
| 78 | * <ol> | |
| 79 | * <li>Accept remote URLs as valid links.</li> | |
| 80 | * <li>Accept existing readable files as valid links.</li> | |
| 81 | * <li>Accept non-{@link ExportFormat#NONE} exports as valid links.</li> | |
| 82 | * <li>Append the images dir to the edited file's dir (baseDir).</li> | |
| 83 | * <li>Search for images by extension.</li> | |
| 84 | * </ol> | |
| 85 | * | |
| 86 | * @param link The link URL to resolve. | |
| 87 | * @param node The document node containing the URL. | |
| 88 | * @return The {@link ResolvedLink} instance used to render the link. | |
| 89 | */ | |
| 90 | private ResolvedLink forImage( final ResolvedLink link, final Node node ) { | |
| 91 | final var url = link.getUrl(); | |
| 92 | final var protocolScheme = getProtocol( url ); | |
| 93 | ||
| 94 | return protocolScheme.isRemote() | |
| 95 | ? valid( link, url ) | |
| 96 | : resolveImageFile( link, node, url ); | |
| 97 | } | |
| 98 | ||
| 99 | private ResolvedLink resolveImageFile( | |
| 100 | final ResolvedLink link, | |
| 101 | final Node node, | |
| 102 | final String url ) { | |
| 103 | final var userPath = new File( url ); | |
| 104 | ||
| 105 | // If the user specified a fully qualified path name, use it verbatim. | |
| 106 | return readable( userPath ) | |
| 107 | ? valid( link, url ) | |
| 108 | : resolveUnqualifiedImageFile( link, node, url ); | |
| 109 | } | |
| 110 | ||
| 111 | private ResolvedLink resolveUnqualifiedImageFile( | |
| 112 | final ResolvedLink link, | |
| 113 | final Node node, | |
| 114 | final String url ) { | |
| 115 | final var baseDir = getBaseDir(); | |
| 116 | final var fqfn = baseDir.resolve( Path.of( url ) ); | |
| 117 | ||
| 118 | // If the image can be found relative to the base directory, then | |
| 119 | // use the link as is when resolving the path. | |
| 120 | return readable( toFile( fqfn ) ) | |
| 121 | ? valid( link, url ) | |
| 122 | : resolveExtensionlessImageFile( link, node, url ); | |
| 123 | } | |
| 124 | ||
| 125 | private ResolvedLink resolveExtensionlessImageFile( | |
| 126 | final ResolvedLink link, | |
| 127 | final Node node, | |
| 128 | final String url | |
| 129 | ) { | |
| 130 | final var imagePath = new File( url ); | |
| 131 | final var file = resolveImageExtension( imagePath ); | |
| 132 | ||
| 133 | return file.isPresent() && readable( file.get() ) | |
| 134 | ? valid( link, file.get().toString() ) | |
| 135 | : resolveRelativeImageFile( link, node, url ); | |
| 136 | } | |
| 137 | ||
| 138 | private ResolvedLink resolveRelativeImageFile( | |
| 139 | final ResolvedLink link, | |
| 140 | final Node node, | |
| 141 | final String url ) { | |
| 142 | final var baseDir = getBaseDir(); | |
| 143 | ||
| 144 | try { | |
| 145 | // Compute the path to the image file. The base directory should | |
| 146 | // be an absolute path to the file being edited, without an extension. | |
| 147 | final var imagesDir = getImageDir(); | |
| 148 | final var baseImagesDir = baseDir.resolve( imagesDir ); | |
| 149 | final var imagePath = baseImagesDir.resolve( url ); | |
| 150 | final var file = resolveImageExtension( toFile( imagePath ) ); | |
| 151 | ||
| 152 | if( file.isPresent() ) { | |
| 153 | final var resolved = imagesDir.resolve( file.get().toPath() ); | |
| 154 | final var relative = baseDir.relativize( resolved ); | |
| 155 | return valid( link, relative.toString() ); | |
| 156 | } | |
| 157 | ||
| 158 | clue( "Main.status.error.file.missing.near", | |
| 159 | imagePath + ".*", node.getLineNumber() | |
| 160 | ); | |
| 161 | } catch( final Exception ex ) { | |
| 162 | clue( ex ); | |
| 163 | } | |
| 164 | ||
| 165 | return link; | |
| 166 | } | |
| 167 | ||
| 168 | private Optional<File> resolveImageExtension( final File imagePath ) { | |
| 169 | for( final var ext : getImageOrder() ) { | |
| 170 | final var file = new File( imagePath.toString() + '.' + ext ); | |
| 171 | ||
| 172 | if( readable( file ) ) { | |
| 173 | return Optional.of( file ); | |
| 174 | } | |
| 175 | } | |
| 176 | ||
| 177 | return Optional.empty(); | |
| 178 | } | |
| 179 | ||
| 180 | private ResolvedLink valid( final ResolvedLink link, final String url ) { | |
| 181 | return link.withStatus( VALID ).withUrl( url ); | |
| 182 | } | |
| 183 | ||
| 184 | private Path getImageDir() { | |
| 185 | return mContext.getImageDir(); | |
| 186 | } | |
| 187 | ||
| 188 | private Iterable<String> getImageOrder() { | |
| 189 | return mContext.getImageOrder(); | |
| 190 | } | |
| 191 | ||
| 192 | private Path getBaseDir() { | |
| 193 | return mContext.getBaseDir(); | |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | private static boolean readable( final File file ) { | |
| 198 | return file.isFile() && file.canRead(); | |
| 199 | } | |
| 200 | } | |
| 1 | 201 |
| 1 | package com.keenwrite.processors.markdown.extensions.outline; | |
| 2 | ||
| 3 | import com.keenwrite.events.ParseHeadingEvent; | |
| 4 | import com.keenwrite.processors.Processor; | |
| 5 | import com.keenwrite.processors.markdown.extensions.common.MarkdownParserExtension; | |
| 6 | import com.vladsch.flexmark.ast.Heading; | |
| 7 | import com.vladsch.flexmark.parser.Parser.Builder; | |
| 8 | import com.vladsch.flexmark.parser.block.NodePostProcessor; | |
| 9 | import com.vladsch.flexmark.parser.block.NodePostProcessorFactory; | |
| 10 | import com.vladsch.flexmark.util.ast.Document; | |
| 11 | import com.vladsch.flexmark.util.ast.Node; | |
| 12 | import com.vladsch.flexmark.util.ast.NodeTracker; | |
| 13 | import org.jetbrains.annotations.NotNull; | |
| 14 | ||
| 15 | import java.util.regex.Pattern; | |
| 16 | ||
| 17 | import static com.keenwrite.events.ParseHeadingEvent.fireNewOutlineEvent; | |
| 18 | ||
| 19 | public final class DocumentOutlineExtension implements MarkdownParserExtension { | |
| 20 | private static final Pattern REGEX = Pattern.compile( "^(#+)" ); | |
| 21 | ||
| 22 | private final Processor<String> mProcessor; | |
| 23 | ||
| 24 | private DocumentOutlineExtension( final Processor<String> processor ) { | |
| 25 | mProcessor = processor; | |
| 26 | } | |
| 27 | ||
| 28 | @Override | |
| 29 | public void extend( final Builder builder ) { | |
| 30 | builder.postProcessorFactory( new Factory() ); | |
| 31 | } | |
| 32 | ||
| 33 | public static DocumentOutlineExtension create( | |
| 34 | final Processor<String> processor ) { | |
| 35 | return new DocumentOutlineExtension( processor ); | |
| 36 | } | |
| 37 | ||
| 38 | private class HeadingNodePostProcessor extends NodePostProcessor { | |
| 39 | @Override | |
| 40 | public void process( | |
| 41 | @NotNull final NodeTracker state, @NotNull final Node node ) { | |
| 42 | final var heading = mProcessor.apply( node.getChars().toString() ); | |
| 43 | final var matcher = REGEX.matcher( heading ); | |
| 44 | ||
| 45 | if( matcher.find() ) { | |
| 46 | final var level = matcher.group().length(); | |
| 47 | final var text = heading.substring( level ); | |
| 48 | final var offset = node.getStartOffset(); | |
| 49 | ParseHeadingEvent.fire( level, text, offset ); | |
| 50 | } | |
| 51 | } | |
| 52 | } | |
| 53 | ||
| 54 | public class Factory extends NodePostProcessorFactory { | |
| 55 | public Factory() { | |
| 56 | super( false ); | |
| 57 | addNodes( Heading.class ); | |
| 58 | } | |
| 59 | ||
| 60 | @NotNull | |
| 61 | @Override | |
| 62 | public NodePostProcessor apply( @NotNull final Document document ) { | |
| 63 | fireNewOutlineEvent(); | |
| 64 | return new HeadingNodePostProcessor(); | |
| 65 | } | |
| 66 | } | |
| 67 | } | |
| 1 | 68 |
| 5 | 5 | import com.keenwrite.processors.ProcessorContext; |
| 6 | 6 | import com.keenwrite.processors.markdown.BaseMarkdownProcessor; |
| 7 | import com.keenwrite.processors.markdown.extensions.common.MarkdownParserExtension; | |
| 7 | 8 | import com.keenwrite.processors.r.RInlineEvaluator; |
| 8 | 9 | import com.vladsch.flexmark.ast.Paragraph; |
| 9 | 10 | import com.vladsch.flexmark.parser.InlineParserExtensionFactory; |
| 10 | 11 | import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor; |
| 11 | 12 | import com.vladsch.flexmark.parser.internal.InlineParserImpl; |
| 12 | 13 | import com.vladsch.flexmark.parser.internal.LinkRefProcessorData; |
| 13 | 14 | import com.vladsch.flexmark.util.data.DataHolder; |
| 14 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 15 | 15 | |
| 16 | 16 | import java.util.BitSet; |
| 17 | 17 | import java.util.List; |
| 18 | 18 | import java.util.Map; |
| 19 | 19 | |
| 20 | 20 | import static com.keenwrite.processors.IdentityProcessor.IDENTITY; |
| 21 | 21 | import static com.vladsch.flexmark.parser.Parser.Builder; |
| 22 | import static com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 23 | 22 | |
| 24 | 23 | /** |
| 25 | 24 | * Responsible for processing inline R statements (denoted using the |
| 26 | 25 | * {@link RInlineEvaluator#PREFIX}) to prevent them from being converted to |
| 27 | 26 | * HTML {@code <code>} elements and stop them from interfering with TeX |
| 28 | 27 | * statements. Note that TeX statements are processed using a Markdown |
| 29 | 28 | * extension, rather than an implementation of {@link Processor}. For this |
| 30 | 29 | * reason, some pre-conversion is necessary. |
| 31 | 30 | */ |
| 32 | public final class RInlineExtension implements ParserExtension { | |
| 31 | public final class RInlineExtension implements MarkdownParserExtension { | |
| 33 | 32 | private final RInlineEvaluator mEvaluator; |
| 34 | 33 | private final BaseMarkdownProcessor mMarkdownProcessor; |
| ... | ||
| 55 | 54 | builder.customInlineParserFactory( InlineParser::new ); |
| 56 | 55 | } |
| 57 | ||
| 58 | @Override | |
| 59 | public void parserOptions( final MutableDataHolder options ) {} | |
| 60 | 56 | |
| 61 | 57 | /** |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.parser.InlineParser; | |
| 8 | import com.vladsch.flexmark.parser.core.delimiter.Delimiter; | |
| 9 | import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor; | |
| 10 | import com.vladsch.flexmark.parser.delimiter.DelimiterRun; | |
| 11 | import com.vladsch.flexmark.util.ast.Node; | |
| 12 | ||
| 13 | import static com.keenwrite.constants.Constants.DEF_DELIM_BEGAN_DEFAULT; | |
| 14 | ||
| 15 | /** | |
| 16 | * Responsible for processing {@code {@type:id}} anchors. | |
| 17 | */ | |
| 18 | class AnchorNameDelimiterProcessor implements DelimiterProcessor { | |
| 19 | ||
| 20 | @Override | |
| 21 | public void process( | |
| 22 | final Delimiter opener, | |
| 23 | final Delimiter closer, | |
| 24 | final int delimitersUsed ) { | |
| 25 | final var node = new AnchorNameNode(); | |
| 26 | opener.moveNodesBetweenDelimitersTo( node, closer ); | |
| 27 | } | |
| 28 | ||
| 29 | @Override | |
| 30 | public char getOpeningCharacter() { | |
| 31 | return '{'; | |
| 32 | } | |
| 33 | ||
| 34 | @Override | |
| 35 | public char getClosingCharacter() { | |
| 36 | return '}'; | |
| 37 | } | |
| 38 | ||
| 39 | @Override | |
| 40 | public int getMinLength() { | |
| 41 | return 1; | |
| 42 | } | |
| 43 | ||
| 44 | @Override | |
| 45 | public int getDelimiterUse( | |
| 46 | final DelimiterRun opener, | |
| 47 | final DelimiterRun closer ) { | |
| 48 | final var text = opener.getNode(); | |
| 49 | ||
| 50 | // Ensure that the default delimiters are respected (not clobbered by | |
| 51 | // transforming them into anchor links). | |
| 52 | return text.getChars().toString().equals( DEF_DELIM_BEGAN_DEFAULT ) ? 0 : 1; | |
| 53 | } | |
| 54 | ||
| 55 | @Override | |
| 56 | public Node unmatchedDelimiterNode( | |
| 57 | final InlineParser inlineParser, | |
| 58 | final DelimiterRun delimiter ) { | |
| 59 | return null; | |
| 60 | } | |
| 61 | ||
| 62 | @Override | |
| 63 | public boolean canBeOpener( | |
| 64 | final String before, | |
| 65 | final String after, | |
| 66 | final boolean leftFlanking, | |
| 67 | final boolean rightFlanking, | |
| 68 | final boolean beforeIsPunctuation, | |
| 69 | final boolean afterIsPunctuation, | |
| 70 | final boolean beforeIsWhitespace, | |
| 71 | final boolean afterIsWhiteSpace ) { | |
| 72 | return leftFlanking; | |
| 73 | } | |
| 74 | ||
| 75 | @Override | |
| 76 | public boolean canBeCloser( | |
| 77 | final String before, | |
| 78 | final String after, | |
| 79 | final boolean leftFlanking, | |
| 80 | final boolean rightFlanking, | |
| 81 | final boolean beforeIsPunctuation, | |
| 82 | final boolean afterIsPunctuation, | |
| 83 | final boolean beforeIsWhitespace, | |
| 84 | final boolean afterIsWhiteSpace ) { | |
| 85 | return rightFlanking; | |
| 86 | } | |
| 87 | ||
| 88 | @Override | |
| 89 | public boolean skipNonOpenerCloser() { | |
| 90 | return false; | |
| 91 | } | |
| 92 | } | |
| 1 | 93 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.ast.DelimitedNodeImpl; | |
| 8 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 9 | import org.jetbrains.annotations.NotNull; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for writing HTML anchor names in the form | |
| 13 | * {@code <a data-type="..." name="name" />}, where {@code name} can be | |
| 14 | * referred to by a cross-reference. | |
| 15 | * | |
| 16 | * @see AnchorXrefNode | |
| 17 | */ | |
| 18 | public class AnchorNameNode extends DelimitedNodeImpl implements CrossReferenceNode { | |
| 19 | ||
| 20 | private BasedSequence mOpeningMarker = BasedSequence.EMPTY; | |
| 21 | private BasedSequence mClosingMarker = BasedSequence.EMPTY; | |
| 22 | ||
| 23 | private BasedSequenceNameParser mParser; | |
| 24 | ||
| 25 | public AnchorNameNode() {} | |
| 26 | ||
| 27 | @Override | |
| 28 | public String getTypeName() { | |
| 29 | return mParser.getTypeName(); | |
| 30 | } | |
| 31 | ||
| 32 | @Override | |
| 33 | public String getIdName() { | |
| 34 | return mParser.getIdName(); | |
| 35 | } | |
| 36 | ||
| 37 | @Override | |
| 38 | public String getRefAttrName() { | |
| 39 | return "name"; | |
| 40 | } | |
| 41 | ||
| 42 | @Override | |
| 43 | public BasedSequence getOpeningMarker() { | |
| 44 | return mOpeningMarker; | |
| 45 | } | |
| 46 | ||
| 47 | @NotNull | |
| 48 | @Override | |
| 49 | public BasedSequence getChars() { | |
| 50 | return BasedSequence.EMPTY; | |
| 51 | } | |
| 52 | ||
| 53 | @Override | |
| 54 | public void setOpeningMarker( final BasedSequence openingMarker ) { | |
| 55 | mOpeningMarker = openingMarker; | |
| 56 | } | |
| 57 | ||
| 58 | @Override | |
| 59 | public BasedSequence getText() { | |
| 60 | return BasedSequence.EMPTY; | |
| 61 | } | |
| 62 | ||
| 63 | @Override | |
| 64 | public void setText( final BasedSequence text ) { | |
| 65 | mParser = BasedSequenceNameParser.parse( text ); | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | public BasedSequence getClosingMarker() { | |
| 70 | return mClosingMarker; | |
| 71 | } | |
| 72 | ||
| 73 | @Override | |
| 74 | public void setClosingMarker( final BasedSequence closingMarker ) { | |
| 75 | mClosingMarker = closingMarker; | |
| 76 | } | |
| 77 | } | |
| 1 | 78 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.util.ast.Node; | |
| 8 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 9 | import org.jetbrains.annotations.NotNull; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for writing HTML anchor cross-references in the form | |
| 13 | * {@code <a data-type="..." href="#name" />} where {@code name} refers | |
| 14 | * to an anchor name. | |
| 15 | * | |
| 16 | * @see AnchorNameNode | |
| 17 | */ | |
| 18 | public class AnchorXrefNode extends Node implements CrossReferenceNode { | |
| 19 | private final String mTypeName; | |
| 20 | private final String mIdName; | |
| 21 | ||
| 22 | AnchorXrefNode( final String type, final String id ) { | |
| 23 | mTypeName = type; | |
| 24 | mIdName = STR. "#\{ id }" ; | |
| 25 | } | |
| 26 | ||
| 27 | @Override | |
| 28 | public String getTypeName() { | |
| 29 | return mTypeName; | |
| 30 | } | |
| 31 | ||
| 32 | @Override | |
| 33 | public String getIdName() { | |
| 34 | return mIdName; | |
| 35 | } | |
| 36 | ||
| 37 | @Override | |
| 38 | public String getRefAttrName() { | |
| 39 | return "href"; | |
| 40 | } | |
| 41 | ||
| 42 | @NotNull | |
| 43 | @Override | |
| 44 | public BasedSequence[] getSegments() { | |
| 45 | return BasedSequence.EMPTY_SEGMENTS; | |
| 46 | } | |
| 47 | } | |
| 1 | 48 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.parser.LinkRefProcessor; | |
| 8 | import com.vladsch.flexmark.parser.LinkRefProcessorFactory; | |
| 9 | import com.vladsch.flexmark.util.ast.Document; | |
| 10 | import com.vladsch.flexmark.util.ast.Node; | |
| 11 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 12 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 13 | import org.jetbrains.annotations.NotNull; | |
| 14 | ||
| 15 | /** | |
| 16 | * Responsible for processing {@code [@type:id]} anchors. | |
| 17 | */ | |
| 18 | class AnchorXrefProcessorFactory implements LinkRefProcessorFactory { | |
| 19 | private final LinkRefProcessor mProcessor = new AnchorLinkRefProcessor(); | |
| 20 | ||
| 21 | @Override | |
| 22 | public boolean getWantExclamationPrefix( @NotNull final DataHolder options ) { | |
| 23 | return false; | |
| 24 | } | |
| 25 | ||
| 26 | @Override | |
| 27 | public int getBracketNestingLevel( @NotNull final DataHolder options ) { | |
| 28 | return 0; | |
| 29 | } | |
| 30 | ||
| 31 | @NotNull | |
| 32 | @Override | |
| 33 | public LinkRefProcessor apply( @NotNull final Document document ) { | |
| 34 | return mProcessor; | |
| 35 | } | |
| 36 | ||
| 37 | private static class AnchorLinkRefProcessor implements LinkRefProcessor { | |
| 38 | ||
| 39 | @Override | |
| 40 | public boolean getWantExclamationPrefix() { | |
| 41 | return false; | |
| 42 | } | |
| 43 | ||
| 44 | @Override | |
| 45 | public int getBracketNestingLevel() { | |
| 46 | return 0; | |
| 47 | } | |
| 48 | ||
| 49 | @Override | |
| 50 | public boolean isMatch( @NotNull final BasedSequence nodeChars ) { | |
| 51 | return nodeChars.indexOf( '@' ) == 1; | |
| 52 | } | |
| 53 | ||
| 54 | @NotNull | |
| 55 | @Override | |
| 56 | public Node createNode( @NotNull final BasedSequence nodeChars ) { | |
| 57 | return BasedSequenceXrefParser.parse( nodeChars ).toNode(); | |
| 58 | } | |
| 59 | ||
| 60 | @NotNull | |
| 61 | @Override | |
| 62 | public BasedSequence adjustInlineText( | |
| 63 | @NotNull final Document document, | |
| 64 | @NotNull final Node node ) { | |
| 65 | return BasedSequence.EMPTY; | |
| 66 | } | |
| 67 | ||
| 68 | @Override | |
| 69 | public boolean allowDelimiters( | |
| 70 | @NotNull final BasedSequence chars, | |
| 71 | @NotNull final Document document, | |
| 72 | @NotNull final Node node ) { | |
| 73 | return false; | |
| 74 | } | |
| 75 | ||
| 76 | @Override | |
| 77 | public void updateNodeElements( | |
| 78 | @NotNull final Document document, | |
| 79 | @NotNull final Node node ) {} | |
| 80 | } | |
| 81 | } | |
| 1 | 82 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 8 | ||
| 9 | import java.util.regex.Pattern; | |
| 10 | ||
| 11 | class BasedSequenceNameParser extends BasedSequenceParser { | |
| 12 | private static final String REGEX = STR. "#\{ REGEX_INNER }" ; | |
| 13 | private static final Pattern PATTERN = asPattern( REGEX ); | |
| 14 | ||
| 15 | private BasedSequenceNameParser( final String text ) { | |
| 16 | super( text ); | |
| 17 | } | |
| 18 | ||
| 19 | static BasedSequenceNameParser parse( final BasedSequence chars ) { | |
| 20 | return new BasedSequenceNameParser( chars.toString() ); | |
| 21 | } | |
| 22 | ||
| 23 | @Override | |
| 24 | Pattern getPattern() { | |
| 25 | return PATTERN; | |
| 26 | } | |
| 27 | } | |
| 1 | 28 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import java.util.regex.Matcher; | |
| 8 | import java.util.regex.Pattern; | |
| 9 | ||
| 10 | import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS; | |
| 11 | ||
| 12 | abstract class BasedSequenceParser { | |
| 13 | /** | |
| 14 | * Shared syntax between subclasses: a letter followed by zero or more | |
| 15 | * alphanumeric characters. | |
| 16 | */ | |
| 17 | static final String REGEX_INNER = | |
| 18 | "(\\p{Alpha}[\\p{Alnum}-_]+):(\\p{Alpha}[\\p{Alnum}-_]+)"; | |
| 19 | ||
| 20 | private final String mTypeName; | |
| 21 | private final String mIdName; | |
| 22 | ||
| 23 | BasedSequenceParser( final String text ) { | |
| 24 | final var matcher = createMatcher( text ); | |
| 25 | ||
| 26 | if( matcher.find() ) { | |
| 27 | mTypeName = matcher.group( 1 ); | |
| 28 | mIdName = matcher.group( 2 ); | |
| 29 | } | |
| 30 | else { | |
| 31 | mTypeName = null; | |
| 32 | mIdName = null; | |
| 33 | } | |
| 34 | } | |
| 35 | ||
| 36 | static Pattern asPattern( final String regex ) { | |
| 37 | return Pattern.compile( regex, UNICODE_CHARACTER_CLASS ); | |
| 38 | } | |
| 39 | ||
| 40 | abstract Pattern getPattern(); | |
| 41 | ||
| 42 | /** | |
| 43 | * Creates a regular expression pattern matcher that can extract the | |
| 44 | * reference elements from text. | |
| 45 | * | |
| 46 | * @param text The text containing an anchor or cross-reference to an anchor. | |
| 47 | * @return The {@link Matcher} to use when extracting the text elements. | |
| 48 | */ | |
| 49 | Matcher createMatcher( final String text ) { | |
| 50 | return getPattern().matcher( text ); | |
| 51 | } | |
| 52 | ||
| 53 | String getTypeName() { | |
| 54 | return mTypeName; | |
| 55 | } | |
| 56 | ||
| 57 | String getIdName() { | |
| 58 | return mIdName; | |
| 59 | } | |
| 60 | } | |
| 1 | 61 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.util.ast.Node; | |
| 8 | import com.vladsch.flexmark.util.sequence.BasedSequence; | |
| 9 | ||
| 10 | import java.util.regex.Pattern; | |
| 11 | ||
| 12 | import static com.keenwrite.processors.markdown.extensions.common.EmptyNode.EMPTY_NODE; | |
| 13 | ||
| 14 | class BasedSequenceXrefParser extends BasedSequenceParser { | |
| 15 | private static final String REGEX = STR. "\\[@\{ REGEX_INNER }]" ; | |
| 16 | private static final Pattern PATTERN = asPattern( REGEX ); | |
| 17 | ||
| 18 | private BasedSequenceXrefParser( final String text ) { | |
| 19 | super( text ); | |
| 20 | } | |
| 21 | ||
| 22 | static BasedSequenceXrefParser parse( final BasedSequence chars ) { | |
| 23 | return new BasedSequenceXrefParser( chars.toString() ); | |
| 24 | } | |
| 25 | ||
| 26 | @Override | |
| 27 | Pattern getPattern() { | |
| 28 | return PATTERN; | |
| 29 | } | |
| 30 | ||
| 31 | Node toNode() { | |
| 32 | final var typeName = getTypeName(); | |
| 33 | final var idName = getIdName(); | |
| 34 | ||
| 35 | return typeName == null || idName == null | |
| 36 | ? EMPTY_NODE | |
| 37 | : new AnchorXrefNode( typeName, idName ); | |
| 38 | } | |
| 39 | } | |
| 1 | 40 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.common.MarkdownRendererExtension; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 9 | import com.vladsch.flexmark.parser.Parser.Builder; | |
| 10 | ||
| 11 | /** | |
| 12 | * Responsible for processing {@code {@type:id}} anchors and their corresponding | |
| 13 | * {@code [@type:id]} cross-references. | |
| 14 | */ | |
| 15 | public final class CrossReferenceExtension extends MarkdownRendererExtension { | |
| 16 | /** | |
| 17 | * Use {@link #create()}. | |
| 18 | */ | |
| 19 | private CrossReferenceExtension() {} | |
| 20 | ||
| 21 | /** | |
| 22 | * Returns a new {@link CrossReferenceExtension}. | |
| 23 | * | |
| 24 | * @return An extension capable of parsing cross-reference syntax. | |
| 25 | */ | |
| 26 | public static CrossReferenceExtension create() { | |
| 27 | return new CrossReferenceExtension(); | |
| 28 | } | |
| 29 | ||
| 30 | @Override | |
| 31 | public void extend( final Builder builder ) { | |
| 32 | builder.linkRefProcessorFactory( new AnchorXrefProcessorFactory() ); | |
| 33 | builder.customDelimiterProcessor( new AnchorNameDelimiterProcessor() ); | |
| 34 | } | |
| 35 | ||
| 36 | @Override | |
| 37 | protected NodeRendererFactory createNodeRendererFactory() { | |
| 38 | return new CrossReferencesNodeRendererFactory(); | |
| 39 | } | |
| 40 | } | |
| 1 | 41 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.vladsch.flexmark.html.HtmlWriter; | |
| 8 | ||
| 9 | /** | |
| 10 | * Responsible for generating anchor links, either named or cross-referenced. | |
| 11 | */ | |
| 12 | public interface CrossReferenceNode { | |
| 13 | String getTypeName(); | |
| 14 | ||
| 15 | String getIdName(); | |
| 16 | ||
| 17 | String getRefAttrName(); | |
| 18 | ||
| 19 | /** | |
| 20 | * Writes the HTML representation for this cross-reference node. | |
| 21 | * | |
| 22 | * @param html The HTML tag is written to the {@link HtmlWriter}. | |
| 23 | */ | |
| 24 | default void write( final HtmlWriter html ) { | |
| 25 | final var type = getTypeName(); | |
| 26 | final var id = getIdName(); | |
| 27 | final var attr = getRefAttrName(); | |
| 28 | ||
| 29 | final var clazz = STR. "class=\"\{ attr }\"" ; | |
| 30 | final var dataType = STR. "data-type=\"\{ type }\"" ; | |
| 31 | final var refId = STR. "\{ attr }=\"\{ id }\"" ; | |
| 32 | ||
| 33 | html.raw( STR. "<a \{ clazz } \{ dataType } \{ refId } />" ); | |
| 34 | } | |
| 35 | } | |
| 1 | 36 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 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.NodeRenderingHandler; | |
| 11 | ||
| 12 | import java.util.Arrays; | |
| 13 | import java.util.HashSet; | |
| 14 | import java.util.Set; | |
| 15 | ||
| 16 | /** | |
| 17 | * Responsible for rendering HTML elements that correspond to cross-references. | |
| 18 | */ | |
| 19 | class CrossReferencesNodeRenderer implements NodeRenderer { | |
| 20 | @Override | |
| 21 | public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { | |
| 22 | return new HashSet<>( Arrays.asList( | |
| 23 | new NodeRenderingHandler<>( AnchorNameNode.class, this::render ), | |
| 24 | new NodeRenderingHandler<>( AnchorXrefNode.class, this::render ) | |
| 25 | ) ); | |
| 26 | } | |
| 27 | ||
| 28 | private void render( | |
| 29 | final CrossReferenceNode node, | |
| 30 | final NodeRendererContext context, | |
| 31 | final HtmlWriter html ) { | |
| 32 | node.write( html ); | |
| 33 | } | |
| 34 | } | |
| 1 | 35 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.common.MarkdownNodeRendererFactory; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRenderer; | |
| 9 | import com.vladsch.flexmark.util.data.DataHolder; | |
| 10 | ||
| 11 | class CrossReferencesNodeRendererFactory extends MarkdownNodeRendererFactory { | |
| 12 | @Override | |
| 13 | protected NodeRenderer createNodeRenderer( final DataHolder options ) { | |
| 14 | return new CrossReferencesNodeRenderer(); | |
| 15 | } | |
| 16 | } | |
| 1 | 17 |
| 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.ProcessorContext; | |
| 6 | import com.keenwrite.processors.markdown.extensions.HtmlRendererAdapter; | |
| 7 | import com.keenwrite.processors.markdown.extensions.tex.TexNodeRenderer.Factory; | |
| 8 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 9 | import com.vladsch.flexmark.parser.Parser; | |
| 10 | import com.vladsch.flexmark.util.data.MutableDataHolder; | |
| 11 | import org.jetbrains.annotations.NotNull; | |
| 12 | ||
| 13 | import java.util.function.Function; | |
| 14 | ||
| 15 | import static com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 16 | ||
| 17 | /** | |
| 18 | * Responsible for wrapping delimited TeX code in Markdown into an XML element | |
| 19 | * that the HTML renderer can handle. For example, {@code $E=mc^2$} becomes | |
| 20 | * {@code <tex>E=mc^2</tex>} when passed to HTML renderer. The HTML renderer | |
| 21 | * is responsible for converting the TeX code for display. This avoids inserting | |
| 22 | * SVG code into the Markdown document, which the parser would then have to | |
| 23 | * iterate---a <em>very</em> wasteful operation that impacts front-end | |
| 24 | * performance. | |
| 25 | */ | |
| 26 | public class TeXExtension extends HtmlRendererAdapter | |
| 27 | implements ParserExtension { | |
| 28 | ||
| 29 | /** | |
| 30 | * Responsible for pre-parsing the input. | |
| 31 | */ | |
| 32 | private final Function<String, String> mEvaluator; | |
| 33 | ||
| 34 | /** | |
| 35 | * Controls how the node renderer produces TeX code within HTML output. | |
| 36 | */ | |
| 37 | private final ExportFormat mExportFormat; | |
| 38 | ||
| 39 | private TeXExtension( | |
| 40 | final Function<String, String> evaluator, final ProcessorContext context ) { | |
| 41 | mEvaluator = evaluator; | |
| 42 | mExportFormat = context.getExportFormat(); | |
| 43 | } | |
| 44 | ||
| 45 | /** | |
| 46 | * Creates an extension capable of handling delimited TeX code in Markdown. | |
| 47 | * | |
| 48 | * @return The new {@link TeXExtension}, never {@code null}. | |
| 49 | */ | |
| 50 | public static TeXExtension create( | |
| 51 | final Function<String, String> evaluator, final ProcessorContext context ) { | |
| 52 | return new TeXExtension( evaluator, context ); | |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| 56 | * Adds the TeX extension for HTML document export types. | |
| 57 | * | |
| 58 | * @param builder The document builder. | |
| 59 | * @param rendererType Indicates the document type to be built. | |
| 60 | */ | |
| 61 | @Override | |
| 62 | public void extend( @NotNull final HtmlRenderer.Builder builder, | |
| 63 | @NotNull final String rendererType ) { | |
| 64 | if( "HTML".equalsIgnoreCase( rendererType ) ) { | |
| 65 | builder.nodeRendererFactory( new Factory( mExportFormat, mEvaluator ) ); | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | @Override | |
| 70 | public void extend( final Parser.Builder builder ) { | |
| 71 | builder.customDelimiterProcessor( new TeXInlineDelimiterProcessor() ); | |
| 72 | } | |
| 73 | ||
| 74 | @Override | |
| 75 | public void parserOptions( final MutableDataHolder options ) { | |
| 76 | } | |
| 77 | } | |
| 78 | 1 |
| 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 | } | |
| 84 | 1 |
| 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.ProcessorContext; | |
| 6 | import com.keenwrite.processors.markdown.extensions.common.MarkdownRendererExtension; | |
| 7 | import com.keenwrite.processors.markdown.extensions.tex.TexNodeRenderer.TexNodeRendererFactory; | |
| 8 | import com.vladsch.flexmark.html.renderer.NodeRendererFactory; | |
| 9 | import com.vladsch.flexmark.parser.Parser; | |
| 10 | ||
| 11 | import java.util.function.Function; | |
| 12 | ||
| 13 | /** | |
| 14 | * Responsible for wrapping delimited TeX code in Markdown into an XML element | |
| 15 | * that the HTML renderer can handle. For example, {@code $E=mc^2$} becomes | |
| 16 | * {@code <tex>E=mc^2</tex>} when passed to HTML renderer. The HTML renderer | |
| 17 | * is responsible for converting the TeX code for display. This avoids inserting | |
| 18 | * SVG code into the Markdown document, which the parser would then have to | |
| 19 | * iterate---a <em>very</em> wasteful operation that impacts front-end | |
| 20 | * performance. | |
| 21 | */ | |
| 22 | public class TexExtension extends MarkdownRendererExtension { | |
| 23 | /** | |
| 24 | * Responsible for pre-parsing the input. | |
| 25 | */ | |
| 26 | private final Function<String, String> mEvaluator; | |
| 27 | ||
| 28 | /** | |
| 29 | * Controls how the node renderer produces TeX code within HTML output. | |
| 30 | */ | |
| 31 | private final ExportFormat mExportFormat; | |
| 32 | ||
| 33 | private TexExtension( | |
| 34 | final Function<String, String> evaluator, | |
| 35 | final ProcessorContext context ) { | |
| 36 | mEvaluator = evaluator; | |
| 37 | mExportFormat = context.getExportFormat(); | |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * Creates an extension capable of handling delimited TeX code in Markdown. | |
| 42 | * | |
| 43 | * @return The new {@link TexExtension}, never {@code null}. | |
| 44 | */ | |
| 45 | public static TexExtension create( | |
| 46 | final Function<String, String> evaluator, final ProcessorContext context ) { | |
| 47 | return new TexExtension( evaluator, context ); | |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Creates the TeX {@link NodeRendererFactory} for HTML document export types. | |
| 52 | */ | |
| 53 | @Override | |
| 54 | public NodeRendererFactory createNodeRendererFactory() { | |
| 55 | return new TexNodeRendererFactory( mExportFormat, mEvaluator ); | |
| 56 | } | |
| 57 | ||
| 58 | @Override | |
| 59 | public void extend( final Parser.Builder builder ) { | |
| 60 | builder.customDelimiterProcessor( new TexInlineDelimiterProcessor() ); | |
| 61 | } | |
| 62 | } | |
| 1 | 63 |
| 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 | class TexInlineDelimiterProcessor implements DelimiterProcessor { | |
| 11 | ||
| 12 | @Override | |
| 13 | public void process( | |
| 14 | final Delimiter opener, | |
| 15 | final Delimiter closer, | |
| 16 | final int delimitersUsed ) { | |
| 17 | final var node = new TexNode( opener, closer ); | |
| 18 | opener.moveNodesBetweenDelimitersTo( node, closer ); | |
| 19 | } | |
| 20 | ||
| 21 | @Override | |
| 22 | public char getOpeningCharacter() { | |
| 23 | return '$'; | |
| 24 | } | |
| 25 | ||
| 26 | @Override | |
| 27 | public char getClosingCharacter() { | |
| 28 | return '$'; | |
| 29 | } | |
| 30 | ||
| 31 | @Override | |
| 32 | public int getMinLength() { | |
| 33 | return 1; | |
| 34 | } | |
| 35 | ||
| 36 | /** | |
| 37 | * Allow for $ or $$. | |
| 38 | * | |
| 39 | * @param opener One or more opening delimiter characters. | |
| 40 | * @param closer One or more closing delimiter characters. | |
| 41 | * @return The number of delimiters to use to determine whether a valid | |
| 42 | * opening delimiter expression is found. | |
| 43 | */ | |
| 44 | @Override | |
| 45 | public int getDelimiterUse( | |
| 46 | final DelimiterRun opener, | |
| 47 | final DelimiterRun closer ) { | |
| 48 | return 1; | |
| 49 | } | |
| 50 | ||
| 51 | @Override | |
| 52 | public boolean canBeOpener( | |
| 53 | final String before, | |
| 54 | final String after, | |
| 55 | final boolean leftFlanking, | |
| 56 | final boolean rightFlanking, | |
| 57 | final boolean beforeIsPunctuation, | |
| 58 | final boolean afterIsPunctuation, | |
| 59 | final boolean beforeIsWhitespace, | |
| 60 | final boolean afterIsWhiteSpace ) { | |
| 61 | return leftFlanking; | |
| 62 | } | |
| 63 | ||
| 64 | @Override | |
| 65 | public boolean canBeCloser( | |
| 66 | final String before, | |
| 67 | final String after, | |
| 68 | final boolean leftFlanking, | |
| 69 | final boolean rightFlanking, | |
| 70 | final boolean beforeIsPunctuation, | |
| 71 | final boolean afterIsPunctuation, | |
| 72 | final boolean beforeIsWhitespace, | |
| 73 | final boolean afterIsWhiteSpace ) { | |
| 74 | return rightFlanking; | |
| 75 | } | |
| 76 | ||
| 77 | @Override | |
| 78 | public Node unmatchedDelimiterNode( | |
| 79 | final InlineParser inlineParser, | |
| 80 | final DelimiterRun delimiter ) { | |
| 81 | return null; | |
| 82 | } | |
| 83 | ||
| 84 | @Override | |
| 85 | public boolean skipNonOpenerCloser() { | |
| 86 | return false; | |
| 87 | } | |
| 88 | } | |
| 1 | 89 |
| 7 | 7 | public class TexNode extends DelimitedNodeImpl { |
| 8 | 8 | /** |
| 9 | * TeX expression wrapped in a {@code <tex>} element. | |
| 9 | * A TeX expression wrapped in a {@code <tex>} element. | |
| 10 | 10 | */ |
| 11 | 11 | public static final String HTML_TEX = "tex"; |
| 12 | 12 | |
| 13 | public static final String TOKEN_OPEN = "$"; | |
| 14 | public static final String TOKEN_CLOSE = "$"; | |
| 13 | static final String TOKEN_OPEN = "$"; | |
| 14 | static final String TOKEN_CLOSE = "$"; | |
| 15 | 15 | |
| 16 | 16 | private final String mOpener; |
| ... | ||
| 33 | 33 | * @return Either '$' or '$$'. |
| 34 | 34 | */ |
| 35 | public String getOpeningDelimiter() { return mOpener; } | |
| 35 | public String getOpeningDelimiter() { | |
| 36 | return mOpener; | |
| 37 | } | |
| 36 | 38 | |
| 37 | 39 | /** |
| 38 | 40 | * @return Either '$' or '$$'. |
| 39 | 41 | */ |
| 40 | public String getClosingDelimiter() { return mCloser; } | |
| 42 | public String getClosingDelimiter() { | |
| 43 | return mCloser; | |
| 44 | } | |
| 41 | 45 | |
| 42 | 46 | private String getDelimiter( final Delimiter delimiter ) { |
| 30 | 30 | APPLICATION_PDF, new TexElementNodeRenderer( true ), |
| 31 | 31 | HTML_TEX_SVG, new TexSvgNodeRenderer(), |
| 32 | HTML_TEX_DELIMITED, new TexDelimNodeRenderer(), | |
| 32 | HTML_TEX_DELIMITED, new TexDelimitedNodeRenderer(), | |
| 33 | 33 | XHTML_TEX, new TexElementNodeRenderer( true ), |
| 34 | 34 | NONE, RENDERER |
| 35 | 35 | ); |
| 36 | 36 | |
| 37 | public static class Factory implements NodeRendererFactory { | |
| 37 | public static class TexNodeRendererFactory implements NodeRendererFactory { | |
| 38 | 38 | private final RendererFacade mNodeRenderer; |
| 39 | 39 | |
| 40 | public Factory( | |
| 40 | public TexNodeRendererFactory( | |
| 41 | 41 | final ExportFormat exportFormat, |
| 42 | 42 | final Function<String, String> evaluator ) { |
| 43 | mNodeRenderer = EXPORT_RENDERERS.getOrDefault( exportFormat, RENDERER ); | |
| 43 | final var format = exportFormat == null ? NONE : exportFormat; | |
| 44 | ||
| 45 | mNodeRenderer = EXPORT_RENDERERS.getOrDefault( format, RENDERER ); | |
| 44 | 46 | mNodeRenderer.setEvaluator( evaluator ); |
| 45 | 47 | } |
| ... | ||
| 129 | 131 | * Responsible for rendering a TeX node as text bracketed by $ tokens. |
| 130 | 132 | */ |
| 131 | private static class TexDelimNodeRenderer extends RendererFacade { | |
| 133 | private static class TexDelimitedNodeRenderer extends RendererFacade { | |
| 132 | 134 | void render( final TexNode node, |
| 133 | 135 | final NodeRendererContext context, |
| 11 | 11 | public class DefaultNotification implements Notification { |
| 12 | 12 | |
| 13 | private final String title; | |
| 14 | private final String content; | |
| 13 | private final String mTitle; | |
| 14 | private final String mContent; | |
| 15 | 15 | |
| 16 | 16 | /** |
| ... | ||
| 25 | 25 | final String message, |
| 26 | 26 | final Object... args ) { |
| 27 | this.title = title; | |
| 28 | this.content = MessageFormat.format( message, args ); | |
| 27 | mTitle = title; | |
| 28 | mContent = MessageFormat.format( message, args ); | |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | 31 | @Override |
| 32 | 32 | public String getTitle() { |
| 33 | return this.title; | |
| 33 | return mTitle; | |
| 34 | 34 | } |
| 35 | 35 | |
| 36 | 36 | @Override |
| 37 | 37 | public String getContent() { |
| 38 | return this.content; | |
| 38 | return mContent; | |
| 39 | 39 | } |
| 40 | 40 | |
| 2 | 2 | package com.keenwrite.service.impl; |
| 3 | 3 | |
| 4 | import com.keenwrite.config.PropertiesConfiguration; | |
| 4 | 5 | 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 | 6 | |
| 9 | 7 | import java.io.InputStreamReader; |
| ... | ||
| 19 | 17 | */ |
| 20 | 18 | public final class DefaultSettings implements Settings { |
| 21 | ||
| 22 | private static final char VALUE_SEPARATOR = ','; | |
| 23 | 19 | |
| 24 | 20 | private final PropertiesConfiguration mProperties = loadProperties(); |
| 25 | 21 | |
| 26 | public DefaultSettings() { | |
| 27 | } | |
| 22 | public DefaultSettings() {} | |
| 28 | 23 | |
| 29 | 24 | /** |
| ... | ||
| 55 | 50 | * |
| 56 | 51 | * @param property The property value to coerce. |
| 57 | * @param defaults The defaults values to use should the property be unset. | |
| 52 | * @param defaults The values to use should the property be unset. | |
| 58 | 53 | * @return The list of properties coerced from objects to strings. |
| 59 | 54 | */ |
| 60 | 55 | @Override |
| 61 | 56 | public List<String> getStringSettingList( |
| 62 | final String property, final List<String> defaults ) { | |
| 63 | return getSettings().getList( String.class, property, defaults ); | |
| 57 | final String property, final List<String> defaults ) { | |
| 58 | return getSettings().getList( property, defaults ); | |
| 64 | 59 | } |
| 65 | 60 | |
| ... | ||
| 89 | 84 | final var url = getPropertySource(); |
| 90 | 85 | final var configuration = new PropertiesConfiguration(); |
| 86 | final var encoding = getDefaultEncoding(); | |
| 91 | 87 | |
| 92 | 88 | if( url != null ) { |
| 93 | 89 | try( final var reader = new InputStreamReader( |
| 94 | url.openStream(), getDefaultEncoding() ) ) { | |
| 95 | configuration.setListDelimiterHandler( createListDelimiterHandler() ); | |
| 90 | url.openStream(), encoding ) ) { | |
| 96 | 91 | configuration.read( reader ); |
| 97 | 92 | } catch( final Exception ex ) { |
| ... | ||
| 105 | 100 | private Charset getDefaultEncoding() { |
| 106 | 101 | return Charset.defaultCharset(); |
| 107 | } | |
| 108 | ||
| 109 | private ListDelimiterHandler createListDelimiterHandler() { | |
| 110 | return new DefaultListDelimiterHandler( VALUE_SEPARATOR ); | |
| 111 | 102 | } |
| 112 | 103 | |
| 22 | 22 | import static com.keenwrite.io.SysFile.toFile; |
| 23 | 23 | import static java.lang.ProcessBuilder.Redirect.DISCARD; |
| 24 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 24 | 25 | import static java.nio.file.Files.*; |
| 25 | 26 | import static java.util.Arrays.asList; |
| ... | ||
| 184 | 185 | private void log( final Path path ) throws IOException { |
| 185 | 186 | if( exists( path ) ) { |
| 186 | log( readAllLines( path ) ); | |
| 187 | log( readAllLines( path, UTF_8 ) ); | |
| 187 | 188 | } |
| 188 | 189 | } |
| 5 | 5 | package com.keenwrite.typesetting.installer.panes; |
| 6 | 6 | |
| 7 | import com.keenwrite.ui.clipboard.Clipboard; | |
| 7 | import com.keenwrite.ui.clipboard.SystemClipboard; | |
| 8 | 8 | import javafx.geometry.Insets; |
| 9 | 9 | import javafx.scene.Node; |
| ... | ||
| 92 | 92 | // Change the label to indicate clipboard is updated. |
| 93 | 93 | copyButton.setOnAction( event -> { |
| 94 | Clipboard.write( mCommands.getText() ); | |
| 94 | SystemClipboard.write( mCommands.getText() ); | |
| 95 | 95 | copyButton.setText( get( PREFIX + ".copy.ended" ) ); |
| 96 | 96 | } ); |
| 51 | 51 | import static com.keenwrite.ui.explorer.FilePickerFactory.SelectionType; |
| 52 | 52 | import static com.keenwrite.ui.explorer.FilePickerFactory.SelectionType.*; |
| 53 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 53 | 54 | import static java.nio.file.Files.writeString; |
| 54 | 55 | import static javafx.application.Platform.runLater; |
| ... | ||
| 230 | 231 | // Processors can export binary files. In such cases, processors |
| 231 | 232 | // return null to prevent further processing. |
| 232 | return export == null ? null : writeString( sourcePath, export ); | |
| 233 | return export == null | |
| 234 | ? null | |
| 235 | : writeString( sourcePath, export, UTF_8 ); | |
| 233 | 236 | } |
| 234 | 237 | }; |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.ui.clipboard; | |
| 6 | ||
| 7 | import javafx.scene.control.TableView; | |
| 8 | import javafx.scene.input.ClipboardContent; | |
| 9 | ||
| 10 | import java.util.TreeSet; | |
| 11 | ||
| 12 | import static javafx.scene.input.Clipboard.getSystemClipboard; | |
| 13 | ||
| 14 | /** | |
| 15 | * Responsible for pasting into the computer's clipboard. | |
| 16 | */ | |
| 17 | public class Clipboard { | |
| 18 | /** | |
| 19 | * Copies the given text into the clipboard, overwriting all data. | |
| 20 | * | |
| 21 | * @param text The text to insert into the clipboard. | |
| 22 | */ | |
| 23 | public static void write( final String text ) { | |
| 24 | final var contents = new ClipboardContent(); | |
| 25 | contents.putString( text ); | |
| 26 | getSystemClipboard().setContent( contents ); | |
| 27 | } | |
| 28 | ||
| 29 | /** | |
| 30 | * Delegates to {@link #write(String)}. | |
| 31 | * | |
| 32 | * @see #write(String) | |
| 33 | */ | |
| 34 | public static void write( final StringBuilder text ) { | |
| 35 | write( text.toString() ); | |
| 36 | } | |
| 37 | ||
| 38 | /** | |
| 39 | * Copies the contents of the selected rows into the clipboard; code is from | |
| 40 | * <a href="https://stackoverflow.com/a/48126059/59087">StackOverflow</a>. | |
| 41 | * | |
| 42 | * @param table The {@link TableView} having selected rows to copy. | |
| 43 | */ | |
| 44 | public static <T> void write( final TableView<T> table ) { | |
| 45 | final var sb = new StringBuilder( 2048 ); | |
| 46 | final var rows = new TreeSet<Integer>(); | |
| 47 | final var cols = table.getColumns(); | |
| 48 | ||
| 49 | for( final var position : table.getSelectionModel().getSelectedCells() ) { | |
| 50 | rows.add( position.getRow() ); | |
| 51 | } | |
| 52 | ||
| 53 | String rSep = ""; | |
| 54 | ||
| 55 | for( final var row : rows ) { | |
| 56 | sb.append( rSep ); | |
| 57 | ||
| 58 | String cSep = ""; | |
| 59 | ||
| 60 | for( final var column : cols ) { | |
| 61 | sb.append( cSep ); | |
| 62 | ||
| 63 | final var data = column.getCellData( row ); | |
| 64 | sb.append( data == null ? "" : data.toString() ); | |
| 65 | ||
| 66 | cSep = "\t"; | |
| 67 | } | |
| 68 | ||
| 69 | rSep = "\n"; | |
| 70 | } | |
| 71 | ||
| 72 | write( sb ); | |
| 73 | } | |
| 74 | } | |
| 75 | 1 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.ui.clipboard; | |
| 6 | ||
| 7 | import javafx.scene.control.TableView; | |
| 8 | import javafx.scene.input.ClipboardContent; | |
| 9 | ||
| 10 | import java.util.TreeSet; | |
| 11 | ||
| 12 | import static javafx.scene.input.Clipboard.getSystemClipboard; | |
| 13 | ||
| 14 | /** | |
| 15 | * Responsible for pasting into the computer's clipboard. | |
| 16 | */ | |
| 17 | public class SystemClipboard { | |
| 18 | /** | |
| 19 | * Copies the given text into the clipboard, overwriting all data. | |
| 20 | * | |
| 21 | * @param text The text to insert into the clipboard. | |
| 22 | */ | |
| 23 | public static void write( final String text ) { | |
| 24 | final var contents = new ClipboardContent(); | |
| 25 | contents.putString( text ); | |
| 26 | getSystemClipboard().setContent( contents ); | |
| 27 | } | |
| 28 | ||
| 29 | /** | |
| 30 | * Delegates to {@link #write(String)}. | |
| 31 | * | |
| 32 | * @see #write(String) | |
| 33 | */ | |
| 34 | public static void write( final StringBuilder text ) { | |
| 35 | write( text.toString() ); | |
| 36 | } | |
| 37 | ||
| 38 | /** | |
| 39 | * Copies the contents of the selected rows into the clipboard; code is from | |
| 40 | * <a href="https://stackoverflow.com/a/48126059/59087">StackOverflow</a>. | |
| 41 | * | |
| 42 | * @param table The {@link TableView} having selected rows to copy. | |
| 43 | */ | |
| 44 | public static <T> void write( final TableView<T> table ) { | |
| 45 | final var sb = new StringBuilder( 2048 ); | |
| 46 | final var rows = new TreeSet<Integer>(); | |
| 47 | final var cols = table.getColumns(); | |
| 48 | ||
| 49 | for( final var position : table.getSelectionModel().getSelectedCells() ) { | |
| 50 | rows.add( position.getRow() ); | |
| 51 | } | |
| 52 | ||
| 53 | String rSep = ""; | |
| 54 | ||
| 55 | for( final var row : rows ) { | |
| 56 | sb.append( rSep ); | |
| 57 | ||
| 58 | String cSep = ""; | |
| 59 | ||
| 60 | for( final var column : cols ) { | |
| 61 | sb.append( cSep ); | |
| 62 | ||
| 63 | final var data = column.getCellData( row ); | |
| 64 | sb.append( data == null ? "" : data.toString() ); | |
| 65 | ||
| 66 | cSep = "\t"; | |
| 67 | } | |
| 68 | ||
| 69 | rSep = "\n"; | |
| 70 | } | |
| 71 | ||
| 72 | write( sb ); | |
| 73 | } | |
| 74 | } | |
| 1 | 75 |
| 6 | 6 | import com.keenwrite.preferences.Workspace; |
| 7 | 7 | import com.keenwrite.ui.actions.Keyboard; |
| 8 | import com.keenwrite.ui.clipboard.Clipboard; | |
| 8 | import com.keenwrite.ui.clipboard.SystemClipboard; | |
| 9 | 9 | import com.whitemagicsoftware.keencount.TokenizerException; |
| 10 | 10 | import javafx.beans.property.IntegerProperty; |
| ... | ||
| 136 | 136 | setOnKeyPressed( event -> { |
| 137 | 137 | if( Keyboard.isCopy( event ) ) { |
| 138 | Clipboard.write( this ); | |
| 138 | SystemClipboard.write( this ); | |
| 139 | 139 | } |
| 140 | 140 | } ); |
| 4 | 4 | import com.keenwrite.events.StatusEvent; |
| 5 | 5 | import com.keenwrite.ui.actions.Keyboard; |
| 6 | import com.keenwrite.ui.clipboard.Clipboard; | |
| 6 | import com.keenwrite.ui.clipboard.SystemClipboard; | |
| 7 | 7 | import javafx.beans.property.SimpleStringProperty; |
| 8 | 8 | import javafx.beans.property.StringProperty; |
| ... | ||
| 117 | 117 | mTable.setOnKeyPressed( event -> { |
| 118 | 118 | if( Keyboard.isCopy( event ) ) { |
| 119 | Clipboard.write( mTable ); | |
| 119 | SystemClipboard.write( mTable ); | |
| 120 | 120 | } |
| 121 | 121 | } ); |
| 327 | 327 | Wizard.typesetter.container.image.tag=${Wizard.typesetter.container.image.name}:${Wizard.typesetter.container.image.version} |
| 328 | 328 | Wizard.typesetter.container.image.url=https://repository.keenwrite.com/containers/${Wizard.typesetter.container.image.tag} |
| 329 | Wizard.typesetter.themes.version=1.8.4 | |
| 330 | Wizard.typesetter.themes.checksum=b30dd800df89c1d06b2446469e62c12252434946f950a874e1ca225719ba4efc | |
| 329 | Wizard.typesetter.themes.version=1.9.0 | |
| 330 | Wizard.typesetter.themes.checksum=aeea45b8c6008fae4f1e84b6668f43a2b7200dbcac716941e4543d0602aec59d | |
| 331 | 331 | |
| 332 | 332 | Wizard.container.install.command=Installing container using: ''{0}'' |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | /* CROSS-REFERENCES ***/ | |
| 81 | a.href::after { | |
| 82 | content: " (" attr(data-type) " reference)"; | |
| 83 | } | |
| 84 | ||
| 80 | 85 | /* ITEMIZED LISTS ***/ |
| 81 | 86 | ol, ul { |
| ... | ||
| 300 | 305 | } |
| 301 | 306 | |
| 302 | div.bubblerx:after, div.bubbletx:after { | |
| 307 | div.bubblerx::after, div.bubbletx::after { | |
| 303 | 308 | content: ""; |
| 304 | 309 | position: absolute; |
| ... | ||
| 317 | 322 | } |
| 318 | 323 | |
| 319 | div.bubbletx:after { | |
| 324 | div.bubbletx::after { | |
| 320 | 325 | right: -1em; |
| 321 | 326 | border-left: 1em solid #ccc; |
| ... | ||
| 334 | 339 | } |
| 335 | 340 | |
| 341 | /* TYPEWRITER ***/ | |
| 336 | 342 | div.typewritten { |
| 337 | 343 | font-family: monospace; |
| 338 | 344 | font-size: 16px; |
| 339 | 345 | font-weight: bold; |
| 340 | 346 | } |
| 341 | ||
| 342 | 347 | |
| 3 | 3 | |
| 4 | 4 | import com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension; |
| 5 | import com.keenwrite.processors.markdown.extensions.references.CrossReferenceExtension; | |
| 5 | 6 | import com.vladsch.flexmark.ext.definition.DefinitionExtension; |
| 6 | 7 | import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension; |
| ... | ||
| 54 | 55 | extensions.add( TablesExtension.create() ); |
| 55 | 56 | extensions.add( FencedDivExtension.create() ); |
| 57 | extensions.add( CrossReferenceExtension.create() ); | |
| 56 | 58 | |
| 57 | 59 | return extensions; |
| 8 | 8 | import java.io.OutputStreamWriter; |
| 9 | 9 | |
| 10 | import static com.keenwrite.constants.Constants.TEMPORARY_DIRECTORY; | |
| 10 | 11 | import static java.io.File.separator; |
| 11 | 12 | import static java.lang.String.format; |
| 12 | 13 | import static java.nio.charset.StandardCharsets.UTF_8; |
| 13 | 14 | |
| 14 | 15 | /** |
| 15 | 16 | * Tests file resource allocation. |
| 16 | 17 | */ |
| 17 | 18 | public class FileObjectTest { |
| 18 | private static final String TEMP_DIR = System.getProperty( "java.io.tmpdir" ); | |
| 19 | ||
| 20 | 19 | /** |
| 21 | 20 | * Test that resources are not exhausted. |
| ... | ||
| 30 | 29 | |
| 31 | 30 | for( int i = 0; i < 10000; i++ ) { |
| 32 | final var filename = format( "%s%s%d.txt", TEMP_DIR, separator, i ); | |
| 31 | final var filename = format( | |
| 32 | "%s%s%d.txt", TEMPORARY_DIRECTORY, separator, i | |
| 33 | ); | |
| 33 | 34 | final var fileObject = session |
| 34 | 35 | .getFileSystemManager() |
| 12 | 12 | |
| 13 | 13 | import static java.io.File.createTempFile; |
| 14 | import static java.nio.charset.StandardCharsets.UTF_8; | |
| 14 | 15 | import static java.nio.file.StandardOpenOption.APPEND; |
| 15 | 16 | import static java.nio.file.StandardOpenOption.CREATE; |
| ... | ||
| 44 | 45 | thread.start(); |
| 45 | 46 | service.addListener( listener ); |
| 46 | Files.writeString( file.toPath(), text, CREATE, APPEND ); | |
| 47 | Files.writeString( file.toPath(), text, UTF_8, CREATE, APPEND ); | |
| 47 | 48 | semaphor.acquire(); |
| 48 | 49 | service.stop(); |
| 27 | 27 | } |
| 28 | 28 | |
| 29 | private static final String URL = | |
| 29 | private static final String URL_BINARY = | |
| 30 | 30 | "https://keenwrite.com/downloads/KeenWrite.exe"; |
| 31 | 31 | |
| ... | ||
| 45 | 45 | file.deleteOnExit(); |
| 46 | 46 | |
| 47 | final var token = open( URL ); | |
| 47 | final var token = open( URL_BINARY ); | |
| 48 | 48 | final var executor = Executors.newFixedThreadPool( 1 ); |
| 49 | 49 | final var result = token.download( file, listener ); |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown; | |
| 3 | ||
| 4 | import com.keenwrite.editors.common.Caret; | |
| 5 | import com.keenwrite.processors.Processor; | |
| 6 | import com.keenwrite.processors.ProcessorContext; | |
| 7 | import com.keenwrite.processors.markdown.extensions.ImageLinkExtension; | |
| 8 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 9 | import com.vladsch.flexmark.parser.Parser; | |
| 10 | import org.junit.jupiter.api.Test; | |
| 11 | ||
| 12 | import java.io.File; | |
| 13 | import java.net.URI; | |
| 14 | import java.nio.file.Path; | |
| 15 | import java.nio.file.Paths; | |
| 16 | import java.util.LinkedHashMap; | |
| 17 | import java.util.List; | |
| 18 | import java.util.Map; | |
| 19 | ||
| 20 | import static com.keenwrite.ExportFormat.XHTML_TEX; | |
| 21 | import static com.keenwrite.constants.Constants.DOCUMENT_DEFAULT; | |
| 22 | import static java.lang.String.format; | |
| 23 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 24 | import static org.junit.jupiter.api.Assertions.assertNotNull; | |
| 25 | ||
| 26 | /** | |
| 27 | * Responsible for testing that linked images render into HTML according to | |
| 28 | * the {@link ImageLinkExtension} rules. | |
| 29 | */ | |
| 30 | @SuppressWarnings( "SameParameterValue" ) | |
| 31 | public class ImageLinkExtensionTest { | |
| 32 | private static final String UIR_DIR = "images"; | |
| 33 | private static final String URI_FILE = "kitten"; | |
| 34 | private static final String URI_PATH = UIR_DIR + '/' + URI_FILE; | |
| 35 | private static final String PATH_KITTEN_JPG = URI_PATH + ".jpg"; | |
| 36 | private static final String PATH_KITTEN_PNG = URI_PATH + ".png"; | |
| 37 | ||
| 38 | private static final Map<String, String> IMAGES = new LinkedHashMap<>(); | |
| 39 | ||
| 40 | static { | |
| 41 | add( PATH_KITTEN_PNG, URI_FILE ); | |
| 42 | add( PATH_KITTEN_PNG, URI_PATH ); | |
| 43 | add( PATH_KITTEN_PNG, PATH_KITTEN_PNG ); | |
| 44 | add( PATH_KITTEN_JPG, PATH_KITTEN_JPG ); | |
| 45 | add( "//placekitten.com/200/200", "//placekitten.com/200/200" ); | |
| 46 | add( "ftp://placekitten.com/200/200", "ftp://placekitten.com/200/200" ); | |
| 47 | add( "http://placekitten.com/200/200", "http://placekitten.com/200/200" ); | |
| 48 | add( "https://placekitten.com/200/200", "https://placekitten.com/200/200" ); | |
| 49 | } | |
| 50 | ||
| 51 | private static void add( final String expected, final String actual ) { | |
| 52 | IMAGES.put( toMd( actual ), toHtml( expected ) ); | |
| 53 | } | |
| 54 | ||
| 55 | private static String toMd( final String resource ) { | |
| 56 | return format( "", resource ); | |
| 57 | } | |
| 58 | ||
| 59 | private static String toHtml( final String url ) { | |
| 60 | return format( | |
| 61 | "<p><img src=\"%s\" alt=\"Tooltip\" title=\"Title\" /></p>%n", url ); | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Test that the key URIs present in the {@link #IMAGES} map are rendered | |
| 66 | * as the value URIs present in the same map. | |
| 67 | */ | |
| 68 | @Test | |
| 69 | void test_ImageLookup_RelativePathWithExtension_ResolvedSuccessfully() { | |
| 70 | final var resource = getResourcePath( PATH_KITTEN_PNG ); | |
| 71 | final var imagePath = new File( PATH_KITTEN_PNG ).toPath(); | |
| 72 | final var subpaths = resource.getNameCount() - imagePath.getNameCount(); | |
| 73 | final var subpath = resource.subpath( 0, subpaths ); | |
| 74 | ||
| 75 | final var root = resource.getRoot(); | |
| 76 | assertNotNull( root ); | |
| 77 | ||
| 78 | final var resolved = root.resolve( subpath ); | |
| 79 | final var doc = resolved.toString(); | |
| 80 | ||
| 81 | // The root component isn't considered part of the path, so add it back. | |
| 82 | final var documentPath = Path.of( doc, DOCUMENT_DEFAULT.getName() ); | |
| 83 | final var imagesDir = Path.of( "images" ); | |
| 84 | final var context = createProcessorContext( documentPath, imagesDir ); | |
| 85 | final var extension = ImageLinkExtension.create( context ); | |
| 86 | final var extensions = List.of( extension ); | |
| 87 | final var pBuilder = Parser.builder(); | |
| 88 | final var hBuilder = HtmlRenderer.builder(); | |
| 89 | final var parser = pBuilder.extensions( extensions ).build(); | |
| 90 | final var renderer = hBuilder.extensions( extensions ).build(); | |
| 91 | ||
| 92 | assertNotNull( parser ); | |
| 93 | assertNotNull( renderer ); | |
| 94 | ||
| 95 | for( final var entry : IMAGES.entrySet() ) { | |
| 96 | final var key = entry.getKey(); | |
| 97 | final var node = parser.parse( key ); | |
| 98 | final var expectedHtml = entry.getValue(); | |
| 99 | final var actualHtml = renderer.render( node ); | |
| 100 | ||
| 101 | assertEquals( expectedHtml, actualHtml ); | |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Creates a new {@link ProcessorContext} for the given file name path. | |
| 107 | * | |
| 108 | * @param inputPath Fully qualified path to the file name. | |
| 109 | * @return A context used for creating new {@link Processor} instances. | |
| 110 | */ | |
| 111 | private ProcessorContext createProcessorContext( | |
| 112 | final Path inputPath, final Path imagesDir ) { | |
| 113 | return ProcessorContext | |
| 114 | .builder() | |
| 115 | .with( ProcessorContext.Mutator::setSourcePath, inputPath ) | |
| 116 | .with( ProcessorContext.Mutator::setExportFormat, XHTML_TEX ) | |
| 117 | .with( ProcessorContext.Mutator::setCaret, () -> Caret.builder().build() ) | |
| 118 | .with( ProcessorContext.Mutator::setImageDir, imagesDir::toFile ) | |
| 119 | .build(); | |
| 120 | } | |
| 121 | ||
| 122 | private static URI toUri( final String path ) { | |
| 123 | try { | |
| 124 | return Path.of( path ).toUri(); | |
| 125 | } catch( final Exception ex ) { | |
| 126 | throw new RuntimeException( ex ); | |
| 127 | } | |
| 128 | } | |
| 129 | ||
| 130 | private static Path getResourcePath( final String path ) { | |
| 131 | return Paths.get( toUri( path ) ); | |
| 132 | } | |
| 133 | } | |
| 134 | 1 |
| 1 | /* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */ | |
| 2 | package com.keenwrite.processors.markdown.extensions.images; | |
| 3 | ||
| 4 | import com.keenwrite.editors.common.Caret; | |
| 5 | import com.keenwrite.processors.Processor; | |
| 6 | import com.keenwrite.processors.ProcessorContext; | |
| 7 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 8 | import com.vladsch.flexmark.parser.Parser; | |
| 9 | import org.junit.jupiter.api.Test; | |
| 10 | ||
| 11 | import java.io.File; | |
| 12 | import java.net.URI; | |
| 13 | import java.nio.file.Path; | |
| 14 | import java.nio.file.Paths; | |
| 15 | import java.util.LinkedHashMap; | |
| 16 | import java.util.List; | |
| 17 | import java.util.Map; | |
| 18 | ||
| 19 | import static com.keenwrite.ExportFormat.XHTML_TEX; | |
| 20 | import static com.keenwrite.constants.Constants.DOCUMENT_DEFAULT; | |
| 21 | import static java.lang.String.format; | |
| 22 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 23 | import static org.junit.jupiter.api.Assertions.assertNotNull; | |
| 24 | ||
| 25 | /** | |
| 26 | * Responsible for testing that linked images render into HTML according to | |
| 27 | * the {@link ImageLinkExtension} rules. | |
| 28 | */ | |
| 29 | @SuppressWarnings( "SameParameterValue" ) | |
| 30 | public class ImageLinkExtensionTest { | |
| 31 | private static final String UIR_DIR = "images"; | |
| 32 | private static final String URI_FILE = "kitten"; | |
| 33 | private static final String URI_PATH = UIR_DIR + '/' + URI_FILE; | |
| 34 | private static final String PATH_KITTEN_JPG = URI_PATH + ".jpg"; | |
| 35 | private static final String PATH_KITTEN_PNG = URI_PATH + ".png"; | |
| 36 | ||
| 37 | private static final Map<String, String> IMAGES = new LinkedHashMap<>(); | |
| 38 | ||
| 39 | static { | |
| 40 | add( PATH_KITTEN_PNG, URI_FILE ); | |
| 41 | add( PATH_KITTEN_PNG, URI_PATH ); | |
| 42 | add( PATH_KITTEN_PNG, PATH_KITTEN_PNG ); | |
| 43 | add( PATH_KITTEN_JPG, PATH_KITTEN_JPG ); | |
| 44 | add( "//placekitten.com/200/200", "//placekitten.com/200/200" ); | |
| 45 | add( "ftp://placekitten.com/200/200", "ftp://placekitten.com/200/200" ); | |
| 46 | add( "http://placekitten.com/200/200", "http://placekitten.com/200/200" ); | |
| 47 | add( "https://placekitten.com/200/200", "https://placekitten.com/200/200" ); | |
| 48 | } | |
| 49 | ||
| 50 | private static void add( final String expected, final String actual ) { | |
| 51 | IMAGES.put( toMd( actual ), toHtml( expected ) ); | |
| 52 | } | |
| 53 | ||
| 54 | private static String toMd( final String resource ) { | |
| 55 | return format( "", resource ); | |
| 56 | } | |
| 57 | ||
| 58 | private static String toHtml( final String url ) { | |
| 59 | return format( | |
| 60 | "<p><img src=\"%s\" alt=\"Tooltip\" title=\"Title\" /></p>%n", url ); | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * Test that the key URIs present in the {@link #IMAGES} map are rendered | |
| 65 | * as the value URIs present in the same map. | |
| 66 | */ | |
| 67 | @Test | |
| 68 | void test_ImageLookup_RelativePathWithExtension_ResolvedSuccessfully() { | |
| 69 | final var resource = getResourcePath( PATH_KITTEN_PNG ); | |
| 70 | final var imagePath = new File( PATH_KITTEN_PNG ).toPath(); | |
| 71 | final var subpaths = resource.getNameCount() - imagePath.getNameCount(); | |
| 72 | final var subpath = resource.subpath( 0, subpaths ); | |
| 73 | ||
| 74 | final var root = resource.getRoot(); | |
| 75 | assertNotNull( root ); | |
| 76 | ||
| 77 | final var resolved = root.resolve( subpath ); | |
| 78 | final var doc = resolved.toString(); | |
| 79 | ||
| 80 | // The root component isn't considered part of the path, so add it back. | |
| 81 | final var documentPath = Path.of( doc, DOCUMENT_DEFAULT.getName() ); | |
| 82 | final var imagesDir = Path.of( "images" ); | |
| 83 | final var context = createProcessorContext( documentPath, imagesDir ); | |
| 84 | final var extension = ImageLinkExtension.create( context ); | |
| 85 | final var extensions = List.of( extension ); | |
| 86 | final var pBuilder = Parser.builder(); | |
| 87 | final var hBuilder = HtmlRenderer.builder(); | |
| 88 | final var parser = pBuilder.extensions( extensions ).build(); | |
| 89 | final var renderer = hBuilder.extensions( extensions ).build(); | |
| 90 | ||
| 91 | assertNotNull( parser ); | |
| 92 | assertNotNull( renderer ); | |
| 93 | ||
| 94 | for( final var entry : IMAGES.entrySet() ) { | |
| 95 | final var key = entry.getKey(); | |
| 96 | final var node = parser.parse( key ); | |
| 97 | final var expectedHtml = entry.getValue(); | |
| 98 | final var actualHtml = renderer.render( node ); | |
| 99 | ||
| 100 | assertEquals( expectedHtml, actualHtml ); | |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | /** | |
| 105 | * Creates a new {@link ProcessorContext} for the given file name path. | |
| 106 | * | |
| 107 | * @param inputPath Fully qualified path to the file name. | |
| 108 | * @return A context used for creating new {@link Processor} instances. | |
| 109 | */ | |
| 110 | private ProcessorContext createProcessorContext( | |
| 111 | final Path inputPath, final Path imagesDir ) { | |
| 112 | return ProcessorContext | |
| 113 | .builder() | |
| 114 | .with( ProcessorContext.Mutator::setSourcePath, inputPath ) | |
| 115 | .with( ProcessorContext.Mutator::setExportFormat, XHTML_TEX ) | |
| 116 | .with( ProcessorContext.Mutator::setCaret, () -> Caret.builder().build() ) | |
| 117 | .with( ProcessorContext.Mutator::setImageDir, imagesDir::toFile ) | |
| 118 | .build(); | |
| 119 | } | |
| 120 | ||
| 121 | private static URI toUri( final String path ) { | |
| 122 | try { | |
| 123 | return Path.of( path ).toUri(); | |
| 124 | } catch( final Exception ex ) { | |
| 125 | throw new RuntimeException( ex ); | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | private static Path getResourcePath( final String path ) { | |
| 130 | return Paths.get( toUri( path ) ); | |
| 131 | } | |
| 132 | } | |
| 1 | 133 |
| 1 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 2 | * | |
| 3 | * SPDX-License-Identifier: MIT | |
| 4 | */ | |
| 5 | package com.keenwrite.processors.markdown.extensions.references; | |
| 6 | ||
| 7 | import com.keenwrite.processors.markdown.extensions.captions.CaptionExtension; | |
| 8 | import com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension; | |
| 9 | import com.keenwrite.processors.markdown.extensions.tex.TexExtension; | |
| 10 | import com.vladsch.flexmark.ext.definition.DefinitionExtension; | |
| 11 | import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension; | |
| 12 | import com.vladsch.flexmark.ext.superscript.SuperscriptExtension; | |
| 13 | import com.vladsch.flexmark.ext.tables.TablesExtension; | |
| 14 | import com.vladsch.flexmark.html.HtmlRenderer; | |
| 15 | import com.vladsch.flexmark.parser.Parser; | |
| 16 | import com.vladsch.flexmark.parser.Parser.ParserExtension; | |
| 17 | import org.junit.jupiter.params.ParameterizedTest; | |
| 18 | import org.junit.jupiter.params.provider.Arguments; | |
| 19 | import org.junit.jupiter.params.provider.MethodSource; | |
| 20 | ||
| 21 | import java.util.LinkedList; | |
| 22 | import java.util.List; | |
| 23 | import java.util.stream.Stream; | |
| 24 | ||
| 25 | import static com.keenwrite.ExportFormat.XHTML_TEX; | |
| 26 | import static com.keenwrite.processors.ProcessorContext.Mutator; | |
| 27 | import static com.keenwrite.processors.ProcessorContext.builder; | |
| 28 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
| 29 | ||
| 30 | @SuppressWarnings( "SpellCheckingInspection" ) | |
| 31 | public class CaptionsAndCrossReferencesExtensionTest { | |
| 32 | @ParameterizedTest | |
| 33 | @MethodSource( "testDocuments" ) | |
| 34 | public void test_References_Documents_Html( | |
| 35 | final String input, final String expected | |
| 36 | ) { | |
| 37 | final var pBuilder = Parser.builder(); | |
| 38 | final var hBuilder = HtmlRenderer.builder(); | |
| 39 | final var extensions = createExtensions(); | |
| 40 | final var parser = pBuilder.extensions( extensions ).build(); | |
| 41 | final var renderer = hBuilder.extensions( extensions ).build(); | |
| 42 | ||
| 43 | final var document = parser.parse( input ); | |
| 44 | final var actual = renderer.render( document ); | |
| 45 | ||
| 46 | assertEquals( expected, actual ); | |
| 47 | } | |
| 48 | ||
| 49 | private static Stream<Arguments> testDocuments() { | |
| 50 | return Stream.of( | |
| 51 | args( | |
| 52 | """ | |
| 53 | {#fig:cats} [@fig:cats] | |
| 54 | {#table:dogs} [@table:dogs] | |
| 55 | {#ocean:whale-01} [@ocean:whale-02] | |
| 56 | """, | |
| 57 | """ | |
| 58 | <p><a class="name" data-type="fig" name="cats" /> <a class="href" data-type="fig" href="#cats" /> | |
| 59 | <a class="name" data-type="table" name="dogs" /> <a class="href" data-type="table" href="#dogs" /> | |
| 60 | <a class="name" data-type="ocean" name="whale-01" /> <a class="href" data-type="ocean" href="#whale-02" /></p> | |
| 61 | """ | |
| 62 | ), | |
| 63 | args( | |
| 64 | """ | |
| 65 | {#日本:w0mbatß} | |
| 66 | [@日本:w0mbatß] | |
| 67 | """, | |
| 68 | """ | |
| 69 | <p><a class="name" data-type="日本" name="w0mbatß" /> | |
| 70 | <a class="href" data-type="日本" href="#w0mbatß" /></p> | |
| 71 | """ | |
| 72 | ), | |
| 73 | args( | |
| 74 | """ | |
| 75 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. | |
| 76 | {#fig:cats} Sed do eiusmod tempor incididunt ut | |
| 77 | labore et dolore magna aliqua. Ut enim ad minim veniam, | |
| 78 | quis nostrud exercitation ullamco laboris nisi ut aliquip | |
| 79 | ex ea commodo consequat. [@fig:cats] | |
| 80 | """, | |
| 81 | """ | |
| 82 | <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. | |
| 83 | <a class="name" data-type="fig" name="cats" /> Sed do eiusmod tempor incididunt ut | |
| 84 | labore et dolore magna aliqua. Ut enim ad minim veniam, | |
| 85 | quis nostrud exercitation ullamco laboris nisi ut aliquip | |
| 86 | ex ea commodo consequat. <a class="href" data-type="fig" href="#cats" /></p> | |
| 87 | """ | |
| 88 | ), | |
| 89 | args( | |
| 90 | """ | |
| 91 | {#note:advancement} Advancement isn't | |
| 92 | measured by the ingenuity of inventions, but by humanity's ability | |
| 93 | to anticipate and forfend dire aftermaths *before* using them. | |
| 94 | ||
| 95 | [@note:advancement] | |
| 96 | ||
| 97 | To what end? | |
| 98 | """, | |
| 99 | """ | |
| 100 | <p><a class="name" data-type="note" name="advancement" /> Advancement isn't | |
| 101 | measured by the ingenuity of inventions, but by humanity's ability | |
| 102 | to anticipate and forfend dire aftermaths <em>before</em> using them.</p> | |
| 103 | <p><a class="href" data-type="note" href="#advancement" /></p> | |
| 104 | <p>To what end?</p> | |
| 105 | """ | |
| 106 | ), | |
| 107 | args( | |
| 108 | """ | |
| 109 | $E=mc^2$ {#eq:label} | |
| 110 | """, | |
| 111 | """ | |
| 112 | <p><tex>$E=mc^2$</tex> <a class="name" data-type="eq" name="label" /></p> | |
| 113 | """ | |
| 114 | ), | |
| 115 | args( | |
| 116 | """ | |
| 117 | $$E=mc^2$$ {#eq:label} | |
| 118 | """, | |
| 119 | """ | |
| 120 | <p><tex>$$E=mc^2$$</tex> <a class="name" data-type="eq" name="label" /></p> | |
| 121 | """ | |
| 122 | ), | |
| 123 | args( | |
| 124 | """ | |
| 125 | $$E=mc^2$$ | |
| 126 | ||
| 127 | :: Caption {#eqn:energy} | |
| 128 | """, | |
| 129 | """ | |
| 130 | <p><span class="caption">Caption </span><a class="name" data-type="eqn" name="energy" /></p> | |
| 131 | <p><tex>$$E=mc^2$$</tex></p> | |
| 132 | """ | |
| 133 | ), | |
| 134 | args( | |
| 135 | """ | |
| 136 | ``` haskell | |
| 137 | main :: IO () | |
| 138 | ``` | |
| 139 | ||
| 140 | :: Source code caption {#listing:haskell1} | |
| 141 | """, | |
| 142 | """ | |
| 143 | <p><span class="caption">Source code caption </span><a class="name" data-type="listing" name="haskell1" /></p> | |
| 144 | <pre><code class="language-haskell">main :: IO () | |
| 145 | </code></pre> | |
| 146 | """ | |
| 147 | ), | |
| 148 | args( | |
| 149 | """ | |
| 150 | ::: warning | |
| 151 | Do not eat processed **sugar**. | |
| 152 | ||
| 153 | Seriously. | |
| 154 | ::: | |
| 155 | ||
| 156 | :: Caption {#warning:sugar} | |
| 157 | """, | |
| 158 | """ | |
| 159 | <p><span class="caption">Caption </span><a class="name" data-type="warning" name="sugar" /></p><div class="warning"> | |
| 160 | <p>Do not eat processed <strong>sugar</strong>.</p> | |
| 161 | <p>Seriously.</p> | |
| 162 | </div> | |
| 163 | """ | |
| 164 | ), | |
| 165 | args( | |
| 166 | """ | |
| 167 |  | |
| 168 | ||
| 169 | :: Caption {#fig:label} | |
| 170 | """, | |
| 171 | """ | |
| 172 | <p><span class="caption">Caption </span><a class="name" data-type="fig" name="label" /></p> | |
| 173 | <p><img src="tunnel" alt="alt text" /></p> | |
| 174 | """ | |
| 175 | ), | |
| 176 | args( | |
| 177 | """ | |
| 178 |  | |
| 179 | ||
| 180 | :: Caption **bold** {#fig:label} *italics* | |
| 181 | """, | |
| 182 | """ | |
| 183 | <p><span class="caption">Caption <strong>bold</strong> <em>italics</em></span><a class="name" data-type="fig" name="label" /></p> | |
| 184 | <p><img src="placekitten" alt="kitteh" /></p> | |
| 185 | """ | |
| 186 | ), | |
| 187 | args( | |
| 188 | """ | |
| 189 | > I'd like to be the lucky devil who gets to burn with you. | |
| 190 | > | |
| 191 | > Well, I'm no angel, my wings have been clipped; | |
| 192 | > | |
| 193 | > I've traded my halo for horns and a whip. | |
| 194 | ||
| 195 | :: Meschiya Lake - Lucky Devil {#lyrics:blues} | |
| 196 | """, | |
| 197 | """ | |
| 198 | <p><span class="caption">Meschiya Lake - Lucky Devil </span><a class="name" data-type="lyrics" name="blues" /></p> | |
| 199 | <blockquote> | |
| 200 | <p>I'd like to be the lucky devil who gets to burn with you.</p> | |
| 201 | <p>Well, I'm no angel, my wings have been clipped;</p> | |
| 202 | <p>I've traded my halo for horns and a whip.</p> | |
| 203 | </blockquote> | |
| 204 | """ | |
| 205 | ), | |
| 206 | args( | |
| 207 | """ | |
| 208 | | a | b | c | | |
| 209 | |---|---|---| | |
| 210 | | 1 | 2 | 3 | | |
| 211 | | 4 | 5 | 6 | | |
| 212 | ||
| 213 | :: Caption {#tbl:label} | |
| 214 | """, | |
| 215 | """ | |
| 216 | <p><span class="caption">Caption </span><a class="name" data-type="tbl" name="label" /></p> | |
| 217 | <table> | |
| 218 | <thead> | |
| 219 | <tr><th>a</th><th>b</th><th>c</th></tr> | |
| 220 | </thead> | |
| 221 | <tbody> | |
| 222 | <tr><td>1</td><td>2</td><td>3</td></tr> | |
| 223 | <tr><td>4</td><td>5</td><td>6</td></tr> | |
| 224 | </tbody> | |
| 225 | </table> | |
| 226 | """ | |
| 227 | ), | |
| 228 | args( | |
| 229 | """ | |
| 230 | ``` diagram-plantuml | |
| 231 | @startuml | |
| 232 | Alice -> Bob: Request | |
| 233 | Bob --> Alice: Response | |
| 234 | @enduml | |
| 235 | ``` | |
| 236 | ||
| 237 | :: Diagram {#dia:seq1} | |
| 238 | """, | |
| 239 | """ | |
| 240 | <p><span class="caption">Diagram </span><a class="name" data-type="dia" name="seq1" /></p> | |
| 241 | <pre><code class="language-diagram-plantuml">@startuml | |
| 242 | Alice -> Bob: Request | |
| 243 | Bob --> Alice: Response | |
| 244 | @enduml | |
| 245 | </code></pre> | |
| 246 | """ | |
| 247 | ), | |
| 248 | args( | |
| 249 | """ | |
| 250 | ::: lyrics | |
| 251 | Weather hit, meltin' road. | |
| 252 | Our mama's gone, six feet cold. | |
| 253 | Gas on down to future town, | |
| 254 | Make prophecy take hold. | |
| 255 | ||
| 256 | Warnin' sign, cent'ry old: | |
| 257 | When buyin' coal, air is sold. | |
| 258 | Aim our toil, ten figure oil; | |
| 259 | Trade life on Earth for gold. | |
| 260 | ::: | |
| 261 | """, | |
| 262 | """ | |
| 263 | <div class="lyrics"> | |
| 264 | <p>Weather hit, meltin' road. | |
| 265 | Our mama's gone, six feet cold. | |
| 266 | Gas on down to future town, | |
| 267 | Make prophecy take hold.</p> | |
| 268 | <p>Warnin' sign, cent'ry old: | |
| 269 | When buyin' coal, air is sold. | |
| 270 | Aim our toil, ten figure oil; | |
| 271 | Trade life on Earth for gold.</p> | |
| 272 | </div> | |
| 273 | """ | |
| 274 | ) | |
| 275 | ); | |
| 276 | } | |
| 277 | ||
| 278 | private static Arguments args( final String in, final String out ) { | |
| 279 | return Arguments.of( in, out ); | |
| 280 | } | |
| 281 | ||
| 282 | private List<ParserExtension> createExtensions() { | |
| 283 | final var extensions = new LinkedList<ParserExtension>(); | |
| 284 | final var context = builder() | |
| 285 | .with( Mutator::setExportFormat, XHTML_TEX ) | |
| 286 | .build(); | |
| 287 | ||
| 288 | extensions.add( TexExtension.create( s -> s, context ) ); | |
| 289 | extensions.add( DefinitionExtension.create() ); | |
| 290 | extensions.add( StrikethroughSubscriptExtension.create() ); | |
| 291 | extensions.add( SuperscriptExtension.create() ); | |
| 292 | extensions.add( TablesExtension.create() ); | |
| 293 | extensions.add( FencedDivExtension.create() ); | |
| 294 | extensions.add( CrossReferenceExtension.create() ); | |
| 295 | extensions.add( CaptionExtension.create() ); | |
| 296 | ||
| 297 | return extensions; | |
| 298 | } | |
| 299 | } | |
| 1 | 300 |
| 1 | #!/usr/bin/env bash | |
| 2 | ||
| 3 | awk '{s+=$1} END {print s}' downloads/*-count.txt 2> /dev/null || echo 0 | |
| 4 | ||
| 1 | 5 |
| 1 | version.txt | |
| 1 | 2 |
| 1 | SetEnv no-gzip dont-vary | |
| 2 | ||
| 3 | <IfModule mod_rewrite.c> | |
| 4 | RewriteEngine On | |
| 5 | ||
| 6 | # Ensure the file exists before attemping to download it. | |
| 7 | RewriteCond %{REQUEST_FILENAME} -f | |
| 8 | ||
| 9 | # Rewrite requests for file extensions to track. | |
| 10 | RewriteRule ^([^/]+\.(zip|app|bin|exe|jar))$ counter.php?filename=$1 [L] | |
| 11 | </IfModule> | |
| 1 | 12 |
| 1 | <?php | |
| 2 | /* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. | |
| 3 | * | |
| 4 | * SPDX-License-Identifier: MIT | |
| 5 | */ | |
| 6 | ||
| 7 | // Log all errors to a temporary file. | |
| 8 | ini_set( 'log_errors', 1 ); | |
| 9 | ini_set( 'error_log', '/tmp/php-errors.log' ); | |
| 10 | ||
| 11 | // Do not impose a time limit for downloads. | |
| 12 | set_time_limit( 0 ); | |
| 13 | ||
| 14 | // Flush any previous output buffers. | |
| 15 | while( ob_get_level() > 0 ) { | |
| 16 | ob_end_flush(); | |
| 17 | } | |
| 18 | ||
| 19 | if( session_id() === "" ) { | |
| 20 | session_start(); | |
| 21 | } | |
| 22 | ||
| 23 | // Keep running upon client disconnect (helps catch file transfer failures). | |
| 24 | // This setting requires checking whether the connection has been aborted at | |
| 25 | // a regular interval to prevent bogging the server with abandoned requests. | |
| 26 | ignore_user_abort( true ); | |
| 27 | ||
| 28 | $filename = get_sanitized_filename(); | |
| 29 | $valid_filename = !empty( $filename ); | |
| 30 | $expiry = 24 * 60 * 60; | |
| 31 | ||
| 32 | if( $valid_filename && download( $filename ) && token_expired( $expiry ) ) { | |
| 33 | increment_count( "$filename-count.txt" ); | |
| 34 | } | |
| 35 | ||
| 36 | /** | |
| 37 | * Retrieve the file name being downloaded from the HTTP GET request. | |
| 38 | * | |
| 39 | * @return string The sanitized file name (without path information). | |
| 40 | */ | |
| 41 | function get_sanitized_filename() { | |
| 42 | $filepath = isset( $_GET[ 'filename' ] ) ? $_GET[ 'filename' ] : ''; | |
| 43 | $fileinfo = pathinfo( $filepath ); | |
| 44 | ||
| 45 | // Remove path information (no /etc/passwd or ../../etc/passwd for you). | |
| 46 | $basename = $fileinfo[ 'basename' ]; | |
| 47 | ||
| 48 | if( isset( $_SERVER[ 'HTTP_USER_AGENT' ] ) ) { | |
| 49 | $periods = substr_count( $basename, '.' ); | |
| 50 | ||
| 51 | // Address IE bug regarding multiple periods in filename. | |
| 52 | $basename = strstr( $_SERVER[ 'HTTP_USER_AGENT' ], 'MSIE' ) | |
| 53 | ? mb_ereg_replace( '/\./', '%2e', $basename, $periods - 1 ) | |
| 54 | : $basename; | |
| 55 | } | |
| 56 | ||
| 57 | // Trim all external and internal spaces. | |
| 58 | $basename = mb_ereg_replace( '/\s+/', '', $basename ); | |
| 59 | ||
| 60 | // Sanitize. | |
| 61 | $basename = mb_ereg_replace( '([^\w\d\-_~,;\[\]\(\).])', '', $basename ); | |
| 62 | ||
| 63 | return $basename; | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Answers whether the user's download token has expired. | |
| 68 | * | |
| 69 | * @param int $lifetime Number of seconds before expiring the token. | |
| 70 | * | |
| 71 | * @return bool True indicates the token has expired (or was not set). | |
| 72 | */ | |
| 73 | function token_expired( $lifetime ) { | |
| 74 | $TOKEN_NAME = 'LAST_DOWNLOAD'; | |
| 75 | $now = time(); | |
| 76 | $expired = !isset( $_SESSION[ $TOKEN_NAME ] ); | |
| 77 | ||
| 78 | if( !$expired && ($now - $_SESSION[ $TOKEN_NAME ] > $lifetime) ) { | |
| 79 | $expired = true; | |
| 80 | $_SESSION = array(); | |
| 81 | ||
| 82 | session_destroy(); | |
| 83 | } | |
| 84 | ||
| 85 | $_SESSION[ $TOKEN_NAME ] = $now; | |
| 86 | ||
| 87 | $TOKEN_CREATE = 'CREATED'; | |
| 88 | ||
| 89 | if( !isset( $_SESSION[ $TOKEN_CREATE ] ) ) { | |
| 90 | $_SESSION[ $TOKEN_CREATE ] = $now; | |
| 91 | } | |
| 92 | else if( $now - $_SESSION[ $TOKEN_CREATE ] > $lifetime ) { | |
| 93 | // Avoid session fixation attacks by regenerating tokens. | |
| 94 | session_regenerate_id( true ); | |
| 95 | $_SESSION[ $TOKEN_CREATE ] = $now; | |
| 96 | } | |
| 97 | ||
| 98 | return $expired; | |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Downloads a file, allowing for resuming partial downloads. | |
| 103 | * | |
| 104 | * @param string $filename File to download, must be in script directory. | |
| 105 | * | |
| 106 | * @return bool True if the file was transferred. | |
| 107 | */ | |
| 108 | function download( $filename ) { | |
| 109 | // Don't cache the file stats result (e.g., file size). | |
| 110 | clearstatcache(); | |
| 111 | ||
| 112 | $size = @filesize( $filename ); | |
| 113 | $size = $size === false || empty( $size ) ? 0 : $size; | |
| 114 | $content_type = mime_content_type( $filename ); | |
| 115 | list( $seek_start, $content_length ) = parse_range( $size ); | |
| 116 | ||
| 117 | // Added by PHP, removed by us. | |
| 118 | header_remove( 'x-powered-by' ); | |
| 119 | ||
| 120 | // HTTP/1.1 clients must treat invalid date formats, especially 0, as past. | |
| 121 | header( 'Expires: 0' ); | |
| 122 | ||
| 123 | // Prevent local caching. | |
| 124 | header( 'Cache-Control: public, must-revalidate, post-check=0, pre-check=0' ); | |
| 125 | ||
| 126 | // No response message portion may be cached (e.g., by a proxy server). | |
| 127 | header( 'Cache-Control: private', false ); | |
| 128 | ||
| 129 | // Force the browser to download, rather than display the file inline. | |
| 130 | header( "Content-Disposition: attachment; filename=\"$filename\"" ); | |
| 131 | header( 'Accept-Ranges: bytes' ); | |
| 132 | header( "Content-Length: $content_length" ); | |
| 133 | header( "Content-Type: $content_type" ); | |
| 134 | ||
| 135 | $method = isset( $_SERVER[ 'REQUEST_METHOD' ] ) | |
| 136 | ? $_SERVER[ 'REQUEST_METHOD' ] | |
| 137 | : 'GET'; | |
| 138 | ||
| 139 | // Honour HTTP HEAD requests. | |
| 140 | return $method === 'HEAD' | |
| 141 | ? false | |
| 142 | : transmit( $filename, $seek_start, $size ); | |
| 143 | } | |
| 144 | ||
| 145 | /** | |
| 146 | * Parses the HTTP range request header, provided one was sent by the | |
| 147 | * client. This provides download resume functionality. | |
| 148 | * | |
| 149 | * @param int $size The total file size (as stored on disk). | |
| 150 | * | |
| 151 | * @return array The starting offset for resuming the download, or 0 to | |
| 152 | * download the entire file (i.e., no offset could be parsed); also the | |
| 153 | * number of bytes to be transferred. | |
| 154 | */ | |
| 155 | function parse_range( $size ) { | |
| 156 | // By default, start transmitting at the beginning of the file. | |
| 157 | $seek_start = 0; | |
| 158 | $content_length = $size; | |
| 159 | ||
| 160 | // Check if a range is sent by browser or download manager. | |
| 161 | if( isset( $_SERVER[ 'HTTP_RANGE' ] ) ) { | |
| 162 | $range_format = '/^bytes=\d*-\d*(,\d*-\d*)*$/'; | |
| 163 | $request_range = $_SERVER[ 'HTTP_RANGE' ]; | |
| 164 | ||
| 165 | // Ensure the content request range is in a valid format. | |
| 166 | if( !preg_match( $range_format, $request_range, $matches ) ) { | |
| 167 | header( 'HTTP/1.1 416 Requested Range Not Satisfiable' ); | |
| 168 | header( "Content-Range: bytes */$size" ); | |
| 169 | ||
| 170 | // Terminate because the range is invalid. | |
| 171 | exit; | |
| 172 | } | |
| 173 | ||
| 174 | // Multiple ranges could be specified, but only serve the first range. | |
| 175 | $seek_start = isset( $matches[ 1 ] ) ? $matches[ 1 ] + 0 : 0; | |
| 176 | $seek_end = isset( $matches[ 2 ] ) ? $matches[ 2 ] + 0 : $size - 1; | |
| 177 | $range_bytes = $seek_start . '-' . $seek_end . '/' . $size; | |
| 178 | $content_length = $seek_end - $seek_start + 1; | |
| 179 | ||
| 180 | header( 'HTTP/1.1 206 Partial Content' ); | |
| 181 | header( "Content-Range: bytes $range_bytes" ); | |
| 182 | } | |
| 183 | ||
| 184 | return array( $seek_start, $content_length ); | |
| 185 | } | |
| 186 | ||
| 187 | /** | |
| 188 | * Transmits a file from the server to the client. | |
| 189 | * | |
| 190 | * @param string $filename File to download, must be this script directory. | |
| 191 | * @param int $seek_start Offset into file to start downloading. | |
| 192 | * @param int $size Total size of the file. | |
| 193 | * | |
| 194 | * @return bool True if the file was transferred. | |
| 195 | */ | |
| 196 | function transmit( $filename, $seek_start, $size ) { | |
| 197 | // Buffer after sending HTTP headers to allow client download estimates. | |
| 198 | if( ob_get_level() == 0 ) { | |
| 199 | ob_start(); | |
| 200 | } | |
| 201 | ||
| 202 | // Don't count missing files as download hits. | |
| 203 | $bytes_sent = -1; | |
| 204 | ||
| 205 | // Open the file to be downloaded. | |
| 206 | $fp = @fopen( $filename, 'rb' ); | |
| 207 | ||
| 208 | if( $fp !== false ) { | |
| 209 | @fseek( $fp, $seek_start ); | |
| 210 | ||
| 211 | $aborted = false; | |
| 212 | $bytes_sent = $seek_start; | |
| 213 | $chunk_size = 1024 * 16; | |
| 214 | ||
| 215 | while( !feof( $fp ) && !$aborted ) { | |
| 216 | // Stream the file. | |
| 217 | print( @fread( $fp, $chunk_size ) ); | |
| 218 | ||
| 219 | // Track running total of bytes sent. | |
| 220 | $bytes_sent += $chunk_size; | |
| 221 | ||
| 222 | // Send the file to download in small chunks. | |
| 223 | if( ob_get_level() > 0 ) { | |
| 224 | ob_flush(); | |
| 225 | } | |
| 226 | ||
| 227 | flush(); | |
| 228 | ||
| 229 | // Chunking the file allows detecting when the connection has closed. | |
| 230 | $aborted = connection_aborted() || connection_status() != 0; | |
| 231 | } | |
| 232 | ||
| 233 | // Indicate that transmission is complete. | |
| 234 | if( ob_get_level() > 0 ) { | |
| 235 | ob_end_flush(); | |
| 236 | } | |
| 237 | ||
| 238 | fclose( $fp ); | |
| 239 | } | |
| 240 | ||
| 241 | // Download succeeded if the total bytes matches or exceeds the file size. | |
| 242 | return $bytes_sent >= $size; | |
| 243 | } | |
| 244 | ||
| 245 | /** | |
| 246 | * Increments the number in a file using an exclusive lock. The file | |
| 247 | * is set to an initial value set to 0 if it doesn't exist. | |
| 248 | * | |
| 249 | * @param string $filename The file containing a number to increment. | |
| 250 | */ | |
| 251 | function increment_count( $filename ) { | |
| 252 | try { | |
| 253 | lock_open( $filename ); | |
| 254 | ||
| 255 | // Coerce value to largest natural numeric data type. | |
| 256 | $count = @file_get_contents( $filename ) + 0; | |
| 257 | ||
| 258 | // Write the new counter value. | |
| 259 | file_put_contents( $filename, $count + 1 ); | |
| 260 | } | |
| 261 | finally { | |
| 262 | lock_close( $filename ); | |
| 263 | } | |
| 264 | } | |
| 265 | ||
| 266 | /** | |
| 267 | * Acquires a lock for a particular file. Callers would be prudent to | |
| 268 | * call this function from within a try/finally block and close the lock | |
| 269 | * in the finally section. The amount of time between opening and closing | |
| 270 | * the lock must be minimal because parallel processes will be waiting on | |
| 271 | * the lock's release. | |
| 272 | * | |
| 273 | * @param string $filename The name of file to lock. | |
| 274 | * | |
| 275 | * @return bool True if the lock was obtained, false upon excessive attempts. | |
| 276 | */ | |
| 277 | function lock_open( $filename ) { | |
| 278 | $lockdir = create_lock_filename( $filename ); | |
| 279 | ||
| 280 | // Track the number of times a lock attempt is made. | |
| 281 | $iterations = 0; | |
| 282 | ||
| 283 | do { | |
| 284 | // Creates and tests lock file existence atomically. | |
| 285 | if( @mkdir( $lockdir, 0777 ) ) { | |
| 286 | // Exit the loop. | |
| 287 | $iterations = 0; | |
| 288 | } | |
| 289 | else { | |
| 290 | $iterations++; | |
| 291 | $lifetime = time() - filemtime( $lockdir ); | |
| 292 | ||
| 293 | if( $lifetime > 10 ) { | |
| 294 | // If the lock has gone stale, delete it. | |
| 295 | @rmdir( $lockdir ); | |
| 296 | } | |
| 297 | else { | |
| 298 | // Wait a random duration to avoid concurrency conflicts. | |
| 299 | usleep( rand( 1000, 10000 ) ); | |
| 300 | } | |
| 301 | } | |
| 302 | } | |
| 303 | while( $iterations > 0 && $iterations < 10 ); | |
| 304 | ||
| 305 | // Indicate whether the maximum number of lock attempts were exceeded. | |
| 306 | return $iterations == 0; | |
| 307 | } | |
| 308 | ||
| 309 | /** | |
| 310 | * Releases the lock on a particular file. | |
| 311 | * | |
| 312 | * @param string $filename The name of file that was locked. | |
| 313 | */ | |
| 314 | function lock_close( $filename ) { | |
| 315 | @rmdir( create_lock_filename( $filename ) ); | |
| 316 | } | |
| 317 | ||
| 318 | /** | |
| 319 | * Creates a uniquely named lock directory name. | |
| 320 | * | |
| 321 | * @param string $filename The name of the file under contention. | |
| 322 | * | |
| 323 | * @return string A unique lock file reference for the given file name. | |
| 324 | */ | |
| 325 | function create_lock_filename( $filename ) { | |
| 326 | return $filename .'.lock'; | |
| 327 | } | |
| 328 | ?> | |
| 1 | 329 |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | viewBox="0 0 157.33104 75" | |
| 4 | version="1.1" | |
| 5 | id="svg1" | |
| 6 | width="157.33104" | |
| 7 | height="75" | |
| 8 | xmlns="http://www.w3.org/2000/svg" | |
| 9 | xmlns:svg="http://www.w3.org/2000/svg"> | |
| 10 | <defs | |
| 11 | id="defs1" /> | |
| 12 | <!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. --> | |
| 13 | <path | |
| 14 | d="m 56.86155,15.62503 c 0,-1.72852 -1.39648,-3.125 -3.125,-3.125 -1.72852,0 -3.125,1.39648 -3.125,3.125 V 39.3262 l -7.16797,-7.16797 c -1.2207,-1.2207 -3.20312,-1.2207 -4.42383,0 -1.2207,1.22071 -1.2207,3.20313 0,4.42383 l 12.5,12.5 c 1.22071,1.2207 3.20313,1.2207 4.42383,0 l 12.5,-12.5 c 1.2207,-1.2207 1.2207,-3.20312 0,-4.42383 -1.2207,-1.2207 -3.20312,-1.2207 -4.42383,0 l -7.1582,7.16797 z m -21.875,31.25 c -3.44727,0 -6.25,2.80273 -6.25,6.25 v 3.125 c 0,3.44727 2.80273,6.25 6.25,6.25 h 37.5 c 3.44727,0 6.25,-2.80273 6.25,-6.25 v -3.125 c 0,-3.44727 -2.80273,-6.25 -6.25,-6.25 h -9.91211 l -4.42383,4.42383 c -2.4414,2.4414 -6.39648,2.4414 -8.83789,0 l -4.41406,-4.42383 z m 35.9375,10.15625 c -1.29883,0 -2.34375,-1.04492 -2.34375,-2.34375 0,-1.29883 1.04492,-2.34375 2.34375,-2.34375 1.29883,0 2.34375,1.04492 2.34375,2.34375 0,1.29883 -1.04492,2.34375 -2.34375,2.34375 z" | |
| 15 | id="path16323" | |
| 16 | style="fill:#000000;fill-opacity:1;stroke-width:0.0976562" /> | |
| 17 | <path | |
| 18 | d="m 121.70706,38.92224 c -0.0223,-4.09673 1.83069,-7.18881 5.58138,-9.46601 -2.0986,-3.00277 -5.26882,-4.65486 -9.45485,-4.97858 -3.96277,-0.31256 -8.29392,2.31069 -9.87903,2.31069 -1.67441,0 -5.51439,-2.19906 -8.52833,-2.19906 -6.22882,0.10046 -12.84832,4.96742 -12.84832,14.86877 q 0,4.38696 1.60743,9.06415 c 1.42883,4.09673 6.58602,14.1432 11.96646,13.97576 2.81303,-0.067 4.79998,-1.99813 8.46136,-1.99813 3.54976,0 5.39161,1.99813 8.52834,1.99813 5.4251,-0.0781 10.09112,-9.20927 11.45298,-13.31716 -7.27811,-3.42696 -6.88742,-10.04647 -6.88742,-10.25856 z m -6.31811,-18.32922 c 3.04743,-3.61673 2.76836,-6.90974 2.67906,-8.09299 -2.69022,0.15627 -5.80463,1.83069 -7.5795,3.89579 -1.95348,2.21023 -3.10325,4.9451 -2.85767,8.02602 2.91348,0.22325 5.57021,-1.27256 7.75811,-3.82882 z" | |
| 19 | id="path5811" | |
| 20 | style="fill:#000000;fill-opacity:1;stroke-width:0.111627" /> | |
| 21 | </svg> | |
| 22 | ||
| 1 | <svg width="157.331" height="75" xmlns="http://www.w3.org/2000/svg"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M56.862 15.625a3.122 3.122 0 0 0-3.125-3.125 3.122 3.122 0 0 0-3.125 3.125v23.701l-7.168-7.168a3.13 3.13 0 0 0-4.424 0 3.13 3.13 0 0 0 0 4.424l12.5 12.5a3.13 3.13 0 0 0 4.424 0l12.5-12.5a3.13 3.13 0 0 0 0-4.424 3.13 3.13 0 0 0-4.424 0l-7.158 7.168zm-21.875 31.25a6.256 6.256 0 0 0-6.25 6.25v3.125a6.256 6.256 0 0 0 6.25 6.25h37.5a6.256 6.256 0 0 0 6.25-6.25v-3.125a6.256 6.256 0 0 0-6.25-6.25h-9.913l-4.423 4.424a6.248 6.248 0 0 1-8.838 0l-4.414-4.424zm35.937 10.156a2.338 2.338 0 0 1-2.344-2.343 2.338 2.338 0 0 1 2.344-2.344 2.338 2.338 0 0 1 2.344 2.344 2.338 2.338 0 0 1-2.344 2.343z" style="fill:#000;fill-opacity:1;stroke-width:.0976562"/><path d="M121.707 38.922c-.022-4.096 1.83-7.189 5.581-9.466-2.098-3.003-5.268-4.655-9.454-4.978-3.963-.313-8.294 2.31-9.88 2.31-1.674 0-5.514-2.199-8.528-2.199-6.229.1-12.848 4.968-12.848 14.87q0 4.386 1.607 9.063c1.43 4.097 6.586 14.143 11.967 13.976 2.813-.067 4.8-1.998 8.461-1.998 3.55 0 5.392 1.998 8.528 1.998 5.426-.078 10.092-9.21 11.453-13.317-7.278-3.427-6.887-10.047-6.887-10.259zm-6.318-18.329c3.047-3.617 2.768-6.91 2.679-8.093-2.69.156-5.805 1.83-7.58 3.896-1.953 2.21-3.103 4.945-2.857 8.026 2.913.223 5.57-1.273 7.758-3.829z" style="fill:#000;fill-opacity:1;stroke-width:.111627"/></svg> |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | viewBox="0 0 157.33104 75" | |
| 4 | version="1.1" | |
| 5 | id="svg1" | |
| 6 | width="157.33104" | |
| 7 | height="75" | |
| 8 | xmlns="http://www.w3.org/2000/svg" | |
| 9 | xmlns:svg="http://www.w3.org/2000/svg"> | |
| 10 | <defs | |
| 11 | id="defs1" /> | |
| 12 | <!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. --> | |
| 13 | <path | |
| 14 | d="m 53.68516,15.625 c 0,-1.72852 -1.39648,-3.125 -3.125,-3.125 -1.72852,0 -3.125,1.39648 -3.125,3.125 V 39.32617 L 40.26719,32.1582 c -1.2207,-1.2207 -3.20312,-1.2207 -4.42383,0 -1.2207,1.22071 -1.2207,3.20313 0,4.42383 l 12.5,12.5 c 1.22071,1.2207 3.20313,1.2207 4.42383,0 l 12.5,-12.5 c 1.2207,-1.2207 1.2207,-3.20312 0,-4.42383 -1.2207,-1.2207 -3.20312,-1.2207 -4.42383,0 l -7.1582,7.16797 z m -21.875,31.25 c -3.44727,0 -6.25,2.80273 -6.25,6.25 v 3.125 c 0,3.44727 2.80273,6.25 6.25,6.25 h 37.5 c 3.44727,0 6.25,-2.80273 6.25,-6.25 v -3.125 c 0,-3.44727 -2.80273,-6.25 -6.25,-6.25 h -9.91211 l -4.42383,4.42383 c -2.4414,2.4414 -6.39648,2.4414 -8.83789,0 L 41.72227,46.875 Z m 35.9375,10.15625 c -1.29883,0 -2.34375,-1.04492 -2.34375,-2.34375 0,-1.29883 1.04492,-2.34375 2.34375,-2.34375 1.29883,0 2.34375,1.04492 2.34375,2.34375 0,1.29883 -1.04492,2.34375 -2.34375,2.34375 z" | |
| 15 | id="path16474" | |
| 16 | style="fill:#000000;fill-opacity:1;stroke-width:0.0976562" /> | |
| 17 | <path | |
| 18 | d="m 121.74171,43.05624 c 0.95702,-0.65429 2.28513,-1.22069 2.28513,-1.22069 0,0 -3.77925,0.68359 -7.53897,0.99608 -4.59954,0.38086 -9.54088,0.45898 -12.02132,0.12695 -5.86906,-0.78124 3.22261,-2.93941 3.22261,-2.93941 0,0 -3.52534,-0.23437 -7.87099,1.85544 -5.12688,2.48044 12.69515,3.61324 21.92354,1.18163 z m -8.33973,-3.13473 c -1.85545,-4.16986 -8.11513,-7.83192 0,-14.23809 C 123.52489,17.69525 118.33355,12.5 118.33355,12.5 c 2.09959,8.25185 -7.38271,10.75181 -10.8104,15.8787 -2.33395,3.50581 1.14256,7.26553 5.87883,11.54281 z m 11.19126,-17.2068 c 0.01,0 -17.10915,4.27729 -8.93543,13.69123 2.41208,2.7734 -0.63476,5.27336 -0.63476,5.27336 0,0 6.12297,-3.16402 3.3105,-7.11904 -2.62691,-3.69136 -4.63861,-5.52727 6.25969,-11.84555 z m -0.5957,26.41567 a 1.190414,1.190414 0 0 1 -0.19531,0.2539 c 12.52914,-3.29097 7.91982,-11.61117 1.93357,-9.50183 a 1.6923604,1.6923604 0 0 0 -0.80077,0.61523 6.879792,6.879792 0 0 1 1.0742,-0.29297 c 3.02731,-0.63475 7.37296,4.05268 -2.01169,8.92567 z m 4.60541,6.0839 c 0,0 1.416,1.1621 -1.55272,2.07029 -5.65422,1.70896 -23.51531,2.22653 -28.47618,0.0684 -1.78709,-0.77147 1.56248,-1.85544 2.61715,-2.08005 1.09374,-0.23437 1.72849,-0.19531 1.72849,-0.19531 -1.98239,-1.39646 -12.82209,2.74411 -5.50773,3.92573 19.94504,3.24215 36.3667,-1.45506 31.19099,-3.78901 z m -21.83174,-4.04291 c -7.68545,2.1484 4.67767,6.58194 14.4627,2.39254 a 18.15308,18.15308 0 0 1 -2.75387,-1.34764 c -4.36518,0.83007 -6.38664,0.88866 -10.35143,0.43945 -3.27144,-0.37109 -1.3574,-1.48435 -1.3574,-1.48435 z m 17.55836,9.49206 c -7.68545,1.44529 -17.16774,1.27928 -22.7829,0.35156 0,-0.01 1.15232,0.94725 7.07022,1.3281 9.00378,0.57617 22.83173,-0.32226 23.15399,-4.58001 0,0 -0.62499,1.6113 -7.44131,2.90035 z M 120.07181,46.9722 c -5.78117,1.11327 -9.13074,1.08397 -13.3592,0.64453 -3.27144,-0.3418 -1.13279,-1.92381 -1.13279,-1.92381 -8.47645,2.81247 4.70696,5.99602 16.55251,2.52927 a 5.89543,5.89543 0 0 1 -2.06052,-1.24999 z" | |
| 19 | id="path5936" | |
| 20 | style="fill:#000000;fill-opacity:1;stroke-width:0.097655" /> | |
| 21 | </svg> | |
| 22 | ||
| 1 | <svg width="157.331" height="75" xmlns="http://www.w3.org/2000/svg"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M53.685 15.625A3.122 3.122 0 0 0 50.56 12.5a3.122 3.122 0 0 0-3.125 3.125v23.701l-7.168-7.168a3.13 3.13 0 0 0-4.424 0 3.13 3.13 0 0 0 0 4.424l12.5 12.5a3.13 3.13 0 0 0 4.424 0l12.5-12.5a3.13 3.13 0 0 0 0-4.424 3.13 3.13 0 0 0-4.424 0l-7.158 7.168zM31.81 46.875a6.256 6.256 0 0 0-6.25 6.25v3.125a6.256 6.256 0 0 0 6.25 6.25h37.5a6.256 6.256 0 0 0 6.25-6.25v-3.125a6.256 6.256 0 0 0-6.25-6.25h-9.912l-4.424 4.424a6.248 6.248 0 0 1-8.838 0l-4.414-4.424Zm35.938 10.156a2.338 2.338 0 0 1-2.344-2.343 2.338 2.338 0 0 1 2.344-2.344 2.338 2.338 0 0 1 2.343 2.343 2.338 2.338 0 0 1-2.343 2.344z" style="fill:#000;fill-opacity:1;stroke-width:.0976562"/><path d="M121.742 43.056c.957-.654 2.285-1.22 2.285-1.22s-3.78.683-7.54.996c-4.599.38-9.54.459-12.02.127-5.87-.782 3.222-2.94 3.222-2.94s-3.525-.234-7.87 1.856c-5.128 2.48 12.694 3.613 21.923 1.181zm-8.34-3.134c-1.855-4.17-8.115-7.832 0-14.239 10.123-7.988 4.932-13.183 4.932-13.183 2.1 8.252-7.383 10.752-10.81 15.879-2.335 3.506 1.142 7.265 5.878 11.543zm11.191-17.207c.01 0-17.109 4.277-8.935 13.69 2.412 2.774-.635 5.274-.635 5.274s6.123-3.164 3.31-7.119c-2.626-3.691-4.638-5.527 6.26-11.845zm-.595 26.415a1.19 1.19 0 0 1-.196.254c12.53-3.29 7.92-11.61 1.934-9.502a1.692 1.692 0 0 0-.801.616 6.88 6.88 0 0 1 1.074-.293c3.028-.635 7.373 4.052-2.011 8.925zm4.605 6.084s1.416 1.162-1.553 2.07c-5.654 1.71-23.515 2.227-28.476.069-1.787-.772 1.563-1.855 2.617-2.08 1.094-.234 1.729-.195 1.729-.195-1.983-1.397-12.822 2.744-5.508 3.925 19.945 3.242 36.367-1.455 31.19-3.789zm-21.832-4.043c-7.685 2.149 4.678 6.582 14.463 2.393a18.153 18.153 0 0 1-2.754-1.348c-4.365.83-6.387.889-10.351.44-3.272-.371-1.358-1.485-1.358-1.485zm17.559 9.492c-7.686 1.446-17.168 1.28-22.783.352 0-.01 1.152.947 7.07 1.328 9.004.576 22.832-.322 23.154-4.58 0 0-.625 1.611-7.441 2.9zm-4.258-13.69c-5.781 1.112-9.13 1.083-13.36.644-3.27-.342-1.132-1.924-1.132-1.924-8.477 2.812 4.707 5.996 16.552 2.53a5.895 5.895 0 0 1-2.06-1.25z" style="fill:#000;fill-opacity:1;stroke-width:.097655"/></svg> |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | viewBox="0 0 157.33104 75" | |
| 4 | version="1.1" | |
| 5 | id="svg1" | |
| 6 | width="157.33104" | |
| 7 | height="75" | |
| 8 | xmlns="http://www.w3.org/2000/svg" | |
| 9 | xmlns:svg="http://www.w3.org/2000/svg"> | |
| 10 | <defs | |
| 11 | id="defs1" /> | |
| 12 | <!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. --> | |
| 13 | <path | |
| 14 | d="m 111.9104,24.539926 c 0.0976,0.0488 0.17577,0.166 0.29295,0.166 0.10741,0 0.27341,-0.039 0.28318,-0.14647 0.0196,-0.13671 -0.18553,-0.22459 -0.31248,-0.28318 -0.166,-0.0684 -0.38082,-0.0976 -0.53706,-0.01 -0.039,0.0195 -0.0781,0.0684 -0.0586,0.10741 0.0293,0.12695 0.22459,0.10742 0.332,0.166 z m -2.13849,0.166 c 0.11718,0 0.1953,-0.11718 0.29294,-0.166 0.10742,-0.0586 0.30271,-0.039 0.34177,-0.15624 0.0195,-0.0391 -0.0195,-0.0879 -0.0586,-0.10741 -0.15623,-0.0879 -0.37106,-0.0586 -0.53706,0.01 -0.12694,0.0586 -0.33201,0.14647 -0.31248,0.28318 0.01,0.0976 0.17577,0.14647 0.27342,0.13671 z m 21.59,27.22431 c -0.35153,-0.3906 -0.51753,-1.13273 -0.70306,-1.92368 -0.17577,-0.79095 -0.38083,-1.64048 -1.02531,-2.18731 -0.12694,-0.10742 -0.25388,-0.20506 -0.39059,-0.28318 -0.12694,-0.0781 -0.26365,-0.14648 -0.40036,-0.1953 0.89836,-2.66579 0.54683,-5.32182 -0.3613,-7.72397 -1.11318,-2.9392 -3.05638,-5.50735 -4.54063,-7.26502 -1.66979,-2.09944 -3.29075,-4.09146 -3.26146,-7.03067 0.0488,-4.48205 0.49801,-12.81143 -7.40172,-12.8212 -9.99918,-0.0196 -7.49938,10.09682 -7.6068,13.20203 -0.166,2.28497 -0.62495,4.08169 -2.19708,6.31784 -1.84555,2.19708 -4.442986,5.74171 -5.673356,9.44257 -0.58588,1.7479 -0.8593,3.5251 -0.60541,5.20465 -0.63472,0.56636 -1.11319,1.43543 -1.62096,1.97249 -0.41012,0.41989 -1.00578,0.57612 -1.66002,0.81048 -0.65424,0.23436 -1.36708,0.58589 -1.8065,1.4159 -0.20506,0.38083 -0.27341,0.79095 -0.27341,1.21084 0,0.38083 0.0586,0.77142 0.11718,1.15225 0.11717,0.79095 0.24412,1.53307 0.0781,2.03108 -0.50777,1.40613 -0.57613,2.38261 -0.21483,3.09544 0.37106,0.71283 1.11319,1.02531 1.96273,1.20108 1.68931,0.35153 3.98405,0.26365 5.79054,1.2206 1.933426,1.01554 3.896156,1.37684 5.458526,1.01554 1.13271,-0.25389 2.06037,-0.93742 2.52908,-1.97249 1.22061,-0.01 2.56815,-0.5273 4.71641,-0.64448 1.45496,-0.11718 3.28098,0.51753 5.38041,0.40035 0.0586,0.2246 0.13671,0.44919 0.24412,0.65425 v 0.01 c 0.81048,1.63072 2.32403,2.37285 3.93522,2.2459 1.62097,-0.12694 3.32981,-1.07413 4.71641,-2.72438 1.32802,-1.60143 3.51533,-2.26544 4.97029,-3.14427 0.7226,-0.43941 1.30849,-0.98624 1.35731,-1.78696 0.039,-0.80071 -0.42965,-1.68931 -1.51355,-2.90015 z m -19.16833,-30.90565 c 0.95695,-2.16779 3.33957,-2.12873 4.29652,-0.0391 0.63471,1.38661 0.35153,3.01733 -0.41989,3.94499 -0.15623,-0.0781 -0.57612,-0.25388 -1.23036,-0.47847 0.10741,-0.11718 0.30271,-0.26365 0.38082,-0.44919 0.46872,-1.15224 -0.0195,-2.6365 -0.88859,-2.66579 -0.71284,-0.0488 -1.35731,1.0546 -1.15225,2.24591 -0.40036,-0.1953 -0.91789,-0.34177 -1.26943,-0.42965 -0.0976,-0.67378 -0.0293,-1.42567 0.28318,-2.12874 z m -3.97428,-1.12295 c 0.98625,0 2.03108,1.3866 1.86508,3.27122 -0.34177,0.0976 -0.6933,0.24412 -0.99601,0.44918 0.11718,-0.86907 -0.32224,-1.96274 -0.93742,-1.91391 -0.82025,0.0684 -0.95696,2.07014 -0.17577,2.74392 0.0977,0.0781 0.18553,-0.0195 -0.57613,0.53706 -1.52331,-1.42566 -1.0253,-5.08747 0.82025,-5.08747 z m -1.32802,5.92724 c 0.60542,-0.44918 1.32802,-0.97648 1.37685,-1.0253 0.45894,-0.42965 1.31825,-1.3866 2.72438,-1.3866 0.6933,0 1.52331,0.22459 2.52909,0.86906 0.61518,0.40036 1.10342,0.42966 2.20684,0.90813 0.82025,0.34177 1.33778,0.94719 1.02531,1.7772 -0.25389,0.6933 -1.07413,1.40613 -2.21661,1.76743 -1.0839,0.35153 -1.93344,1.56237 -3.73016,1.45496 -0.38083,-0.0196 -0.68354,-0.0977 -0.93742,-0.20507 -0.78119,-0.34176 -1.19131,-1.01554 -1.95297,-1.46472 -0.83977,-0.46871 -1.28896,-1.01554 -1.43543,-1.49401 -0.13671,-0.47848 0,-0.87884 0.41012,-1.20108 z m 0.32224,32.61449 c -0.26365,3.42745 -4.28675,3.35909 -7.352896,1.75766 -2.91968,-1.54284 -6.69866,-0.63471 -7.47009,-2.13849 -0.23435,-0.45895 -0.23435,-1.24013 0.25389,-2.57791 v -0.0195 c 0.23435,-0.74213 0.0586,-1.56237 -0.0586,-2.33379 -0.11718,-0.76166 -0.17577,-1.46472 0.0879,-1.95297 0.34177,-0.65424 0.83002,-0.8886 1.4452,-1.10342 1.00578,-0.3613 1.15225,-0.33201 1.9139,-0.96672 0.53707,-0.55659 0.92766,-1.25966 1.39637,-1.75766 0.49801,-0.53707 0.97648,-0.79095 1.72837,-0.67378 0.79095,0.11718 1.474486,0.66401 2.138496,1.56237 l 1.9139,3.47628 c 0.92766,1.9432 4.20863,4.72617 4.00357,6.72796 z m -0.1367,-2.52909 c -0.40036,-0.64448 -0.93743,-1.32801 -1.40614,-1.9139 0.6933,0 1.38661,-0.21483 1.63073,-0.86907 0.22459,-0.60542 0,-1.45496 -0.7226,-2.43144 -1.31825,-1.7772 -3.73992,-3.17357 -3.73992,-3.17357 -1.31825,-0.82024 -2.06038,-1.82602 -2.40215,-2.91968 -0.34176,-1.09366 -0.29294,-2.2752 -0.0293,-3.43721 0.50777,-2.23614 1.81626,-4.4137 2.65603,-5.78077 0.22459,-0.16601 0.0781,0.31247 -0.84954,2.03108 -0.83,1.57214 -2.382606,5.20465 -0.25388,8.04621 0.0586,-2.02132 0.53706,-4.08169 1.34754,-6.00536 1.17178,-2.67556 3.64228,-7.31385 3.83757,-11.00495 0.10742,0.0781 0.44919,0.31247 0.60543,0.40036 0.44918,0.26365 0.79095,0.65424 1.23036,1.00577 1.21084,0.97648 2.78298,0.89836 4.14028,0.11718 0.60542,-0.34177 1.09366,-0.73236 1.55261,-0.87883 0.96672,-0.30271 1.73814,-0.83978 2.17755,-1.46473 0.75189,2.96851 2.50956,7.25526 3.63251,9.34493 0.59566,1.11319 1.78697,3.46651 2.3045,6.30807 0.32224,-0.01 0.68354,0.039 1.06437,0.13671 1.34754,-3.48604 -1.14249,-7.24549 -2.27521,-8.29033 -0.45894,-0.44918 -0.47847,-0.64448 -0.25388,-0.63471 1.23037,1.09366 2.85133,3.29074 3.43722,5.76124 0.27341,1.13272 0.32223,2.31426 0.039,3.48604 1.60143,0.66401 3.50557,1.7479 2.9978,3.39815 -0.21483,-0.01 -0.31248,0 -0.41013,0 0.31248,-0.98624 -0.38082,-1.7186 -2.22637,-2.54861 -1.91391,-0.83978 -3.51534,-0.83978 -3.73993,1.2206 -1.18154,0.41012 -1.78696,1.43543 -2.08967,2.66579 -0.27341,1.09366 -0.35153,2.41191 -0.42965,3.89617 -0.0488,0.75189 -0.35154,1.75766 -0.66401,2.83179 -3.1345,2.23615 -7.48961,3.21263 -11.16118,0.70307 z m 25.13463,-1.12295 c -0.0879,1.64049 -4.0231,1.94319 -6.17136,4.54063 -1.28895,1.53308 -2.87085,2.38262 -4.25746,2.49003 -1.38661,0.10741 -2.58768,-0.46871 -3.29074,-1.88461 -0.45895,-1.08389 -0.23436,-2.25567 0.10741,-3.54462 0.3613,-1.38661 0.89836,-2.81227 0.96672,-3.96452 0.0781,-1.48425 0.166,-2.78297 0.41012,-3.77898 0.25388,-1.00578 0.64448,-1.67955 1.33778,-2.06038 0.0293,-0.0195 0.0684,-0.0293 0.0976,-0.0488 0.0781,1.28895 0.71284,2.59744 1.83579,2.88062 1.23037,0.32223 2.9978,-0.73237 3.74969,-1.59167 0.87884,-0.0293 1.53308,-0.0879 2.20685,0.49801 0.96672,0.83001 0.6933,2.95874 1.66978,4.06216 1.03507,1.13272 1.36708,1.90414 1.33778,2.40215 z m -24.93934,-27.77114 c 0.1953,0.18553 0.45895,0.43942 0.78119,0.6933 0.64448,0.50777 1.54284,1.03507 2.6658,1.03507 1.13271,0 2.19708,-0.57612 3.10521,-1.0546 0.47847,-0.25388 1.06436,-0.68353 1.44519,-1.01554 0.38083,-0.332 0.57612,-0.61518 0.30271,-0.64447 -0.27342,-0.0293 -0.25389,0.25388 -0.58589,0.498 -0.42965,0.31248 -0.94719,0.7226 -1.35731,0.95695 -0.7226,0.41012 -1.90414,0.99601 -2.91968,0.99601 -1.01554,0 -1.82602,-0.46871 -2.43144,-0.94718 -0.30271,-0.24412 -0.55659,-0.48824 -0.75189,-0.67378 -0.14647,-0.1367 -0.18553,-0.44918 -0.41989,-0.47847 -0.13671,-0.01 -0.17577,0.3613 0.166,0.63471 z" | |
| 15 | id="path6061" | |
| 16 | style="fill:#000000;fill-opacity:1;stroke-width:0.097648" /> | |
| 17 | <path | |
| 18 | d="m 52.578354,15.625006 c 0,-1.72852 -1.39648,-3.125 -3.125,-3.125 -1.72852,0 -3.125,1.39648 -3.125,3.125 v 23.70117 l -7.16797,-7.16797 c -1.2207,-1.2207 -3.20312,-1.2207 -4.42383,0 -1.2207,1.22071 -1.2207,3.20313 0,4.42383 l 12.5,12.5 c 1.22071,1.2207 3.20313,1.2207 4.42383,0 l 12.5,-12.5 c 1.2207,-1.2207 1.2207,-3.20312 0,-4.42383 -1.2207,-1.2207 -3.20312,-1.2207 -4.42383,0 l -7.1582,7.16797 z m -21.875,31.25 c -3.44727,0 -6.25,2.80273 -6.25,6.25 v 3.125 c 0,3.44727 2.80273,6.25 6.25,6.25 h 37.5 c 3.44727,0 6.25,-2.80273 6.25,-6.25 v -3.125 c 0,-3.44727 -2.80273,-6.25 -6.25,-6.25 h -9.91211 l -4.42383,4.42383 c -2.4414,2.4414 -6.39648,2.4414 -8.83789,0 l -4.41406,-4.42383 z m 35.9375,10.15625 c -1.29883,0 -2.34375,-1.04492 -2.34375,-2.34375 0,-1.29883 1.04492,-2.34375 2.34375,-2.34375 1.29883,0 2.34375,1.04492 2.34375,2.34375 0,1.29883 -1.04492,2.34375 -2.34375,2.34375 z" | |
| 19 | id="path15448" | |
| 20 | style="fill:#000000;fill-opacity:1;stroke-width:0.0976562" /> | |
| 21 | </svg> | |
| 22 | ||
| 1 | <svg width="157.331" height="75" xmlns="http://www.w3.org/2000/svg"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M111.91 24.54c.098.049.176.166.293.166.108 0 .274-.04.284-.147.02-.136-.186-.224-.313-.283-.166-.068-.38-.097-.537-.01-.039.02-.078.069-.059.108.03.127.225.107.332.166zm-2.138.166c.117 0 .195-.117.293-.166.107-.059.303-.04.342-.156.02-.04-.02-.088-.059-.108-.156-.088-.371-.058-.537.01-.127.059-.332.147-.313.283.01.098.176.147.274.137zm21.59 27.224c-.352-.39-.518-1.132-.703-1.923-.176-.791-.381-1.64-1.025-2.188a2.639 2.639 0 0 0-.391-.283 2.167 2.167 0 0 0-.4-.195c.898-2.666.546-5.322-.362-7.724-1.113-2.94-3.056-5.508-4.54-7.265-1.67-2.1-3.291-4.092-3.262-7.03.049-4.483.498-12.812-7.402-12.822-9.999-.02-7.499 10.097-7.606 13.202-.166 2.285-.625 4.082-2.197 6.318-1.846 2.197-4.443 5.741-5.674 9.442-.586 1.748-.86 3.525-.605 5.205-.635.566-1.113 1.435-1.621 1.972-.41.42-1.006.577-1.66.811-.654.234-1.367.586-1.807 1.416-.205.38-.273.79-.273 1.21 0 .382.059.772.117 1.153.117.79.244 1.533.078 2.031-.508 1.406-.576 2.383-.215 3.095.371.713 1.114 1.026 1.963 1.202 1.69.351 3.984.263 5.79 1.22 1.934 1.016 3.897 1.377 5.46 1.016 1.132-.254 2.06-.938 2.528-1.973 1.22-.01 2.568-.527 4.717-.644 1.455-.117 3.28.517 5.38.4.059.225.137.45.244.654v.01c.81 1.631 2.324 2.373 3.935 2.246 1.621-.127 3.33-1.074 4.717-2.724 1.328-1.602 3.515-2.266 4.97-3.144.723-.44 1.309-.987 1.357-1.787.04-.801-.43-1.69-1.513-2.9zm-19.168-30.905c.957-2.168 3.34-2.13 4.296-.04.635 1.387.352 3.018-.42 3.945-.156-.078-.576-.253-1.23-.478.107-.117.303-.264.38-.45.47-1.151-.019-2.636-.888-2.665-.713-.049-1.357 1.055-1.152 2.246-.4-.195-.918-.342-1.27-.43a3.89 3.89 0 0 1 .284-2.128zm-3.975-1.123c.987 0 2.031 1.386 1.865 3.27a3.511 3.511 0 0 0-.996.45c.118-.869-.322-1.963-.937-1.914-.82.069-.957 2.07-.176 2.744.098.078.186-.02-.576.537-1.523-1.426-1.025-5.087.82-5.087zm-1.328 5.927c.606-.45 1.328-.977 1.377-1.025.46-.43 1.318-1.387 2.725-1.387.693 0 1.523.225 2.529.869.615.4 1.103.43 2.206.908.82.342 1.338.947 1.026 1.777-.254.694-1.074 1.406-2.217 1.768-1.084.351-1.933 1.562-3.73 1.455a2.722 2.722 0 0 1-.937-.205c-.782-.342-1.192-1.016-1.953-1.465-.84-.469-1.29-1.016-1.436-1.494-.137-.479 0-.879.41-1.201zm.323 32.614c-.264 3.428-4.287 3.36-7.353 1.758-2.92-1.543-6.699-.635-7.47-2.138-.235-.46-.235-1.24.253-2.578v-.02c.235-.742.059-1.562-.058-2.334-.117-.761-.176-1.464.088-1.953.341-.654.83-.888 1.445-1.103 1.006-.361 1.152-.332 1.914-.967.537-.556.927-1.26 1.396-1.757.498-.538.977-.791 1.729-.674.79.117 1.474.664 2.138 1.562l1.914 3.476c.928 1.944 4.209 4.727 4.004 6.728zm-.137-2.529c-.4-.644-.938-1.328-1.406-1.914.693 0 1.386-.214 1.63-.869.225-.605 0-1.455-.722-2.431-1.318-1.777-3.74-3.174-3.74-3.174-1.318-.82-2.06-1.826-2.402-2.92-.342-1.093-.293-2.275-.03-3.437.508-2.236 1.817-4.413 2.656-5.78.225-.166.079.312-.85 2.03-.83 1.573-2.382 5.205-.253 8.047.059-2.021.537-4.082 1.348-6.005 1.171-2.676 3.642-7.314 3.837-11.005.108.078.45.312.606.4.449.264.79.654 1.23 1.006 1.21.976 2.783.898 4.14.117.606-.342 1.094-.732 1.553-.879.967-.303 1.738-.84 2.177-1.465.752 2.969 2.51 7.256 3.633 9.345.596 1.113 1.787 3.467 2.304 6.308.323-.01.684.04 1.065.137 1.347-3.486-1.143-7.245-2.275-8.29-.46-.45-.479-.645-.254-.635 1.23 1.094 2.851 3.29 3.437 5.761.273 1.133.322 2.315.039 3.486 1.601.664 3.505 1.748 2.998 3.398-.215-.01-.313 0-.41 0 .312-.986-.381-1.718-2.227-2.548-1.914-.84-3.515-.84-3.74 1.22-1.181.41-1.787 1.436-2.09 2.666-.273 1.094-.35 2.412-.43 3.896-.048.752-.35 1.758-.663 2.832-3.135 2.236-7.49 3.213-11.161.703zm25.134-1.123c-.087 1.64-4.023 1.944-6.17 4.541-1.29 1.533-2.872 2.383-4.258 2.49-1.387.107-2.588-.469-3.291-1.885-.46-1.084-.234-2.255.107-3.544.362-1.387.899-2.813.967-3.965.078-1.484.166-2.783.41-3.779.254-1.006.645-1.68 1.338-2.06.03-.02.068-.03.098-.049.078 1.289.712 2.598 1.835 2.88 1.23.323 2.998-.732 3.75-1.591.879-.03 1.533-.088 2.207.498.967.83.693 2.959 1.67 4.062 1.035 1.133 1.367 1.904 1.337 2.402zm-24.939-27.77c.195.185.46.439.781.692.645.508 1.543 1.036 2.666 1.036 1.133 0 2.197-.577 3.105-1.055.479-.254 1.065-.684 1.446-1.016.38-.332.576-.615.302-.644-.273-.03-.254.254-.586.498-.43.312-.947.723-1.357.957-.723.41-1.904.996-2.92.996-1.015 0-1.826-.469-2.431-.947-.303-.244-.557-.489-.752-.674-.146-.137-.186-.45-.42-.479-.137-.01-.176.362.166.635z" style="fill:#000;fill-opacity:1;stroke-width:.097648"/><path d="M52.578 15.625a3.122 3.122 0 0 0-3.125-3.125 3.122 3.122 0 0 0-3.125 3.125v23.701l-7.168-7.168a3.13 3.13 0 0 0-4.423 0 3.13 3.13 0 0 0 0 4.424l12.5 12.5a3.13 3.13 0 0 0 4.423 0l12.5-12.5a3.13 3.13 0 0 0 0-4.424 3.13 3.13 0 0 0-4.423 0l-7.159 7.168zm-21.875 31.25a6.256 6.256 0 0 0-6.25 6.25v3.125a6.256 6.256 0 0 0 6.25 6.25h37.5a6.256 6.256 0 0 0 6.25-6.25v-3.125a6.256 6.256 0 0 0-6.25-6.25h-9.912l-4.424 4.424a6.248 6.248 0 0 1-8.837 0l-4.415-4.424zm35.938 10.156a2.338 2.338 0 0 1-2.344-2.343 2.338 2.338 0 0 1 2.344-2.344 2.338 2.338 0 0 1 2.344 2.344 2.338 2.338 0 0 1-2.344 2.343z" style="fill:#000;fill-opacity:1;stroke-width:.0976562"/></svg> |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | viewBox="0 0 157.33104 75" | |
| 4 | version="1.1" | |
| 5 | id="svg1" | |
| 6 | width="157.33104" | |
| 7 | height="75" | |
| 8 | xmlns="http://www.w3.org/2000/svg" | |
| 9 | xmlns:svg="http://www.w3.org/2000/svg"> | |
| 10 | <defs | |
| 11 | id="defs1" /> | |
| 12 | <!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. --> | |
| 13 | <path | |
| 14 | d="M 83.66552,19.38616 104.15659,16.5625 V 36.36161 H 83.66552 Z m 0,36.22768 20.49107,2.82366 V 38.88393 H 83.66552 Z m 22.74554,3.125 L 133.66552,62.5 V 38.88393 h -27.25446 z m 0,-42.47768 v 20.10045 h 27.25446 V 12.5 Z" | |
| 15 | id="path6186" | |
| 16 | style="fill:#000000;fill-opacity:1;stroke-width:0.111607" /> | |
| 17 | <path | |
| 18 | d="m 51.79052,15.625 c 0,-1.72852 -1.39648,-3.125 -3.125,-3.125 -1.72852,0 -3.125,1.39648 -3.125,3.125 V 39.32617 L 38.37255,32.1582 c -1.2207,-1.2207 -3.20312,-1.2207 -4.42383,0 -1.2207,1.22071 -1.2207,3.20313 0,4.42383 l 12.5,12.5 c 1.22071,1.2207 3.20313,1.2207 4.42383,0 l 12.5,-12.5 c 1.2207,-1.2207 1.2207,-3.20312 0,-4.42383 -1.2207,-1.2207 -3.20312,-1.2207 -4.42383,0 l -7.1582,7.16797 z m -21.875,31.25 c -3.44727,0 -6.25,2.80273 -6.25,6.25 v 3.125 c 0,3.44727 2.80273,6.25 6.25,6.25 h 37.5 c 3.44727,0 6.25,-2.80273 6.25,-6.25 v -3.125 c 0,-3.44727 -2.80273,-6.25 -6.25,-6.25 h -9.91211 l -4.42383,4.42383 c -2.4414,2.4414 -6.39648,2.4414 -8.83789,0 L 39.82763,46.875 Z m 35.9375,10.15625 c -1.29883,0 -2.34375,-1.04492 -2.34375,-2.34375 0,-1.29883 1.04492,-2.34375 2.34375,-2.34375 1.29883,0 2.34375,1.04492 2.34375,2.34375 0,1.29883 -1.04492,2.34375 -2.34375,2.34375 z" | |
| 19 | id="path16036" | |
| 20 | style="fill:#000000;fill-opacity:1;stroke-width:0.0976562" /> | |
| 21 | </svg> | |
| 22 | ||
| 1 | <svg width="157.331" height="75" xmlns="http://www.w3.org/2000/svg"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="m83.666 19.386 20.49-2.823v19.799h-20.49Zm0 36.228 20.49 2.823V38.884h-20.49Zm22.745 3.125 27.255 3.761V38.884H106.41zm0-42.478v20.1h27.255V12.5Z" style="fill:#000;fill-opacity:1;stroke-width:.111607"/><path d="M51.79 15.625a3.122 3.122 0 0 0-3.124-3.125 3.122 3.122 0 0 0-3.125 3.125v23.701l-7.168-7.168a3.13 3.13 0 0 0-4.424 0 3.13 3.13 0 0 0 0 4.424l12.5 12.5a3.13 3.13 0 0 0 4.424 0l12.5-12.5a3.13 3.13 0 0 0 0-4.424 3.13 3.13 0 0 0-4.424 0l-7.158 7.168zm-21.874 31.25a6.256 6.256 0 0 0-6.25 6.25v3.125a6.256 6.256 0 0 0 6.25 6.25h37.5a6.256 6.256 0 0 0 6.25-6.25v-3.125a6.256 6.256 0 0 0-6.25-6.25h-9.913l-4.423 4.424a6.248 6.248 0 0 1-8.838 0l-4.414-4.424Zm35.937 10.156a2.338 2.338 0 0 1-2.344-2.343 2.338 2.338 0 0 1 2.344-2.344 2.338 2.338 0 0 1 2.344 2.343 2.338 2.338 0 0 1-2.344 2.344z" style="fill:#000;fill-opacity:1;stroke-width:.0976562"/></svg> |
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |
| 2 | <svg | |
| 3 | width="300" | |
| 4 | height="71.784" | |
| 5 | viewBox="0 0 79.375 18.993" | |
| 6 | version="1.1" | |
| 7 | id="svg7" | |
| 8 | xmlns:xlink="http://www.w3.org/1999/xlink" | |
| 9 | xmlns="http://www.w3.org/2000/svg" | |
| 10 | xmlns:svg="http://www.w3.org/2000/svg"> | |
| 11 | <defs | |
| 12 | id="defs2"> | |
| 13 | <linearGradient | |
| 14 | xlink:href="#a" | |
| 15 | id="b" | |
| 16 | x1=".152" | |
| 17 | x2=".969" | |
| 18 | y1="-.045" | |
| 19 | y2="-.049" | |
| 20 | gradientTransform="matrix(-5.21456 25.531 -25.531 -5.21456 762.3 221.95)" | |
| 21 | gradientUnits="userSpaceOnUse" /> | |
| 22 | <linearGradient | |
| 23 | id="a" | |
| 24 | x1=".152" | |
| 25 | x2=".969" | |
| 26 | y1="-.045" | |
| 27 | y2="-.049" | |
| 28 | gradientTransform="matrix(-5.21456 25.531 -25.531 -5.21456 762.3 221.95)" | |
| 29 | gradientUnits="userSpaceOnUse"> | |
| 30 | <stop | |
| 31 | offset="0" | |
| 32 | stop-color="#ec706a" | |
| 33 | id="stop1" /> | |
| 34 | <stop | |
| 35 | offset="1" | |
| 36 | stop-color="#ecd980" | |
| 37 | id="stop2" /> | |
| 38 | </linearGradient> | |
| 39 | </defs> | |
| 40 | <path | |
| 41 | id="path32" | |
| 42 | style="fill:#51a9cf;fill-opacity:1" | |
| 43 | d="M 76.696905 4.2860773 C 75.760905 4.2860773 74.906182 4.5252025 74.132183 5.0038687 C 73.363517 5.4772016 72.75569 6.1470652 72.309024 7.0130643 C 71.857024 7.8677302 71.631024 8.8301007 71.631024 9.9007663 C 71.631024 10.551432 71.749377 11.151121 71.986044 11.69912 C 72.22271 12.242453 72.599183 12.680446 73.115183 13.013779 C 73.609849 13.347112 74.249645 13.514011 75.034977 13.514011 L 75.036011 13.515045 C 75.401344 13.515045 75.774515 13.471486 76.155848 13.384819 C 76.543181 13.293486 76.914668 13.167728 77.270001 13.007061 C 77.63 12.840395 77.957928 12.643578 78.253928 12.416912 C 78.554594 12.191579 78.804426 11.944165 79.003759 11.674832 L 78.278216 10.319866 C 78.132883 10.513199 77.91502 10.709648 77.625021 10.908982 C 77.334354 11.107648 77.019551 11.274547 76.680885 11.409213 C 76.342218 11.54388 76.024997 11.610753 75.728997 11.610753 C 75.341664 11.610753 75.061765 11.52782 74.889765 11.361154 C 74.717766 11.194487 74.615472 10.938419 74.582805 10.593753 L 74.582805 10.367925 C 75.255471 10.195925 75.882142 9.9917041 76.462808 9.7550376 C 77.043475 9.5130379 77.551793 9.2307205 77.987792 8.9080542 C 78.423125 8.5860545 78.761759 8.2149346 79.003759 7.794935 C 79.251092 7.3702687 79.374799 6.8890835 79.374799 6.351084 C 79.374799 5.9477511 79.27544 5.5926906 79.076106 5.2860243 C 78.882773 4.9746912 78.586815 4.7302129 78.188815 4.5522131 C 77.796149 4.3748799 77.298904 4.2860773 76.696905 4.2860773 z M 76.066964 5.8188125 C 76.28763 5.8188125 76.440888 5.8834174 76.526888 6.0120839 C 76.618888 6.1360838 76.664865 6.3406717 76.664865 6.6260048 C 76.664865 6.9160045 76.602828 7.1983219 76.478828 7.4729882 C 76.355495 7.7416546 76.189113 7.986133 75.979113 8.2067994 C 75.774447 8.4267992 75.548446 8.6044044 75.301113 8.7390709 C 75.054447 8.8737374 74.804098 8.9485318 74.550765 8.9638652 C 74.561478 8.6893636 74.596925 8.3453745 74.656186 7.9318786 C 74.714853 7.517879 74.857404 7.1312164 75.08407 6.7712167 C 75.25607 6.5018837 75.414314 6.275883 75.55898 6.0932165 C 75.704314 5.9105501 75.873631 5.8188125 76.066964 5.8188125 z " /> | |
| 44 | <path | |
| 45 | id="path25" | |
| 46 | style="fill:#51a9cf;fill-opacity:1" | |
| 47 | d="M 15.616943 4.2860773 C 14.680944 4.2860773 13.826221 4.5252025 13.052222 5.0038687 C 12.283556 5.4772016 11.675729 6.1470652 11.229063 7.0130643 C 10.777063 7.8677302 10.551062 8.8301007 10.551062 9.9007663 C 10.551062 10.551432 10.669416 11.151121 10.906082 11.69912 C 11.142749 12.24312 11.519222 12.68148 12.035221 13.014813 C 12.529888 13.348146 13.169684 13.515045 13.955016 13.515045 L 13.95605 13.515045 C 14.321383 13.515045 14.694554 13.471486 15.075887 13.384819 C 15.46322 13.293486 15.834707 13.167728 16.19004 13.007061 C 16.550039 12.840395 16.877967 12.643578 17.173967 12.416912 C 17.474633 12.191579 17.724464 11.944165 17.923798 11.674832 L 17.198255 10.319866 C 17.052922 10.513199 16.834692 10.709648 16.544026 10.908982 C 16.254026 11.107648 15.93959 11.274547 15.600924 11.409213 C 15.262257 11.54388 14.945036 11.610753 14.649036 11.610753 C 14.261703 11.610753 13.981804 11.52782 13.809804 11.361154 C 13.637804 11.194487 13.53551 10.938419 13.502844 10.593753 L 13.502844 10.367925 C 14.17551 10.195925 14.802181 9.9917041 15.382847 9.7550376 C 15.963513 9.5130379 16.471464 9.2307205 16.906797 8.9080542 C 17.342797 8.5860545 17.681798 8.2149346 17.923798 7.794935 C 18.171131 7.3702687 18.294837 6.8890835 18.294837 6.351084 C 18.294837 5.9477511 18.195478 5.5926906 17.996145 5.2860243 C 17.802812 4.9746912 17.506853 4.7302129 17.108854 4.5522131 C 16.716188 4.3748799 16.218943 4.2860773 15.616943 4.2860773 z M 14.987003 5.8188125 C 15.207669 5.8188125 15.360927 5.8834174 15.446927 6.0120839 C 15.538926 6.1360838 15.584904 6.3406717 15.584904 6.6260048 C 15.584904 6.9160045 15.522867 7.1983219 15.398867 7.4729882 C 15.275534 7.7416546 15.109152 7.986133 14.899152 8.2067994 C 14.694486 8.4267992 14.468485 8.6044044 14.221152 8.7390709 C 13.974485 8.8737374 13.724137 8.9488987 13.470804 8.9648987 C 13.481471 8.6902323 13.516892 8.3458782 13.576225 7.9318786 C 13.634891 7.517879 13.777443 7.1312164 14.004109 6.7712167 C 14.176109 6.5018837 14.334353 6.275883 14.479019 6.0932165 C 14.624352 5.9105501 14.79367 5.8188125 14.987003 5.8188125 z " /> | |
| 48 | <path | |
| 49 | id="path27" | |
| 50 | style="fill:#51a9cf;fill-opacity:1" | |
| 51 | d="M 24.377202 4.2860773 C 23.441869 4.2860773 22.586629 4.5252025 21.811963 5.0038687 C 21.043297 5.4772016 20.43547 6.1470652 19.988804 7.0130643 C 19.536805 7.8677302 19.310804 8.8301007 19.310804 9.9007663 C 19.310804 10.551432 19.429158 11.151121 19.665824 11.69912 C 19.90249 12.24312 20.278964 12.68148 20.794963 13.014813 C 21.290296 13.348146 21.930092 13.515045 22.714758 13.515045 C 23.080757 13.515045 23.455179 13.471486 23.837179 13.384819 C 24.223845 13.293486 24.594448 13.167728 24.949781 13.007061 C 25.309781 12.840395 25.638225 12.643578 25.934225 12.416912 C 26.235558 12.191579 26.48539 11.944165 26.684056 11.674832 L 25.957996 10.319866 C 25.812663 10.513199 25.594801 10.709648 25.304801 10.908982 C 25.014135 11.107648 24.699848 11.274547 24.361182 11.409213 C 24.022516 11.54388 23.705144 11.610753 23.409811 11.610753 C 23.021812 11.610753 22.742062 11.52782 22.570063 11.361154 C 22.398729 11.194487 22.296435 10.938419 22.263102 10.593753 L 22.263102 10.367925 C 22.935768 10.195925 23.56244 9.9917041 24.143106 9.7550376 C 24.723772 9.5130379 25.23209 9.2307205 25.668089 8.9080542 C 26.103422 8.5860545 26.442423 8.2149346 26.68509 7.794935 C 26.931756 7.3702687 27.055096 6.8890835 27.055096 6.351084 C 27.055096 5.9477511 26.955587 5.5926906 26.75692 5.2860243 C 26.562921 4.9746912 26.267112 4.7302129 25.869112 4.5522131 C 25.477113 4.3748799 24.979868 4.2860773 24.377202 4.2860773 z M 23.747778 5.8188125 C 23.967778 5.8188125 24.121552 5.8834174 24.208218 6.0120839 C 24.299552 6.1360838 24.345162 6.3406717 24.345162 6.6260048 C 24.345162 6.9160045 24.283125 7.1983219 24.159125 7.4729882 C 24.035792 7.7416546 23.868893 7.986133 23.658894 8.2067994 C 23.454894 8.4267992 23.228893 8.6044044 22.980893 8.7390709 C 22.734227 8.8737374 22.484396 8.9488987 22.231062 8.9648987 C 22.242396 8.6902323 22.2773 8.3458782 22.335966 7.9318786 C 22.3953 7.517879 22.537851 7.1312164 22.763851 6.7712167 C 22.935851 6.5018837 23.094461 6.275883 23.239794 6.0932165 C 23.385128 5.9105501 23.554445 5.8188125 23.747778 5.8188125 z " /> | |
| 52 | <path | |
| 53 | d="M751.566 230.706c-2.527 12.283 1.949 15.415 1.949 15.415s1.787-7.837 5.132-6.391c2.871 1.237.668 6.515.668 6.515s18.882-18.12 4.517-24.348c0 0-3.15-1.644-6.354-.387-2.345.92-4.718 3.391-5.912 9.196" | |
| 54 | paint-order="stroke" | |
| 55 | style="fill:url(#b);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.72817;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" | |
| 56 | transform="matrix(.20372 0 0 .20372 -95.423 -31.173)" | |
| 57 | id="path2" /> | |
| 58 | <path | |
| 59 | fill="#126d95" | |
| 60 | d="m771.784 223.478-6.529-10.073 1.046-3.86 5.948 1.61zM773.124 207.876l-.545 2.01-5.935-1.607.545-2.01z" | |
| 61 | style="stroke-width:.59394" | |
| 62 | transform="matrix(.20372 0 0 .20372 -95.423 -31.173)" | |
| 63 | id="path3" /> | |
| 64 | <g | |
| 65 | fill="#51a9cf" | |
| 66 | id="g4"> | |
| 67 | <path | |
| 68 | d="M62.74 3.45a.467.467 0 0 0-.446.344.47.47 0 0 0 .332.578l-1.69 6.235-1.21-.328c1.012-3.807.605-6.29.605-6.29A13.105 13.105 0 0 0 63.674 0zM60.87 10.849l-.11.41-1.215-.33.11-.41zM60.69 11.516l-.213.787-2.182 1.1 1.183-2.216z" | |
| 69 | id="path4" /> | |
| 70 | </g> | |
| 71 | <path | |
| 72 | fill="#126d95" | |
| 73 | d="m777.66 170.312 4.589-16.945c-.19 4.992.169 14.581 4.286 25.19 0 0-7.065 9.8-13.065 28.138l-5.96-1.614 8.295-30.605a2.308 2.308 0 0 0 1.855-4.164" | |
| 74 | style="stroke-width:.59394" | |
| 75 | transform="matrix(.20372 0 0 .20372 -95.423 -31.173)" | |
| 76 | id="path5" /> | |
| 77 | <path | |
| 78 | style="fill:#51a9cf;fill-opacity:1" | |
| 79 | d="M 52.691,13.385 53.917,4.4 h 2.71 l -0.129,1.872 q 0.178,-0.525 0.516,-0.976 0.34,-0.452 0.799,-0.726 0.46,-0.283 0.992,-0.283 0.428,0 0.549,0.097 l -0.5,2.856 Q 58.797,7.143 58.555,7.086 58.313,7.03 58.079,7.03 q -0.347,0 -0.645,0.072 -0.29,0.065 -0.565,0.218 -0.266,0.154 -0.548,0.404 l -0.823,5.662 z" | |
| 80 | id="path31" /> | |
| 81 | <path | |
| 82 | style="fill:#51a9cf;fill-opacity:1" | |
| 83 | d="M 40.107,13.385 38.639,1.334 h 2.823 l 0.452,7.711 0.161,1.759 0.532,-1.759 2.033,-5.05 -0.29,-2.661 h 2.726 l 0.468,7.711 0.178,1.759 0.42,-1.759 2.629,-7.711 h 2.872 l -4.68,12.051 H 45.705 L 45.253,9.078 45.108,7.755 44.624,9.078 43.059,13.385 Z" | |
| 84 | id="path30" /> | |
| 85 | <path | |
| 86 | style="fill:#51a9cf;fill-opacity:1" | |
| 87 | d="M 28.088,13.385 29.314,4.4 h 2.694 l -0.113,1.63 q 0.549,-0.823 1.29,-1.283 0.75,-0.46 1.63,-0.46 1.065,0 1.622,0.549 0.556,0.548 0.556,1.823 0,0.177 -0.056,0.645 -0.049,0.46 -0.13,1.025 -0.072,0.556 -0.137,1.024 L 36.582,9.95 q -0.057,0.436 -0.137,1 -0.073,0.557 -0.154,1.097 -0.072,0.54 -0.129,0.912 l -0.056,0.427 h -2.807 q 0.12,-0.806 0.217,-1.516 0.105,-0.718 0.186,-1.34 0.08,-0.628 0.145,-1.16 Q 33.944,8.554 34,8.014 34.065,7.465 34.073,7.175 34.089,6.748 33.928,6.578 33.767,6.401 33.428,6.401 33.21,6.401 32.968,6.506 32.734,6.61 32.5,6.812 32.266,7.006 32.048,7.28 31.838,7.554 31.669,7.885 l -0.774,5.501 z" | |
| 88 | id="path29" /> | |
| 89 | <path | |
| 90 | style="fill:#51a9cf;fill-opacity:1" | |
| 91 | d="M 0,13.385 1.726,1.334 h 2.71 L 3.662,6.609 7.905,1.334 H 11.1 L 6.324,6.642 q 0.113,0.21 0.274,0.532 0.17,0.315 0.404,0.79 0.242,0.468 0.572,1.138 0.34,0.67 0.8,1.59 0.209,0.427 0.41,0.814 0.202,0.379 0.388,0.718 0.185,0.33 0.355,0.62 l 0.33,0.541 H 6.582 Q 6.55,13.329 6.462,13.151 6.372,12.974 6.235,12.675 6.098,12.369 5.905,11.949 l -0.42,-0.968 -1,-2.307 -1.29,1.356 -0.485,3.355 z" | |
| 92 | id="path6" /> | |
| 93 | <path | |
| 94 | style="fill:#51a9cf;fill-opacity:1" | |
| 95 | d="m 67.21,13.547 q -0.854,0 -1.355,-0.444 -0.5,-0.444 -0.5,-1.25 0,-0.073 0.024,-0.347 l 0.057,-0.613 q 0.04,-0.347 0.08,-0.638 L 66.081,6.109 H 65.42 l 0.29,-1.71 h 0.823 l 0.774,-2.162 h 2.114 L 69.114,4.4 h 1.759 l -0.178,1.71 h -1.79 l -0.307,2.227 -0.194,1.387 q -0.072,0.508 -0.104,0.774 l -0.049,0.388 q -0.008,0.12 -0.008,0.226 0,0.242 0.113,0.379 0.113,0.137 0.436,0.137 0.169,0 0.427,-0.089 0.258,-0.097 0.516,-0.242 0.259,-0.153 0.428,-0.315 l 0.436,1.34 q -0.42,0.322 -0.944,0.605 -0.517,0.274 -1.13,0.451 -0.613,0.17 -1.314,0.17 z" | |
| 96 | id="path7" /> | |
| 97 | </svg> | |
| 98 | ||
| 1 | <svg width="300" height="71.784" viewBox="0 0 79.375 18.993" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient xlink:href="#a" id="b" x1=".152" x2=".969" y1="-.045" y2="-.049" gradientTransform="rotate(101.544 290.55 422.146) scale(26.05808)" gradientUnits="userSpaceOnUse"/><linearGradient id="a" x1=".152" x2=".969" y1="-.045" y2="-.049" gradientTransform="rotate(101.544 290.55 422.146) scale(26.05808)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ec706a"/><stop offset="1" stop-color="#ecd980"/></linearGradient></defs><path style="fill:#51a9cf;fill-opacity:1" d="M76.697 4.286c-.936 0-1.79.24-2.565.718-.768.473-1.376 1.143-1.823 2.01-.452.854-.678 1.816-.678 2.887 0 .65.118 1.25.355 1.798.237.543.613.981 1.13 1.315.494.333 1.134.5 1.919.5l.001.001c.365 0 .739-.044 1.12-.13a6.09 6.09 0 0 0 1.114-.378c.36-.167.688-.363.984-.59.3-.225.55-.473.75-.742l-.726-1.355c-.145.193-.363.39-.653.589a4.83 4.83 0 0 1-.944.5 2.583 2.583 0 0 1-.952.202c-.387 0-.667-.083-.84-.25-.171-.167-.274-.423-.306-.767v-.226a14.49 14.49 0 0 0 1.88-.613 7.222 7.222 0 0 0 1.525-.847 3.661 3.661 0 0 0 1.016-1.113c.247-.425.37-.906.37-1.444 0-.403-.099-.758-.298-1.065-.193-.311-.49-.556-.887-.734-.393-.177-.89-.266-1.492-.266zm-.63 1.533c.22 0 .374.064.46.193.092.124.138.329.138.614 0 .29-.062.572-.186.847-.124.269-.29.513-.5.734-.205.22-.43.397-.678.532a1.78 1.78 0 0 1-.75.225c.01-.275.046-.619.105-1.032.059-.414.201-.8.428-1.16.172-.27.33-.496.475-.679.145-.182.315-.274.508-.274zM15.617 4.286c-.936 0-1.79.24-2.565.718-.768.473-1.376 1.143-1.823 2.01-.452.854-.678 1.816-.678 2.887 0 .65.118 1.25.355 1.798.237.544.613.982 1.13 1.316.494.333 1.134.5 1.919.5h.001c.365 0 .739-.044 1.12-.13a6.09 6.09 0 0 0 1.114-.378c.36-.167.688-.363.984-.59.3-.225.55-.473.75-.742l-.726-1.355c-.145.193-.363.39-.654.589-.29.199-.604.366-.943.5a2.583 2.583 0 0 1-.952.202c-.387 0-.667-.083-.84-.25-.171-.167-.273-.423-.306-.767v-.226a14.49 14.49 0 0 0 1.88-.613 7.197 7.197 0 0 0 1.524-.847 3.656 3.656 0 0 0 1.017-1.113c.247-.425.37-.906.37-1.444 0-.403-.099-.758-.298-1.065-.193-.311-.49-.556-.887-.734-.393-.177-.89-.266-1.492-.266zm-.63 1.533c.22 0 .374.064.46.193.092.124.138.329.138.614 0 .29-.062.572-.186.847-.123.269-.29.513-.5.734-.205.22-.43.397-.678.532-.247.135-.497.21-.75.226.01-.275.046-.62.105-1.033.059-.414.201-.8.428-1.16.172-.27.33-.496.475-.679.145-.182.315-.274.508-.274zM24.377 4.286c-.935 0-1.79.24-2.565.718-.769.473-1.377 1.143-1.823 2.01-.452.854-.678 1.816-.678 2.887 0 .65.118 1.25.355 1.798.236.544.613.982 1.129 1.316.495.333 1.135.5 1.92.5.366 0 .74-.044 1.122-.13a6.091 6.091 0 0 0 1.113-.378 5.22 5.22 0 0 0 .984-.59 3.6 3.6 0 0 0 .75-.742l-.726-1.355c-.145.193-.363.39-.653.589-.29.199-.605.366-.944.5a2.583 2.583 0 0 1-.951.202c-.388 0-.668-.083-.84-.25-.171-.167-.274-.423-.307-.767v-.226a14.49 14.49 0 0 0 1.88-.613 7.222 7.222 0 0 0 1.525-.847 3.673 3.673 0 0 0 1.017-1.113c.247-.425.37-.906.37-1.444 0-.403-.1-.758-.298-1.065-.194-.311-.49-.556-.888-.734-.392-.177-.89-.266-1.492-.266zm-.63 1.533c.22 0 .375.064.461.193.092.124.137.329.137.614 0 .29-.062.572-.186.847-.123.269-.29.513-.5.734-.204.22-.43.397-.678.532a1.79 1.79 0 0 1-.75.226c.011-.275.046-.62.105-1.033.06-.414.202-.8.428-1.16.172-.27.33-.496.476-.679.145-.182.314-.274.508-.274z"/><path d="M751.566 230.706c-2.527 12.283 1.949 15.415 1.949 15.415s1.787-7.837 5.132-6.391c2.871 1.237.668 6.515.668 6.515s18.882-18.12 4.517-24.348c0 0-3.15-1.644-6.354-.387-2.345.92-4.718 3.391-5.912 9.196" paint-order="stroke" style="fill:url(#b);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.72817;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" transform="translate(-95.423 -31.173) scale(.20372)"/><path fill="#126d95" d="m771.784 223.478-6.529-10.073 1.046-3.86 5.948 1.61zm1.34-15.602-.545 2.01-5.935-1.607.545-2.01z" style="stroke-width:.59394" transform="translate(-95.423 -31.173) scale(.20372)"/><path d="M62.74 3.45a.467.467 0 0 0-.446.344.47.47 0 0 0 .332.578l-1.69 6.235-1.21-.328c1.012-3.807.605-6.29.605-6.29A13.105 13.105 0 0 0 63.674 0zm-1.87 7.399-.11.41-1.215-.33.11-.41zm-.18.667-.213.787-2.182 1.1 1.183-2.216z" fill="#51a9cf"/><path fill="#126d95" d="m777.66 170.312 4.589-16.945c-.19 4.992.169 14.581 4.286 25.19 0 0-7.065 9.8-13.065 28.138l-5.96-1.614 8.295-30.605a2.308 2.308 0 0 0 1.855-4.164" style="stroke-width:.59394" transform="translate(-95.423 -31.173) scale(.20372)"/><path style="fill:#51a9cf;fill-opacity:1" d="M52.691 13.385 53.917 4.4h2.71l-.129 1.872q.178-.525.516-.976.34-.452.799-.726.46-.283.992-.283.428 0 .549.097l-.5 2.856q-.057-.097-.299-.154-.242-.056-.476-.056-.347 0-.645.072-.29.065-.565.218-.266.154-.548.404l-.823 5.662zM40.107 13.385 38.639 1.334h2.823l.452 7.711.161 1.759.532-1.759 2.033-5.05-.29-2.661h2.726l.468 7.711.178 1.759.42-1.759 2.629-7.711h2.872l-4.68 12.051h-3.258l-.452-4.307-.145-1.323-.484 1.323-1.565 4.307ZM28.088 13.385 29.314 4.4h2.694l-.113 1.63q.549-.823 1.29-1.283.75-.46 1.63-.46 1.065 0 1.622.549.556.548.556 1.823 0 .177-.056.645-.049.46-.13 1.025-.072.556-.137 1.024l-.088.597q-.057.436-.137 1-.073.557-.154 1.097-.072.54-.129.912l-.056.427h-2.807q.12-.806.217-1.516.105-.718.186-1.34.08-.628.145-1.16.097-.816.153-1.356.065-.549.073-.839.016-.427-.145-.597-.161-.177-.5-.177-.218 0-.46.105-.234.104-.468.306-.234.194-.452.468-.21.274-.379.605l-.774 5.501zM0 13.385 1.726 1.334h2.71l-.774 5.275 4.243-5.275H11.1L6.324 6.642q.113.21.274.532.17.315.404.79.242.468.572 1.138.34.67.8 1.59.209.427.41.814.202.379.388.718.185.33.355.62l.33.541H6.582q-.032-.056-.12-.234-.09-.177-.227-.476-.137-.306-.33-.726l-.42-.968-1-2.307-1.29 1.356-.485 3.355zM67.21 13.547q-.854 0-1.355-.444-.5-.444-.5-1.25 0-.073.024-.347l.057-.613q.04-.347.08-.638l.565-4.146h-.661l.29-1.71h.823l.774-2.162h2.114L69.114 4.4h1.759l-.178 1.71h-1.79l-.307 2.227-.194 1.387q-.072.508-.104.774l-.049.388q-.008.12-.008.226 0 .242.113.379t.436.137q.169 0 .427-.089.258-.097.516-.242.259-.153.428-.315l.436 1.34q-.42.322-.944.605-.517.274-1.13.451-.613.17-1.314.17z"/></svg> |
| 1 | <!DOCTYPE html> | |
| 2 | <html lang="en"> | |
| 3 | <head> | |
| 4 | <title>KeenWrite</title> | |
| 5 | <meta charset="UTF-8"> | |
| 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 7 | <meta name="description" content="cross-platform, open-source desktop editor"> | |
| 8 | <meta name="keywords" content="markdown, text, editor, software"> | |
| 9 | <meta name="robots" content="index, follow"> | |
| 10 | <link rel="stylesheet" href="styles/base.css" | |
| 11 | media="print" onload="this.media='all'"> | |
| 12 | </head> | |
| 13 | <body> | |
| 14 | <header> | |
| 15 | <img src="images/logo/title.svg" alt="KeenWrite" class="title"> | |
| 16 | <p> | |
| 17 | A free, cross-platform desktop text editor for producing beautifully | |
| 18 | typeset PDF files. | |
| 19 | </p> | |
| 20 | </header> | |
| 21 | <main> | |
| 22 | <div class="downloads"> | |
| 23 | <a href="downloads/keenwrite.bin" | |
| 24 | class="download" | |
| 25 | title="Download for 64-bit Linux (x86)" | |
| 26 | aria-label="Download for Linux"><img | |
| 27 | src="images/icons/linux.svg" | |
| 28 | alt="Download for Linux" | |
| 29 | class="download"></a> | |
| 30 | <a href="downloads/keenwrite.jar" | |
| 31 | class="download" | |
| 32 | title="Download for Java virtual machine" | |
| 33 | aria-label="Download for Java"><img | |
| 34 | src="images/icons/java.svg" | |
| 35 | alt="Download for Java" | |
| 36 | class="download"></a> | |
| 37 | <a href="downloads/KeenWrite.exe" | |
| 38 | class="download" | |
| 39 | title="Download for 64-bit Windows (x86)" | |
| 40 | aria-label="Download for Windows"><img | |
| 41 | src="images/icons/windows.svg" | |
| 42 | alt="Download for Windows" | |
| 43 | class="download"></a> | |
| 44 | <a href="downloads/keenwrite.app" | |
| 45 | class="download" | |
| 46 | title="Download for 64-bit MacOS (x86)" | |
| 47 | aria-label="Download for MacOS"><img | |
| 48 | src="images/icons/apple.svg" | |
| 49 | alt="Download for MacOS" | |
| 50 | class="download"></a> | |
| 51 | </div> | |
| 52 | </main> | |
| 53 | <nav> | |
| 54 | <ul> | |
| 55 | <li><a href="screenshots.html">screenshots</a></li> | |
| 56 | <li><a href="https://www.youtube.com/playlist?list=PLB-WIt1cZYLm1MMx2FBG9KWzPIoWZMKu_">tutorials</a></li> | |
| 57 | <li><a href="https://gitlab.com/DaveJarvis/KeenWrite">sources</a></li> | |
| 58 | <li><a href="https://gitlab.com/DaveJarvis/KeenWrite/issues">issues</a></li> | |
| 59 | <li><a href="https://gitlab.com/DaveJarvis/KeenWrite/-/blob/main/docs/README.md">documentation</a></li> | |
| 60 | </ul> | |
| 61 | </nav> | |
| 62 | <footer> | |
| 63 | © 2023, White Magic Software, Ltd. | |
| 64 | </footer> | |
| 65 | </body> | |
| 66 | </html> | |
| 67 | ||
| 68 | 1 |
| 1 | <!DOCTYPE html> | |
| 2 | <html lang="en"> | |
| 3 | <head> | |
| 4 | <title>KeenWrite</title> | |
| 5 | <meta charset="UTF-8"> | |
| 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 7 | <meta name="description" content="cross-platform, open-source desktop editor"> | |
| 8 | <meta name="keywords" content="markdown, text, editor, software"> | |
| 9 | <meta name="robots" content="index, follow"> | |
| 10 | <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline';"> | |
| 11 | <style><!--#include file="styles/base.css" --></style> | |
| 12 | </head> | |
| 13 | <body> | |
| 14 | <header> | |
| 15 | <img src="images/logo/title.svg" alt="KeenWrite" class="title"> | |
| 16 | <p> | |
| 17 | A free, cross-platform desktop text editor for producing beautifully | |
| 18 | typeset PDF files. | |
| 19 | </p> | |
| 20 | </header> | |
| 21 | <main> | |
| 22 | <div class="downloads"> | |
| 23 | <a href="downloads/keenwrite.bin" | |
| 24 | class="download" | |
| 25 | title="Download for 64-bit Linux (x86)" | |
| 26 | aria-label="Download for Linux"><img | |
| 27 | src="images/icons/linux.svg" | |
| 28 | alt="Download for Linux" | |
| 29 | class="download"></a> | |
| 30 | <a href="downloads/keenwrite.jar" | |
| 31 | class="download" | |
| 32 | title="Download for Java virtual machine" | |
| 33 | aria-label="Download for Java"><img | |
| 34 | src="images/icons/java.svg" | |
| 35 | alt="Download for Java" | |
| 36 | class="download"></a> | |
| 37 | <a href="downloads/KeenWrite.exe" | |
| 38 | class="download" | |
| 39 | title="Download for 64-bit Windows (x86)" | |
| 40 | aria-label="Download for Windows"><img | |
| 41 | src="images/icons/windows.svg" | |
| 42 | alt="Download for Windows" | |
| 43 | class="download"></a> | |
| 44 | <a href="downloads/keenwrite.app" | |
| 45 | class="download" | |
| 46 | title="Download for 64-bit MacOS (x86)" | |
| 47 | aria-label="Download for MacOS"><img | |
| 48 | src="images/icons/apple.svg" | |
| 49 | alt="Download for MacOS" | |
| 50 | class="download"></a> | |
| 51 | </div> | |
| 52 | <!--#config timefmt="%d-%b-%Y" --> | |
| 53 | <p class="version"> | |
| 54 | <strong>Version <!--#include file="downloads/version.txt" --></strong> | |
| 55 | <br><!--#flastmod virtual="downloads/version.txt" --> | |
| 56 | <br><!--#exec cmd="./count.sh" --> downloads | |
| 57 | </p> | |
| 58 | </main> | |
| 59 | <nav> | |
| 60 | <ul> | |
| 61 | <li><a href="screenshots.html">screenshots</a></li> | |
| 62 | <li><a href="https://www.youtube.com/playlist?list=PLB-WIt1cZYLm1MMx2FBG9KWzPIoWZMKu_">tutorials</a></li> | |
| 63 | <li><a href="https://gitlab.com/DaveJarvis/KeenWrite">sources</a></li> | |
| 64 | <li><a href="https://gitlab.com/DaveJarvis/KeenWrite/issues">issues</a></li> | |
| 65 | <li><a href="https://gitlab.com/DaveJarvis/KeenWrite/-/blob/main/docs/README.md">documentation</a></li> | |
| 66 | </ul> | |
| 67 | </nav> | |
| 68 | <footer> | |
| 69 | © 2023, White Magic Software, Ltd. | |
| 70 | </footer> | |
| 71 | </body> | |
| 72 | </html> | |
| 73 | ||
| 1 | 74 |
| 3 | 3 | crawl-delay: 60 |
| 4 | 4 | |
| 5 | user-agent: Googlebot | |
| 6 | disallow: /repository/* | |
| 7 | disallow: /downloads/* | |
| 8 | user-agent: Bingbot | |
| 9 | disallow: /repository/* | |
| 10 | disallow: /downloads/* | |
| 11 | user-agent: YandexBot | |
| 12 | disallow: /repository/* | |
| 13 | disallow: /downloads/* | |
| 14 | user-agent: MicrosoftBot | |
| 15 | disallow: /repository/* | |
| 16 | disallow: /downloads/* | |
| 17 | ||
| 18 |
| 1 | /* ************************************************************************ | |
| 1 | /* | |
| 2 | 2 | * Page |
| 3 | * ************************************************************************ */ | |
| 3 | */ | |
| 4 | 4 | :root { |
| 5 | 5 | --accent-colour: #ec706a; |
| ... | ||
| 21 | 21 | } |
| 22 | 22 | |
| 23 | /* ************************************************************************ | |
| 23 | /* | |
| 24 | 24 | * Header |
| 25 | * ************************************************************************ */ | |
| 26 | ||
| 25 | */ | |
| 27 | 26 | header { |
| 28 | 27 | /* Avoid being flush with top of page, put space between the title and |
| ... | ||
| 43 | 42 | } |
| 44 | 43 | |
| 45 | /* ************************************************************************ | |
| 44 | /* | |
| 46 | 45 | * Screenshots |
| 47 | * ************************************************************************ */ | |
| 48 | ||
| 46 | */ | |
| 49 | 47 | main.screenshots { |
| 50 | 48 | text-align: center; |
| ... | ||
| 70 | 68 | } |
| 71 | 69 | |
| 72 | /* ************************************************************************ | |
| 73 | * Download buttons | |
| 74 | * ************************************************************************ */ | |
| 70 | /* | |
| 71 | * Version information | |
| 72 | */ | |
| 73 | main > p.version { | |
| 74 | text-align: center; | |
| 75 | } | |
| 75 | 76 | |
| 77 | /* | |
| 78 | * Download buttons | |
| 79 | */ | |
| 76 | 80 | main > div.downloads { |
| 77 | 81 | /* Arrange the buttons in a responsive, 2 x 2 grid. */ |
| ... | ||
| 104 | 108 | filter: invert(6%) |
| 105 | 109 | sepia(58%) saturate(857%) hue-rotate(158deg) brightness(91%) contrast(91%); |
| 110 | ||
| 111 | width: 157px; | |
| 112 | height: 75px; | |
| 106 | 113 | } |
| 107 | 114 | |
| 108 | /* ************************************************************************ | |
| 115 | /* | |
| 109 | 116 | * Navigation |
| 110 | * ************************************************************************ */ | |
| 111 | ||
| 117 | */ | |
| 112 | 118 | nav { |
| 113 | 119 | /* Don't crowd navigation links against the download buttons. */ |
| ... | ||
| 140 | 146 | } |
| 141 | 147 | |
| 142 | /* ************************************************************************ | |
| 148 | /* | |
| 143 | 149 | * Footer |
| 144 | * ************************************************************************ */ | |
| 150 | */ | |
| 145 | 151 | footer { |
| 146 | 152 | margin-top: 2em; |