Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
A .gitattributes
1
# always use LF line endings
2
3
# ALL FILES:
4
#   Normalize line endings to LF on checkin and
5
#   prevent conversion to CRLF when the file is checked out.
6
7
* text eol=lf
8
9
10
# BINARY FILES:
11
#   Disable line ending normalize on checkin.
12
13
*.blend binary
14
15
*.bin binary
16
*.bmp binary
17
*.eps binary
18
*.exe binary
19
*.gif binary
20
*.ico binary
21
*.jar binary
22
*.jpg binary
23
*.mng binary
24
*.png binary
25
*.zip binary
26
*.otf binary
27
*.ttf binary
28
129
A .gitignore
1
dist
2
*.bin
3
*.exe
4
*.app
5
/*.jar
6
build
7
.gradle
8
video
9
.settings
10
.classpath
11
.idea
12
count/
13
themes/
14
quotes/
15
tex/
16
spell/
17
keenwrite.build_artifacts.txt
18
todo
19
tokens/
120
A BUILD.md
1
# Introduction
2
3
This document describes how to build the application and platform binaries.
4
5
# Requirements
6
7
Download and install the following software packages:
18
9
* [JDK 22](https://bell-sw.com/pages/downloads) (Full JDK + JavaFX)
10
* [Gradle 8.9](https5
A LICENSE.md
1
# License
2
3
Copyright 2023 White Magic Software, Ltd.
4
5
All rights reserved.
6
7
Redistribution and use in source and binary forms, with or without
8
modification, are permitted provided that the following conditions are met:
9
10
* Redistributions of source
11
  disclaimer.
12
13
* 
14
  disclaimer in the
15
 
16
 INCLUDING, BUT NOT
17
18
A PARTICULAR PURPOSE ARE 
19
20
 
21
22
THEORY OF LIABILITY, 
123
A R/bootstrap.R
1
setwd( v$application$r$working$directory )
2
3
# To reference additional R variables in documents, define them such as:
4
# assign( "variable", v$key$name, envir = .GlobalEnv )
5
6
source( "pluralize.R" )
7
source( "possessive.R" )
8
source( "conversion.R" )
9
source( "csv.R" )
10
111
A R/conversion.R
1
1
# -----------------------------------------------------------------------------
2
# Copyright 2020, White Magic Software, Ltd.
3
# 
4
# Permission is hereby granted, free of charge, to any person obtaining
5
# a copy of this software and associated documentation ounds the given value to the nearest integer.
6
#
7
# @param n The value roundround.intformat( round( n
A R/csv.R
1
# -----------------------------------------------------------------------------
2
# Copyright 2020, White Magic Software, Ltd.
3
# 
4
# Permission is hereby granted, free of charge, to any person obtaining
5
# a copy of this software and associated documentation , caption = ""Use pandoc syntax for table captions.
6
  if( caption != "" ) {
7
    caption <- paste( '\n[', caption, ']\n', sep='' )
8
  }
9
10
  # Crsep=''
11
    ),
12
    '|',
13
    caption,
14
    sep=''
15
  )
16
}
17
118
A R/numeric.R
1
# TODO: Finish the implementation
2
3
# -----------------------------------------------------------------------------
4
# Converts an integer value into English words. Negative numbers are prefixed
5
# with the word minus. This is useful for very large numbers.
6
#
7
# See https://english.stackexchange.com/a/111837/22099
8
#
9
# @param n Any integer value, including zero, and negative numbers.
10
# -----------------------------------------------------------------------------
11
to.words <- function( n ) {
12
  s <- 'zero';
13
14
  if( n > 0 ) {
15
    s <- to.words.nz( n );
16
  }
17
  else if( n < 0 ) {
18
    s <- paste0( 'minus ', to.words.nz( -n ) );
19
  }
20
21
  s
22
}
23
24
# -----------------------------------------------------------------------------
25
# Converts a non-zero number into English words.
26
# -----------------------------------------------------------------------------
27
to.words.nz <- function( n ) {
28
  scales <- c(
29
    "thousand", "million", "billion", "trillion", "quadrillion",
30
    "quintillion", "sextillion", "septillion", "octillion", "nonillion",
31
    "decillion", "undecillion", "duodecillion", "tredecillion",
32
    "quattuordecillion", "quindecillion", "sexdecillion", "septendecillion",
33
    "octodecillion", "novemdecillion", "vigintillion", "centillion",
34
    "quadrillion", "quitillion", "sextillion"
35
  );
36
37
  i <- 0;
38
  s <- "";
39
40
  while( n > 0 ) {
41
    if( !(n %% 1000 == 0) ) {
42
      j <- if( n < 100 ) "," else "";
43
      s <- paste( to.words.help( n %% 1000 ), scales[ i ], j, s );
44
    }
45
46
    n <- floor( n / 1000 );
47
    i <- i + 1;
48
  }
49
50
  s
51
}
52
53
to.words.help <- function( n ) {
54
  low <- c( 
55
    "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
56
    "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
57
    "seventeen", "eighteen", "nineteen"
58
  );
59
60
  tens <- c(
61
    "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
62
  );
63
64
  if( n < 20 ) {
65
    s <- low[ n ];
66
  }
67
  else if( n < 100 ) {
68
    d <- n %% 10;
69
    j <- if( d > 0 ) "-" else "";
70
    s <- paste0( tens[ (n / 10) - 1 ], j, to.words.help( d ) );
71
  }
72
  else {
73
    d <- (n / 100);
74
    r <- (n %% 100);
75
    j <- if( r > 0 ) "and" else "";
76
    s <- paste( low[ d ], "hundred", j, to.words.help( r ) );
77
  }
78
79
  s
80
}
81
182
A R/pluralize.R
1
1
# -----------------------------------------------------------------------------
2
# Copyright 2021 Robin Gertenbach.
3
#
4
# Copyright 2021 White Magic Software, Ltd.
5
# 
6
# Permission is hereby granted, free of charge, to any person obtaining
7
# a copy of this so
A R/possessive.R
1
1
# -----------------------------------------------------------------------------
2
# Copyright 2020, White Magic Software, Ltd.
3
# 
4
# Permission is hereby granted, free of charge, to any person obtaining
5
# a copy of this software and associated documentation madj<- 'his<- 'hishers<-
A R.zip
Binary file
A README.md
1
# ![Logo](docs/images/app-title.png)
2
3
A free, open-source, cross-platform desktop Markdown editor that can produce beautifully typeset PDFs.
4
5
## Download
6
7
Visit [KeenWrite.com](https://keenwrite.com/) for downloads.
8
9
## Run
110
11
Note that the first time the a2
A README.zh-CN.md
1
# ![Logo](docs/images/app-title.zh-CN.png)
2
3
智能写入是一个文本编辑器,它使用插值字符串引用外部定义的值。
4
5
## 下载
6
7
[KeenWrite.com](https://keenwrite.com/)
8
9
## 跑
10
11
在第一次运行期间,应用程序将自身解压到本地目录中。随后的启动会更快。
12
13
### Windows
14
15
双击应用程序以启动。您必须授予应用程序运行权限。 
16
17
升级时,删除以下目录:
18
19
    C:\Users\%USERNAME%\AppData\Local\warp\packages\keenwrite.exe
20
21
### Linux
22
23
执行以下命令:
24
25
``` bash
26
chmod +x keenwrite.bin
27
./keenwrite.bin
28
```
29
30
### MacOS
31
32
执行以下命令:
33
34
``` bash
35
chmod +x keenwrite.app
36
./keenwrite.app
37
```
38
39
### Java
40
41
使用Java,首先按照以下一次性设置步骤进行操作:
42
43
1. 下载Java Runtime Environment(JRE)的*完整版本*,[JRE 21](https://bell-sw.com/pages/downloads)。
44
   * 需要BellSoft的*完整版本*中捆绑的JavaFX。
45
1. 安装JRE(将JRE的`bin`目录包含在`PATH`环境变量中)。
46
1. 打开一个新的终端。
47
1. 验证安装:`java -version`
48
1. 下载[keenwrite.jar](https://keenwrite.com/downloads/keenwrite.jar)。
49
1. 下载[keenwrite.sh](https://gitlab.com/DaveJarvis/KeenWrite/-/raw/main/keenwrite.sh?inline=false)。
50
1. 将`.jar`和`.sh`文件放置在同一个目录中。
51
1. 使`keenwrite.sh`可执行:`chmod +x keenwrite.sh`
52
53
按以下方式启动应用程序:
54
55
1. 打开一个新的终端。
56
1. 切换到`.jar`和`.sh`目录。
57
1. 运行:`./keenwrite.sh`
58
59
应用程序已启动。
60
61
## 特征
62
63
* 用户定义的插值字符串
64
* 带变量替换的实时预览
65
* 基于变量值自动完成变量名
66
* 独立于操作系统
67
* 打字时拼写检查
68
* 使用TeX的子集编写数学公式
69
* 嵌入R语句
70
71
## Typesetting
72
73
排版到 PDF 文件需要以下內容:
74
75
* [Theme Pack](https://gitlab.com/DaveJarvis/keenwrite-themes/-/releases/permalink/latest/downloads/theme-pack.zip)
76
* [ConTeXt](https://wiki.contextgarden.net/Installation)
77
78
## 软件使用
79
80
有關使用該應用程序的信息,請參[閱詳細文檔](docs/README.md)。
81
82
## 截图
83
84
![GraphViz Diagram Screenshot](docs/images/screenshots/01.png)
85
86
![Korean Poem Screenshot](docs/images/screenshots/02.png)
87
88
![TeX Equations Screenshot](docs/images/screenshots/03.png)
89
90
91
## 软件许可证
92
93
This software is licensed under the [BSD 2-Clause License](LICENSE.md) and
94
based on [Markdown-Writer-FX](licenses/MARKDOWN-WRITER-FX.md).
95
196
A bug-filter.xml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<FindBugsFilter>
3
  <Match>
4
    <Or>
5
      <Bug code="EI, EI2, CT, RV, PI" />
6
    </Or>
7
  </Match>
8
9
  <Match class="com.keenwrite.preview.HighQualityRenderingHints">
10
    <Method name="initializeRenderingHints" />
11
    <Bug code="WMI" />
12
  </Match>
13
14
  <Match class="com.keenwrite.processors.html.HtmlPreviewProcessor">
15
    <Method name="&lt;init&gt;" />
16
    <Bug code="ST" />
17
  </Match>
18
</FindBugsFilter>
119
A build.gradle
1
//file:noinspection SpellCheckingInspection
12
3
buildscript {
4
  repositories {
5
    mavenCentral()
6
    maven {
7
      url "https://plugins.gradle.org/m2/"
8
    }
9
  }
10
  dependencies {
11
    classpath 'org.owasp:dependency-check-gradle:10.0.3'
12
    classpath "com.22
13
  targetCompatibility = 22'21'
A container/.gitignore
1
token.txt
12
A container/Containerfile
1
# ########################################################################
2
#
3
# Copyright 2022 White Magic Software, Ltd.
4
#
5
# Creates a container image that can run ConTeXt to typeset documents.
6
#
7
# ########################################################################
8
9
LABEL org.opencontainers.image.description Configures a typesetting system.
10
11
FROM alpine:latest
12
ENV ENV="/etc/profile"
13
ENV PROFILE=/etc/profile
14
15
ENV INSTALL_DIR=/opt
16
ENV SOURCE_DIR=/root/source
17
ENV TARGET_DIR=/root/target
18
ENV IMAGES_DIR=/root/images
19
ENV THEMES_DIR=/root/themes
20
ENV CACHES_DIR=/root/caches
21
ENV FONTS_DIR=/usr/share/fonts/user
22
ENV DOWNLOAD_DIR=/root
23
24
ENV CONTEXT_HOME=$INSTALL_DIR/context
25
26
# ########################################################################
27
#
28
# Download all required dependencies
29
#
30
# ########################################################################
31
WORKDIR $DOWNLOAD_DIR
32
33
# Many fonts may be downloaded using Google's download URL. Example:
34
# https://fonts.google.com/download?family=Roboto%20Mono
35
36
# Fonts are repacked with minimal file set, flat directory, and license.
37
ADD "https://fonts.keenwrite.com/download/andada-pro.zip" ./
38
ADD "https://fonts.keenwrite.com/download/archivo-narrow.zip" ./
39
ADD "https://fonts.keenwrite.com/download/carlito.zip" ./
40
ADD "https://fonts.keenwrite.com/download/courier-prime.zip" ./
41
ADD "https://fonts.keenwrite.com/download/inconsolata.zip" ./
42
ADD "https://fonts.keenwrite.com/download/libre-baskerville.zip" ./
43
ADD "https://fonts.keenwrite.com/download/niconne.zip" ./
44
ADD "https://fonts.keenwrite.com/download/nunito.zip" ./
45
ADD "https://fonts.keenwrite.com/download/open-sans-emoji.zip" ./
46
ADD "https://fonts.keenwrite.com/download/pt-mono.zip" ./
47
ADD "https://fonts.keenwrite.com/download/pt-sans.zip" ./
48
ADD "https://fonts.keenwrite.com/download/pt-serif.zip" ./
49
ADD "https://fonts.keenwrite.com/download/roboto.zip" ./
50
ADD "https://fonts.keenwrite.com/download/roboto-mono.zip" ./
51
ADD "https://fonts.keenwrite.com/download/source-serif-4.zip" ./
52
ADD "https://fonts.keenwrite.com/download/underwood.zip" ./
53
54
# Typesetting software
55
ADD "http://lmtx.pragma-ade.nl/install-lmtx/context-linuxmusl.zip" "context.zip"
56
57
# ########################################################################
58
#
59
# Install components, modules, configure system, remove unnecessary files
60
#
61
# ########################################################################
62
WORKDIR $CONTEXT_HOME
63
64
RUN \
65
  apk add -t py3-cssselect && \
66
  apk add -t py3-lxml && \
67
  apk add -t py3-numpy && \
68
  apk --update --no-cache \
69
    add ca-certificates curl fontconfig inkscape rsync && \
70
  mkdir -p \
71
    "$FONTS_DIR" \
72
    "$INSTALL_DIR" \
73
    "$TARGET_DIR" \
74
    "$SOURCE_DIR" \
75
    "$THEMES_DIR" \
76
    "$IMAGES_DIR" \
77
    "$CACHES_DIR" && \
78
  echo "export CONTEXT_HOME=\"$CONTEXT_HOME\"" >> $PROFILE && \
79
  echo "export PATH=\"\$PATH:\$CONTEXT_HOME/tex/texmf-linuxmusl/bin\"" >> $PROFILE && \
80
  echo "export OSFONTDIR=\"/usr/share/fonts//\"" >> $PROFILE && \
81
  echo "PS1='\\u@typesetter:\\w\\$ '" >> $PROFILE && \
82
  unzip -d $CONTEXT_HOME $DOWNLOAD_DIR/context.zip && \
83
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/andada-pro.zip "*.otf" && \
84
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/archivo-narrow.zip "*.otf" && \
85
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/carlito.zip "*.ttf" && \
86
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/courier-prime.zip "*.ttf" && \
87
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/libre-baskerville.zip "*.ttf" && \
88
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/inconsolata.zip "*.ttf" && \
89
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/niconne.zip "*.ttf" && \
90
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/nunito.zip "*.ttf" && \
91
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/open-sans-emoji.zip "*.ttf" && \
92
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/pt-mono.zip "*.ttf" && \
93
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/pt-sans.zip "*.ttf" && \
94
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/pt-serif.zip "*.ttf" && \
95
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/roboto.zip "*.ttf" && \
96
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/roboto-mono.zip "*.ttf" && \
97
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/source-serif-4.zip "*.otf" && \
98
  unzip -j -o -d $FONTS_DIR $DOWNLOAD_DIR/underwood.zip "*.ttf" && \
99
  fc-cache -f -v && \
100
  mkdir -p tex && \
101
  sh install.sh && \
102
  rm -rf \
103
    "modules" \
104
    "/var/cache" \
105
    "/usr/share/icons" \
106
    "/opt/context/tex/texmf-context/source" \
107
    $DOWNLOAD_DIR/*.zip \
108
    $CONTEXT_HOME/tex/texmf-modules/doc \
109
    $CONTEXT_HOME/tex/texmf-context/doc && \
110
  mkdir -p $CONTEXT_HOME/tex/texmf-fonts/tex/context/user && \
111
  ln -s $CONTEXT_HOME/tex/texmf-fonts/tex/context/user $HOME/fonts && \
112
  source $PROFILE && \
113
  mtxrun --generate && \
114
  find \
115
    /usr/share/inkscape \
116
    -type f -not -iname \*.xml -exec rm {} \; && \
117
  find \
118
    $CONTEXT_HOME \
119
    -type f \
120
      \( -iname \*.pdf -o -iname \*.txt -o -iname \*.log \) \
121
    -exec rm {} \;
122
123
# ########################################################################
124
#
125
# Ensure login goes to the target directory. ConTeXt prefers to export to
126
# the current working directory.
127
#
128
# ########################################################################
129
WORKDIR $TARGET_DIR
130
1131
A container/README.md
1
# Overview
2
3
This document describes how to maintain the containerized typesetting system.
4
The container is built locally then deployed to a web server capable of
5
serving static web pages.
6
7
## Installation wizard
18
9
The installation wizard is responsible for --build --export
A container/manage.sh
1
#!/usr/bin/env bash
2
3
# ---------------------------------------------------------------------------
4
# Copyright 2022 White Magic Software, Ltd.
5
#
6
# This script manages the container configured to run ConTeXt.
7
# ---------------------------------------------------------------------------
8
9
source ../scripts/build-template
10
11
# Reads the value of a property from a properties file.
12
#
13
# $1 - The key name to obtain.
14
function property {
15
  grep "^${1}" "${PROPERTIES}" | cut -d'=' -f2
16
}
17
18
readonly BUILD_DIR=build
19
readonly PROPERTIES="${SCRIPT_DIR}/../src/main/resources/com/keenwrite/messages.properties"
20
21
readonly CONTAINER_EXE=podman
22
readonly CONTAINER_SHORTNAME=$(property Wizard.typesetter.container.image.name)
23
readonly CONTAINER_VERSION=$(property Wizard.typesetter.container.image.version)
24
readonly CONTAINER_NETWORK=host
25
readonly CONTAINER_FILE="${CONTAINER_SHORTNAME}-${CONTAINER_VERSION}"
26
readonly CONTAINER_ARCHIVE_FILE="${CONTAINER_FILE}.tar"
27
readonly CONTAINER_ARCHIVE_PATH="${BUILD_DIR}/${CONTAINER_ARCHIVE_FILE}"
28
readonly CONTAINER_COMPRESSED_FILE="${CONTAINER_ARCHIVE_FILE}.gz"
29
readonly CONTAINER_COMPRESSED_PATH="${BUILD_DIR}/${CONTAINER_ARCHIVE_FILE}.gz"
30
readonly CONTAINER_IMAGE_FILE="${BUILD_DIR}/${CONTAINER_FILE}"
31
readonly CONTAINER_DIR_SOURCE="/root/source"
32
readonly CONTAINER_DIR_TARGET="/root/target"
33
readonly CONTAINER_DIR_IMAGES="/root/images"
34
readonly CONTAINER_DIR_FONTS="/root/fonts"
35
36
ARG_CONTAINER_NAME="${CONTAINER_SHORTNAME}:${CONTAINER_VERSION}"
37
ARG_CONTAINER_COMMAND="context --version"
38
ARG_MOUNTPOINT_SOURCE=""
39
ARG_MOUNTPOINT_TARGET="."
40
ARG_MOUNTPOINT_IMAGES=""
41
ARG_MOUNTPOINT_FONTS="${HOME}/.fonts"
42
43
DEPENDENCIES=(
44
  "podman,https://podman.io"
45
  "tar,https://www.gnu.org/software/tar"
46
  "bzip2,https://gitlab.com/bzip2/bzip2"
47
)
48
49
ARGUMENTS+=(
50
  "b,build,Build container"
51
  "c,connect,Connect to container"
52
  "d,delete,Remove all containers"
53
  "s,source,Set mount point for input document (before typesetting)"
54
  "t,target,Set mount point for output file (after typesetting)"
55
  "i,images,Set mount point for image files (to typeset)"
56
  "f,fonts,Set mount point for font files (during typesetting)"
57
  "l,load,Load container (${CONTAINER_COMPRESSED_PATH})"
58
  "p,publish,Publish the container"
59
  "r,run,Run a command in the container (\"${ARG_CONTAINER_COMMAND}\")"
60
  "x,export,Save container (${CONTAINER_COMPRESSED_PATH})"
61
)
62
63
# ---------------------------------------------------------------------------
64
# Manages the container.
65
# ---------------------------------------------------------------------------
66
execute() {
67
  $do_delete
68
  $do_build
69
  $do_publish
70
  $do_export
71
  $do_load
72
  $do_execute
73
  $do_connect
74
75
  return 1
76
}
77
78
# ---------------------------------------------------------------------------
79
# Deletes all containers.
80
# ---------------------------------------------------------------------------
81
utile_delete() {
82
  $log "Deleting all containers"
83
84
  ${CONTAINER_EXE} rmi --all --force > /dev/null
85
86
  $log "Containers deleted"
87
}
88
89
# ---------------------------------------------------------------------------
90
# Builds the container file in the current working directory.
91
# ---------------------------------------------------------------------------
92
utile_build() {
93
  $log "Building container version ${CONTAINER_VERSION}"
94
95
  # Show what commands are run while building, but not the commands' output.
96
  ${CONTAINER_EXE} build \
97
    --network="${CONTAINER_NETWORK}" \
98
    --squash \
99
    -t "${ARG_CONTAINER_NAME}" . | \
100
  grep ^STEP
101
}
102
103
# ---------------------------------------------------------------------------
104
# Publishes the container to the repository.
105
# ---------------------------------------------------------------------------
106
utile_publish() {
107
  local -r TOKEN_FILE="token.txt"
108
109
  if [[ -f "${TOKEN_FILE}" ]]; then
110
    local -r repository=$(cat ${TOKEN_FILE})
111
    local -r remote_file="${CONTAINER_SHORTNAME}:${CONTAINER_VERSION}"
112
    local -r remote_path="${repository}/${remote_file}"
113
114
    $log "Publishing ${CONTAINER_IMAGE_FILE} to ${remote_path}"
115
116
    # Path to the repository.
117
    scp -q "${CONTAINER_IMAGE_FILE}" "${remote_path}"
118
  else
119
    error "Create ${TOKEN_FILE} with publish credentials"
120
  fi
121
}
122
123
# ---------------------------------------------------------------------------
124
# Creates the command-line option for a read-only mountpoint.
125
#
126
# $1 - The host directory.
127
# $2 - The guest (container) directory.
128
# $3 - The file system permissions (set to 1 for read-write).
129
# ---------------------------------------------------------------------------
130
get_mountpoint() {
131
  local result=""
132
  local binding="ro"
133
134
  if [ ! -z "${3+x}" ]; then
135
    binding="Z"
136
  fi
137
138
  if [ ! -z "${1}" ]; then
139
    result="-v ${1}:${2}:${binding}"
140
  fi
141
142
  echo "${result}"
143
}
144
145
get_mountpoint_source() {
146
  echo $(get_mountpoint "${ARG_MOUNTPOINT_SOURCE}" "${CONTAINER_DIR_SOURCE}")
147
}
148
149
get_mountpoint_target() {
150
  echo $(get_mountpoint "${ARG_MOUNTPOINT_TARGET}" "${CONTAINER_DIR_TARGET}" 1)
151
}
152
153
get_mountpoint_images() {
154
  echo $(get_mountpoint "${ARG_MOUNTPOINT_IMAGES}" "${CONTAINER_DIR_IMAGES}")
155
}
156
157
get_mountpoint_fonts() {
158
  echo $(get_mountpoint "${ARG_MOUNTPOINT_FONTS}" "${CONTAINER_DIR_FONTS}")
159
}
160
161
# ---------------------------------------------------------------------------
162
# Connects to the container.
163
# ---------------------------------------------------------------------------
164
utile_connect() {
165
  $log "Connecting to container"
166
167
  declare -r mount_source=$(get_mountpoint_source)
168
  declare -r mount_target=$(get_mountpoint_target)
169
  declare -r mount_images=$(get_mountpoint_images)
170
  declare -r mount_fonts=$(get_mountpoint_fonts)
171
172
  $log "mount_source = '${mount_source}'"
173
  $log "mount_target = '${mount_target}'"
174
  $log "mount_images = '${mount_images}'"
175
  $log "mount_fonts = '${mount_fonts}'"
176
177
  ${CONTAINER_EXE} run \
178
    --network="${CONTAINER_NETWORK}" \
179
    --rm \
180
    -it \
181
    ${mount_source} \
182
    ${mount_target} \
183
    ${mount_images} \
184
    ${mount_fonts} \
185
    "${ARG_CONTAINER_NAME}"
186
}
187
188
# ---------------------------------------------------------------------------
189
# Runs a command in the container.
190
#
191
# Examples:
192
#
193
#   ./manage.sh -r "ls /"
194
#   ./manage.sh -r "context --version"
195
# ---------------------------------------------------------------------------
196
utile_execute() {
197
  $log "Running \"${ARG_CONTAINER_COMMAND}\":"
198
199
  ${CONTAINER_EXE} run \
200
    --network=${CONTAINER_NETWORK} \
201
    --rm \
202
    -i \
203
    -t "${ARG_CONTAINER_NAME}" \
204
    /bin/sh --login -c "${ARG_CONTAINER_COMMAND}"
205
}
206
207
# ---------------------------------------------------------------------------
208
# Saves the container to a file.
209
# ---------------------------------------------------------------------------
210
utile_export() {
211
  if [[ -f "${CONTAINER_COMPRESSED_PATH}" ]]; then
212
    warning "${CONTAINER_COMPRESSED_PATH} exists, delete before saving."
213
  else
214
    $log "Saving ${CONTAINER_SHORTNAME} image"
215
216
    mkdir -p "${BUILD_DIR}"
217
218
    ${CONTAINER_EXE} save \
219
      --quiet \
220
      -o "${BUILD_DIR}/${CONTAINER_ARCHIVE_FILE}" \
221
      "${ARG_CONTAINER_NAME}"
222
223
    $log "Compressing to ${CONTAINER_COMPRESSED_PATH}"
224
    gzip "${CONTAINER_ARCHIVE_PATH}"
225
226
    $log "Renaming to ${CONTAINER_IMAGE_FILE}"
227
    mv "${CONTAINER_COMPRESSED_PATH}" "${CONTAINER_IMAGE_FILE}"
228
229
    $log "Saved ${CONTAINER_IMAGE_FILE} image"
230
  fi
231
}
232
233
# ---------------------------------------------------------------------------
234
# Loads the container from a file.
235
# ---------------------------------------------------------------------------
236
utile_load() {
237
  if [[ -f "${CONTAINER_COMPRESSED_PATH}" ]]; then
238
    $log "Loading ${CONTAINER_SHORTNAME} from ${CONTAINER_COMPRESSED_PATH}"
239
240
    ${CONTAINER_EXE} load \
241
      --quiet \
242
      -i "${CONTAINER_COMPRESSED_PATH}"
243
244
    $log "Loaded ${CONTAINER_SHORTNAME} image"
245
  else
246
    warning "Missing ${CONTAINER_COMPRESSED_PATH}; use build followed by save"
247
  fi
248
}
249
250
argument() {
251
  local consume=1
252
253
  case "$1" in
254
    -b|--build)
255
    do_build=utile_build
256
    ;;
257
    -c|--connect)
258
    do_connect=utile_connect
259
    ;;
260
    -d|--delete)
261
    do_delete=utile_delete
262
    ;;
263
    -l|--load)
264
    do_load=utile_load
265
    ;;
266
    -i|--images)
267
    if [ ! -z "${2+x}" ]; then
268
      ARG_MOUNTPOINT_IMAGES="$2"
269
      consume=2
270
    fi
271
    ;;
272
    -t|--target)
273
    if [ ! -z "${2+x}" ]; then
274
      ARG_MOUNTPOINT_TARGET="$2"
275
      consume=2
276
    fi
277
    ;;
278
    -p|--publish)
279
    do_publish=utile_publish
280
    ;;
281
    -r|--run)
282
    do_execute=utile_execute
283
284
    if [ ! -z "${2+x}" ]; then
285
      ARG_CONTAINER_COMMAND="$2"
286
      consume=2
287
    fi
288
    ;;
289
    -s|--source)
290
    if [ ! -z "${2+x}" ]; then
291
      ARG_MOUNTPOINT_SOURCE="$2"
292
      consume=2
293
    fi
294
    ;;
295
    -x|--export)
296
    do_export=utile_export
297
    ;;
298
  esac
299
300
  return ${consume}
301
}
302
303
do_build=:
304
do_connect=:
305
do_delete=:
306
do_execute=:
307
do_load=:
308
do_publish=:
309
do_export=:
310
311
main "$@"
312
1313
A docs/README.md
1
# Documentation
2
3
The following documents have additional details about using the editor:
4
5
* [cmd.md](cmd.md) -- Command-line argument usage
6
* [div.md](div.md) -- Syntax for annotated text (fenced divs)
7
* [i18n.md](i18n.md) -- Internationalization features
8
* [r.md](r.md) -- R functions within R Markdown documents
9
* [samples](samples) -- Example documents
10
* [skins.md](skins.md) -- User interface customization
11
* [svg.md](svg.md) -- Resolve issues with some SVG files
12
* [metadata.md](metadata.md) -- Document metadata
13
* [references.md](references.md) -- Captions and cross-references
14
* [typesetting.md](typesetting.md) -- Document typesetting
15
* [variables.md](variables.md) -- Variable definitions and interpolation
16
17
# Contributions
18
19
* [credits.md](credits.md) -- Thanks to authors of contributing projects
20
* [licenses](licenses) -- Third-party licenses
21
122
A docs/cmd.md
1
# Command-line arguments
2
3
The application may be run from the command-line to convert Markdown and
4
R Markdown files to a variety of output formats. Without specifying any
5
command-line arguments, the application will launch a graphical user interface.
6
7
## Common arguments
8
9
The most common command-line arguments to use include:
10
11
* `-h` -- displays all command-line arguments, then exits.
12
* `-i` -- sets the input file name, must be a full path.
13
* `-o` -- sets the output file name, can be a relative path.
14
* `-s` -- sets a variable name and value at build time (dynamic data).
15
16
## Example usage
17
18
On Linux, simple usages include:
19
20
    keenwrite.bin -i $HOME/document/01.md -o document.xhtml
21
22
    keenwrite.bin -i $HOME/document/01.md -o document.md \
23
      -v $HOME/document/variables.yaml
24
25
That command will convert `01.md` into the respective file formats. In
26
the first case, it will become an HTML page. In the second case, it will
27
become a Markdown document with all variables interpolated and replaced.
28
29
A more complex example follows:
30
31
    keenwrite.bin -i $HOME/document/01.Rmd -o document.pdf \
32
      --image-dir=$HOME/document/images -v $HOME/document/variables.yaml \
33
      --metadata="title={{book.title}}" --metadata="author={{book.author}}" \
34
      --r-dir=$HOME/document/r --r-script=$HOME/document/r/bootstrap.R \
35
      --theme-dir=$HOME/document/themes/boschet
36
37
That command will convert `01.Rmd` to `document.pdf` and replace the metadata
38
using values from the variable definitions file.
39
40
Directory names containing spaces must be quoted. For example, on Windows:
41
42
    keenwrite.bin -i "C:\Users\My Documents\01.Rmd" -o document.pdf
43
144
A docs/credits.md
1
# Credits
2
3
Using libraries from:
4
5
* Tomas Mikula: [RichTextFX](https://github.com/TomasMikula/RichTextFX), [WellBehavedFX](https://github.com/TomasMikula/WellBehavedFX), [Flowless](https://github.com/TomasMikula/Flowless), and [UndoFX](https://github.com/TomasMikula/UndoFX)
6
* Jens Deters: [FontAwesomeFX](https://bitbucket.org/Jerady/fontawesomefx)
7
* Dieter Holz: [PreferencesFX](https://github.com/dlsc-software-consulting-gmbh/PreferencesFX)
8
* David Croft: [File Preferences](http://www.davidc.net/programming/java/java-preferences-using-file-backing-store)
9
* Alex Bertram: [Renjin](https://www.renjin.org/)
10
* Vladimir Schneider: [flexmark](https://github.com/vsch/flexmark-java)
11
* Alberto Fernández, Shy Shalom, Kohei Taketa: [juniversalchardet](https://github.com/takscape/juniversalchardet)
12
* Morten Nobel-Jørgensen: [Java Image Scaling](https://github.com/mortennobel/java-image-scaling)
13
14
Inspired by:
15
16
* Karl Tauber: [Markdown Writer FX](https://github.com/JFormDesigner/markdown-writer-fx)
117
A docs/development/proguard/README.md
1
From https://github.com/greenrobot/EventBus#r8-proguard
2
3
	-keepattributes *Annotation*
4
	-keepclassmembers class * {
5
			@org.greenrobot.eventbus.Subscribe <methods>;
6
	}
7
	-keep enum org.greenrobot.eventbus.ThreadMode { *; }
8
	 
19
A docs/diagram.md
1
# Introduction
2
3
From a high level, the application architecture for converting Markdown documents is captured in the following figure:
4
5
``` diagram-graphviz
6
digraph {
7
  node [fontname = "Noto Sans" fontsize=6 height=.25 penwidth=.5];
8
  edge [fontname = "Noto Sans" fontsize=6  penwidth=.5 arrowsize=.5];
9
  node [shape=box color="{{keenwrite.palette.primary.light}}" fontcolor="{{keenwrite.palette.primary.dark}}"]
10
  edge [color="{{keenwrite.palette.grayscale.light}}" fontcolor="{{keenwrite.palette.grayscale.dark}}"]
11
12
  {{keenwrite.classes.processors.variable.definition}} ->   {{keenwrite.classes.processors.markdown}} [xlabel="{{keenwrite.graph.label.chain.next}}  "]
13
  {{keenwrite.classes.processors.markdown}} -> {{keenwrite.classes.processors.preview}} [xlabel="{{keenwrite.graph.label.chain.next}}  "]
14
  {{keenwrite.classes.processors.markdown}} -> Extensions [label="  contains"]
15
16
Extensions -> FencedBlockExtension
17
Extensions -> CaretExtension
18
Extensions -> ImageLinkExtension
19
Extensions -> TeXExtension
20
}
21
```
22
23
An extension is an addition to the Markdown parser, flexmark-java, that is used when converting the document's abstract syntax tree into an HTML document. The {{keenwrite.classes.processors.markdown}} contains both prepackaged and custom extensions.
124
A docs/diagram.yaml
1
---
2
keenwrite:
3
  classes:
4
    processors:
5
      markdown: MarkdownProcessor
6
      variable:
7
        definition: DefinitionProcessor
8
      preview: PreviewProcessor
9
  palette:
10
    primary:
11
      light: '#51a9cf'
12
      dark: '#126d95'
13
    secondary:
14
      light: '#ec706a'
15
      dark: '#7e252f'
16
    accent:
17
      light: '#76A786'
18
      dark: '#385742'
19
    grayscale:
20
      light: '#bac2c5'
21
      dark: '#394343'
22
  graph:
23
    label:
24
      chain:
25
        next: successor
126
A docs/div.md
1
# Fenced divs
2
3
This section describes the syntax to generate HTML `div` elements. The
4
syntax is known as a _fenced div_.
5
6
# Basic syntax
7
8
A fenced div has the following basic syntax:
9
10
``` markdown
11
::: name
12
Content
13
:::
14
```
115
16
To start a fenced div, begin a lss
A docs/i18n.md
1
# Internationalization
2
3
The application supports internationalization (I18N). There are multiple
4
components to editing and previewing internationalized text documents.
5
These include:
6
7
* Fonts
8
* Language
9
10
Both fonts and language must be set for non-Latin-based text.
11
12
# Fonts
13
14
The text editors and preview panel have independent font settings. For
15
all Chinese, Japanese, and Korean (CJK) fonts, you may have to type in
16
the font family name directly.
17
18
For example, CJK font families for the editor have the following names:
19
20
* **Noto Sans CJK KR** --- Korean font
21
* **Noto Sans CJK JP** --- Japanese font
22
* **Noto Sans CJK HN** --- Chinese font
23
* **Noto Sans CJK SC** --- Simplified Chinese font
24
25
While CJK font families for the preview have the following names:
26
27
* **Noto Serif CJK KR** --- Korean font
28
* **Noto Serif CJK JP** --- Japanese font
29
* **Noto Serif CJK HN** --- Chinese font
30
* **Noto Serif CJK SC** --- Simplified Chinese font
31
32
## Editor
33
34
Complete the following steps to change the editor font:
35
36
1. Click **Edit → Preferences**.
37
1. Click **Fonts**.
38
1. Click **Change** under **Editor Font**.
39
1. Find the font name by typing or scrolling.
40
1. Click the desired font family.
41
1. Click **OK**.
42
1. Click **Apply**.
43
44
The text editor font is changed.
45
46
Note the following:
47
48
* The font must be installed in the system for this to work.
49
* You may have to edit the font name if it cannot be selected from the list.
50
* Setting the editor font also sets the statistics panel font.
51
52
## Preview
53
54
The preview panel uses regular and monospace fonts.
55
56
### Regular
57
58
Complete the following steps to change the regular preview font:
59
60
1. Click **Edit → Preferences**.
61
1. Click **Fonts**.
62
1. Click **Change** under **Preview Font** for the **Preview pane font name**.
63
1. Find the font name by typing or scrolling.
64
1. Click the desired font family.
65
1. Click **OK**.
66
1. Click **Apply**.
67
68
The regular preview font is changed.
69
70
### Monospace
71
72
Complete the following steps to change the monospace preview font:
73
74
1. Click **Edit → Preferences**.
75
1. Click **Fonts**.
76
1. Click **Change** under **Preview Font** for the **Monospace font name**.
77
1. Find the font name by typing or scrolling.
78
1. Click the desired font family.
79
1. Click **OK**.
80
1. Click **Apply**.
81
82
The monospace font is changed.
83
84
# Language
85
86
Language settings control the locale that the application uses. When using
87
a CJK font, for example, the application must also be instructed to use
88
a particular locale. Change the locale as follows:
89
90
1. Click **Edit → Preferences**.
91
1. Click **Language**.
92
1. Select a value for **Locale**.
93
1. Click **Apply**.
94
95
The language is set.
96
197
A docs/images/app-ide.png
Binary file
A docs/images/app-title.png
Binary file
A docs/images/app-title.zh-CN.png
Binary file
A docs/images/architecture/architecture.png
Binary file
A docs/images/architecture/architecture.svg
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<svg
3
   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
   xmlns:cc="http://creativecommons.org/ns#"
5
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
   xmlns:svg="http://www.w3.org/2000/svg"
7
   xmlns="http://www.w3.org/2000/svg"
8
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
   inkscape:export-ydpi="150.0097"
11
   inkscape:export-xdpi="150.0097"
12
   sodipodi:docname="architecture.svg"
13
   viewBox="0 0 764.4414 811.46748"
14
   height="811.46747"
15
   width="764.44141"
16
   id="svg4610"
17
   version="1.2"
18
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)">
19
  <metadata
20
     id="metadata4616">
21
    <rdf:RDF>
22
      <cc:Work
23
         rdf:about="">
24
        <dc:format>image/svg+xml</dc:format>
25
        <dc:type
26
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
27
        <dc:title />
28
      </cc:Work>
29
    </rdf:RDF>
30
  </metadata>
31
  <defs
32
     id="defs4614">
33
    <marker
34
       inkscape:stockid="Arrow1Mend"
35
       orient="auto"
36
       refY="0"
37
       refX="0"
38
       id="marker10933"
39
       style="overflow:visible"
40
       inkscape:isstock="true">
41
      <path
42
         id="path10931"
43
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
44
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
45
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
46
         inkscape:connector-curvature="0" />
47
    </marker>
48
    <marker
49
       inkscape:stockid="Arrow1Mend"
50
       orient="auto"
51
       refY="0"
52
       refX="0"
53
       id="marker9893"
54
       style="overflow:visible"
55
       inkscape:isstock="true">
56
      <path
57
         id="path9891"
58
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
59
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
60
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
61
         inkscape:connector-curvature="0" />
62
    </marker>
63
    <marker
64
       inkscape:collect="always"
65
       inkscape:isstock="true"
66
       style="overflow:visible"
67
       id="marker9767"
68
       refX="0"
69
       refY="0"
70
       orient="auto"
71
       inkscape:stockid="Arrow1Mend">
72
      <path
73
         inkscape:connector-curvature="0"
74
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
75
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
76
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
77
         id="path9765" />
78
    </marker>
79
    <marker
80
       inkscape:collect="always"
81
       inkscape:stockid="Arrow1Mend"
82
       orient="auto"
83
       refY="0"
84
       refX="0"
85
       id="marker9761"
86
       style="overflow:visible"
87
       inkscape:isstock="true">
88
      <path
89
         id="path9759"
90
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
91
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
92
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
93
         inkscape:connector-curvature="0" />
94
    </marker>
95
    <marker
96
       inkscape:isstock="true"
97
       style="overflow:visible"
98
       id="marker9750"
99
       refX="0"
100
       refY="0"
101
       orient="auto"
102
       inkscape:stockid="Arrow1Mend">
103
      <path
104
         inkscape:connector-curvature="0"
105
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
106
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
107
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
108
         id="path9748" />
109
    </marker>
110
    <marker
111
       inkscape:isstock="true"
112
       style="overflow:visible"
113
       id="marker9715"
114
       refX="0"
115
       refY="0"
116
       orient="auto"
117
       inkscape:stockid="Arrow1Mend">
118
      <path
119
         inkscape:connector-curvature="0"
120
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
121
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
122
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
123
         id="path9713" />
124
    </marker>
125
    <marker
126
       inkscape:collect="always"
127
       inkscape:stockid="Arrow1Mend"
128
       orient="auto"
129
       refY="0"
130
       refX="0"
131
       id="marker9685"
132
       style="overflow:visible"
133
       inkscape:isstock="true">
134
      <path
135
         id="path9683"
136
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
137
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
138
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
139
         inkscape:connector-curvature="0" />
140
    </marker>
141
    <marker
142
       inkscape:collect="always"
143
       inkscape:stockid="Arrow1Mend"
144
       orient="auto"
145
       refY="0"
146
       refX="0"
147
       id="marker9679"
148
       style="overflow:visible"
149
       inkscape:isstock="true">
150
      <path
151
         id="path9677"
152
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
153
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
154
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
155
         inkscape:connector-curvature="0" />
156
    </marker>
157
    <marker
158
       inkscape:collect="always"
159
       inkscape:isstock="true"
160
       style="overflow:visible"
161
       id="marker9640"
162
       refX="0"
163
       refY="0"
164
       orient="auto"
165
       inkscape:stockid="Arrow1Mend">
166
      <path
167
         inkscape:connector-curvature="0"
168
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
169
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
170
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
171
         id="path9638" />
172
    </marker>
173
    <marker
174
       inkscape:collect="always"
175
       inkscape:isstock="true"
176
       style="overflow:visible"
177
       id="marker9513"
178
       refX="0"
179
       refY="0"
180
       orient="auto"
181
       inkscape:stockid="Arrow1Mend">
182
      <path
183
         inkscape:connector-curvature="0"
184
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
185
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
186
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
187
         id="path9511" />
188
    </marker>
189
    <marker
190
       inkscape:stockid="Arrow1Mend"
191
       orient="auto"
192
       refY="0"
193
       refX="0"
194
       id="marker9509"
195
       style="overflow:visible"
196
       inkscape:isstock="true">
197
      <path
198
         id="path9507"
199
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
200
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
201
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
202
         inkscape:connector-curvature="0" />
203
    </marker>
204
    <marker
205
       inkscape:isstock="true"
206
       style="overflow:visible"
207
       id="marker9505"
208
       refX="0"
209
       refY="0"
210
       orient="auto"
211
       inkscape:stockid="Arrow1Mend">
212
      <path
213
         inkscape:connector-curvature="0"
214
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
215
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
216
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
217
         id="path9503" />
218
    </marker>
219
    <marker
220
       inkscape:collect="always"
221
       inkscape:stockid="Arrow1Mend"
222
       orient="auto"
223
       refY="0"
224
       refX="0"
225
       id="marker9479"
226
       style="overflow:visible"
227
       inkscape:isstock="true">
228
      <path
229
         id="path9477"
230
         d="M 0,0 5,-5 -12.5,0 5,5 Z"
231
         style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke:#05556e;stroke-width:1.00000003pt;stroke-opacity:1"
232
         transform="matrix(-0.4,0,0,-0.4,-4,0)"
233
         inkscape:connector-curvature="0" />
234
    </marker>
235
    <clipPath
236
       id="ID000001">
237
      <rect
238
         id="rect6"
239
         height="961.125"
240
         width="1381.6169"
241
         y="-43.688"
242
         x="-62.683998" />
243
    </clipPath>
244
    <filter
245
       id="filter2842"
246
       inkscape:label="Drop Shadow"
247
       style="color-interpolation-filters:sRGB;">
248
      <feFlood
249
         id="feFlood2832"
250
         result="flood"
251
         flood-color="rgb(0,0,0)"
252
         flood-opacity="0.498039" />
253
      <feComposite
254
         id="feComposite2834"
255
         result="composite1"
256
         operator="in"
257
         in2="SourceGraphic"
258
         in="flood" />
259
      <feGaussianBlur
260
         id="feGaussianBlur2836"
261
         result="blur"
262
         stdDeviation="2"
263
         in="composite1" />
264
      <feOffset
265
         id="feOffset2838"
266
         result="offset"
267
         dy="3"
268
         dx="3" />
269
      <feComposite
270
         id="feComposite2840"
271
         result="composite2"
272
         operator="over"
273
         in2="offset"
274
         in="SourceGraphic" />
275
    </filter>
276
    <filter
277
       id="filter2854"
278
       inkscape:label="Drop Shadow"
279
       style="color-interpolation-filters:sRGB;">
280
      <feFlood
281
         id="feFlood2844"
282
         result="flood"
283
         flood-color="rgb(0,0,0)"
284
         flood-opacity="0.498039" />
285
      <feComposite
286
         id="feComposite2846"
287
         result="composite1"
288
         operator="in"
289
         in2="SourceGraphic"
290
         in="flood" />
291
      <feGaussianBlur
292
         id="feGaussianBlur2848"
293
         result="blur"
294
         stdDeviation="2"
295
         in="composite1" />
296
      <feOffset
297
         id="feOffset2850"
298
         result="offset"
299
         dy="3"
300
         dx="3" />
301
      <feComposite
302
         id="feComposite2852"
303
         result="composite2"
304
         operator="over"
305
         in2="offset"
306
         in="SourceGraphic" />
307
    </filter>
308
    <filter
309
       id="filter2866"
310
       inkscape:label="Drop Shadow"
311
       style="color-interpolation-filters:sRGB;">
312
      <feFlood
313
         id="feFlood2856"
314
         result="flood"
315
         flood-color="rgb(0,0,0)"
316
         flood-opacity="0.498039" />
317
      <feComposite
318
         id="feComposite2858"
319
         result="composite1"
320
         operator="in"
321
         in2="SourceGraphic"
322
         in="flood" />
323
      <feGaussianBlur
324
         id="feGaussianBlur2860"
325
         result="blur"
326
         stdDeviation="2"
327
         in="composite1" />
328
      <feOffset
329
         id="feOffset2862"
330
         result="offset"
331
         dy="3"
332
         dx="3" />
333
      <feComposite
334
         id="feComposite2864"
335
         result="composite2"
336
         operator="over"
337
         in2="offset"
338
         in="SourceGraphic" />
339
    </filter>
340
    <filter
341
       id="filter2878"
342
       inkscape:label="Drop Shadow"
343
       style="color-interpolation-filters:sRGB;">
344
      <feFlood
345
         id="feFlood2868"
346
         result="flood"
347
         flood-color="rgb(0,0,0)"
348
         flood-opacity="0.498039" />
349
      <feComposite
350
         id="feComposite2870"
351
         result="composite1"
352
         operator="in"
353
         in2="SourceGraphic"
354
         in="flood" />
355
      <feGaussianBlur
356
         id="feGaussianBlur2872"
357
         result="blur"
358
         stdDeviation="2"
359
         in="composite1" />
360
      <feOffset
361
         id="feOffset2874"
362
         result="offset"
363
         dy="3"
364
         dx="3" />
365
      <feComposite
366
         id="feComposite2876"
367
         result="composite2"
368
         operator="over"
369
         in2="offset"
370
         in="SourceGraphic" />
371
    </filter>
372
    <filter
373
       id="filter2890"
374
       inkscape:label="Drop Shadow"
375
       style="color-interpolation-filters:sRGB;">
376
      <feFlood
377
         id="feFlood2880"
378
         result="flood"
379
         flood-color="rgb(0,0,0)"
380
         flood-opacity="0.498039" />
381
      <feComposite
382
         id="feComposite2882"
383
         result="composite1"
384
         operator="in"
385
         in2="SourceGraphic"
386
         in="flood" />
387
      <feGaussianBlur
388
         id="feGaussianBlur2884"
389
         result="blur"
390
         stdDeviation="2"
391
         in="composite1" />
392
      <feOffset
393
         id="feOffset2886"
394
         result="offset"
395
         dy="3"
396
         dx="3" />
397
      <feComposite
398
         id="feComposite2888"
399
         result="composite2"
400
         operator="over"
401
         in2="offset"
402
         in="SourceGraphic" />
403
    </filter>
404
    <filter
405
       id="filter2902"
406
       inkscape:label="Drop Shadow"
407
       style="color-interpolation-filters:sRGB;">
408
      <feFlood
409
         id="feFlood2892"
410
         result="flood"
411
         flood-color="rgb(0,0,0)"
412
         flood-opacity="0.498039" />
413
      <feComposite
414
         id="feComposite2894"
415
         result="composite1"
416
         operator="in"
417
         in2="SourceGraphic"
418
         in="flood" />
419
      <feGaussianBlur
420
         id="feGaussianBlur2896"
421
         result="blur"
422
         stdDeviation="2"
423
         in="composite1" />
424
      <feOffset
425
         id="feOffset2898"
426
         result="offset"
427
         dy="3"
428
         dx="3" />
429
      <feComposite
430
         id="feComposite2900"
431
         result="composite2"
432
         operator="over"
433
         in2="offset"
434
         in="SourceGraphic" />
435
    </filter>
436
    <filter
437
       id="filter2914"
438
       inkscape:label="Drop Shadow"
439
       style="color-interpolation-filters:sRGB;">
440
      <feFlood
441
         id="feFlood2904"
442
         result="flood"
443
         flood-color="rgb(0,0,0)"
444
         flood-opacity="0.498039" />
445
      <feComposite
446
         id="feComposite2906"
447
         result="composite1"
448
         operator="in"
449
         in2="SourceGraphic"
450
         in="flood" />
451
      <feGaussianBlur
452
         id="feGaussianBlur2908"
453
         result="blur"
454
         stdDeviation="2"
455
         in="composite1" />
456
      <feOffset
457
         id="feOffset2910"
458
         result="offset"
459
         dy="3"
460
         dx="3" />
461
      <feComposite
462
         id="feComposite2912"
463
         result="composite2"
464
         operator="over"
465
         in2="offset"
466
         in="SourceGraphic" />
467
    </filter>
468
    <filter
469
       id="filter2926"
470
       inkscape:label="Drop Shadow"
471
       style="color-interpolation-filters:sRGB;">
472
      <feFlood
473
         id="feFlood2916"
474
         result="flood"
475
         flood-color="rgb(0,0,0)"
476
         flood-opacity="0.498039" />
477
      <feComposite
478
         id="feComposite2918"
479
         result="composite1"
480
         operator="in"
481
         in2="SourceGraphic"
482
         in="flood" />
483
      <feGaussianBlur
484
         id="feGaussianBlur2920"
485
         result="blur"
486
         stdDeviation="2"
487
         in="composite1" />
488
      <feOffset
489
         id="feOffset2922"
490
         result="offset"
491
         dy="3"
492
         dx="3" />
493
      <feComposite
494
         id="feComposite2924"
495
         result="composite2"
496
         operator="over"
497
         in2="offset"
498
         in="SourceGraphic" />
499
    </filter>
500
    <filter
501
       id="filter2938"
502
       inkscape:label="Drop Shadow"
503
       style="color-interpolation-filters:sRGB;">
504
      <feFlood
505
         id="feFlood2928"
506
         result="flood"
507
         flood-color="rgb(0,0,0)"
508
         flood-opacity="0.498039" />
509
      <feComposite
510
         id="feComposite2930"
511
         result="composite1"
512
         operator="in"
513
         in2="SourceGraphic"
514
         in="flood" />
515
      <feGaussianBlur
516
         id="feGaussianBlur2932"
517
         result="blur"
518
         stdDeviation="2"
519
         in="composite1" />
520
      <feOffset
521
         id="feOffset2934"
522
         result="offset"
523
         dy="3"
524
         dx="3" />
525
      <feComposite
526
         id="feComposite2936"
527
         result="composite2"
528
         operator="over"
529
         in2="offset"
530
         in="SourceGraphic" />
531
    </filter>
532
    <filter
533
       id="filter2950"
534
       inkscape:label="Drop Shadow"
535
       style="color-interpolation-filters:sRGB;">
536
      <feFlood
537
         id="feFlood2940"
538
         result="flood"
539
         flood-color="rgb(0,0,0)"
540
         flood-opacity="0.498039" />
541
      <feComposite
542
         id="feComposite2942"
543
         result="composite1"
544
         operator="in"
545
         in2="SourceGraphic"
546
         in="flood" />
547
      <feGaussianBlur
548
         id="feGaussianBlur2944"
549
         result="blur"
550
         stdDeviation="2"
551
         in="composite1" />
552
      <feOffset
553
         id="feOffset2946"
554
         result="offset"
555
         dy="3"
556
         dx="3" />
557
      <feComposite
558
         id="feComposite2948"
559
         result="composite2"
560
         operator="over"
561
         in2="offset"
562
         in="SourceGraphic" />
563
    </filter>
564
    <filter
565
       id="filter2962"
566
       inkscape:label="Drop Shadow"
567
       style="color-interpolation-filters:sRGB;">
568
      <feFlood
569
         id="feFlood2952"
570
         result="flood"
571
         flood-color="rgb(0,0,0)"
572
         flood-opacity="0.498039" />
573
      <feComposite
574
         id="feComposite2954"
575
         result="composite1"
576
         operator="in"
577
         in2="SourceGraphic"
578
         in="flood" />
579
      <feGaussianBlur
580
         id="feGaussianBlur2956"
581
         result="blur"
582
         stdDeviation="2"
583
         in="composite1" />
584
      <feOffset
585
         id="feOffset2958"
586
         result="offset"
587
         dy="3"
588
         dx="3" />
589
      <feComposite
590
         id="feComposite2960"
591
         result="composite2"
592
         operator="over"
593
         in2="offset"
594
         in="SourceGraphic" />
595
    </filter>
596
    <filter
597
       id="filter2974"
598
       inkscape:label="Drop Shadow"
599
       style="color-interpolation-filters:sRGB;">
600
      <feFlood
601
         id="feFlood2964"
602
         result="flood"
603
         flood-color="rgb(0,0,0)"
604
         flood-opacity="0.498039" />
605
      <feComposite
606
         id="feComposite2966"
607
         result="composite1"
608
         operator="in"
609
         in2="SourceGraphic"
610
         in="flood" />
611
      <feGaussianBlur
612
         id="feGaussianBlur2968"
613
         result="blur"
614
         stdDeviation="2"
615
         in="composite1" />
616
      <feOffset
617
         id="feOffset2970"
618
         result="offset"
619
         dy="3"
620
         dx="3" />
621
      <feComposite
622
         id="feComposite2972"
623
         result="composite2"
624
         operator="over"
625
         in2="offset"
626
         in="SourceGraphic" />
627
    </filter>
628
    <filter
629
       id="filter2986"
630
       inkscape:label="Drop Shadow"
631
       style="color-interpolation-filters:sRGB;">
632
      <feFlood
633
         id="feFlood2976"
634
         result="flood"
635
         flood-color="rgb(0,0,0)"
636
         flood-opacity="0.498039" />
637
      <feComposite
638
         id="feComposite2978"
639
         result="composite1"
640
         operator="in"
641
         in2="SourceGraphic"
642
         in="flood" />
643
      <feGaussianBlur
644
         id="feGaussianBlur2980"
645
         result="blur"
646
         stdDeviation="2"
647
         in="composite1" />
648
      <feOffset
649
         id="feOffset2982"
650
         result="offset"
651
         dy="3"
652
         dx="3" />
653
      <feComposite
654
         id="feComposite2984"
655
         result="composite2"
656
         operator="over"
657
         in2="offset"
658
         in="SourceGraphic" />
659
    </filter>
660
  </defs>
661
  <sodipodi:namedview
662
     inkscape:snap-text-baseline="false"
663
     inkscape:document-rotation="0"
664
     fit-margin-bottom="20"
665
     fit-margin-right="20"
666
     fit-margin-left="20"
667
     fit-margin-top="20"
668
     inkscape:current-layer="svg4610"
669
     inkscape:cy="370.55742"
670
     inkscape:cx="398.61418"
671
     inkscape:zoom="1.3753763"
672
     showgrid="false"
673
     id="namedview4612"
674
     inkscape:window-height="1280"
675
     inkscape:window-width="2055"
676
     inkscape:pageshadow="2"
677
     inkscape:pageopacity="1"
678
     guidetolerance="10"
679
     gridtolerance="10"
680
     objecttolerance="10"
681
     borderopacity="1"
682
     bordercolor="#666666"
683
     pagecolor="#ffffff"
684
     inkscape:window-x="215"
685
     inkscape:window-y="26"
686
     inkscape:window-maximized="0" />
687
  <path
688
     sodipodi:nodetypes="ccssssc"
689
     inkscape:connector-curvature="0"
690
     style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#df4d65;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
691
     d="M 53.35547,445.11522 V 790.96744 H 741.0332 c 1.6112,0 2.90821,-1.29701 2.90821,-2.9082 V 448.02342 c 0,-1.6112 -1.297,-2.9082 -2.90821,-2.9082 z"
692
     id="path9961" />
693
  <path
694
     sodipodi:nodetypes="sssccssss"
695
     id="path9940"
696
     d="m 20.5,787.82486 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.35253,0.91797 2.22265,0.91797 H 53.35547 V 445.11522 H 23.64062 c -0.87012,0 -1.65487,0.35019 -2.22265,0.91797 -0.56778,0.56778 -0.91797,1.35254 -0.91797,2.22266 z"
697
     style="fill:#df4d65;fill-opacity:1;fill-rule:nonzero;stroke:#df4d65;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
698
     inkscape:connector-curvature="0" />
699
  <path
700
     sodipodi:nodetypes="sssccssss"
701
     id="path11125"
702
     d="m 20.5,423.31014 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.354494,0.9764 2.22265,0.91797 H 53.35547 V 210.6005 H 23.64062 c -0.87012,0 -1.65487,0.3502 -2.22265,0.918 C 20.85019,212.08629 20.5,212.871 20.5,213.74109 Z"
703
     style="fill:#3e3e3e;fill-opacity:1;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
704
     inkscape:connector-curvature="0" />
705
  <path
706
     sodipodi:nodetypes="ccssssc"
707
     inkscape:connector-curvature="0"
708
     style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
709
     d="m 53.35547,210.6005 v 215.85222 h 687.67774 c 1.6112,0 2.9082,-1.29701 2.9082,-2.9082 V 213.5087 c 0,-1.6112 -1.29701,-2.90352 -2.9082,-2.9082 z"
710
     id="path11123" />
711
  <path
712
     id="path6150"
713
     d="m 557.756,222.53493 c -0.87012,0 -1.65683,0.35019 -2.22461,0.91797 -0.56778,0.56778 -0.91797,1.35253 -0.91797,2.22265 v 29.71485 h 165.6211 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
714
     style="fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2926)"
715
     inkscape:connector-curvature="0"
716
     sodipodi:nodetypes="sssccssss" />
717
  <path
718
     sodipodi:nodetypes="ccssssc"
719
     id="path6134"
720
     d="m 720.75716,255.39041 h -165.6211 v 152.63392 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z"
721
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2926)"
722
     inkscape:connector-curvature="0" />
723
  <path
724
     id="path6082"
725
     d="m 317.13559,222.53494 c -0.87011,0 -1.65683,0.35019 -2.2246,0.91797 -0.56779,0.56778 -0.91798,1.35253 -0.91798,2.22265 v 29.71485 h 165.62111 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91798,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
726
     style="fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2938)"
727
     inkscape:connector-curvature="0"
728
     sodipodi:nodetypes="sssccssss" />
729
  <path
730
     sodipodi:nodetypes="ccssssc"
731
     id="path6080"
732
     d="M 479.61412,255.39041 H 313.99301 v 152.63392 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.90821,-1.29701 2.90821,-2.90821 z"
733
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2938)"
734
     inkscape:connector-curvature="0" />
735
  <path
736
     id="path10980"
737
     d="M 53.35547,20.500012 V 188.35224 h 687.67774 c 1.6112,0 2.9082,-1.29701 2.9082,-2.9082 V 23.408212 c 0,-1.6112 -1.29701,-2.912886 -2.9082,-2.9082 z"
738
     style="fill:#333333;fill-opacity:0.0666667;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
739
     inkscape:connector-curvature="0"
740
     sodipodi:nodetypes="ccssssc" />
741
  <path
742
     inkscape:connector-curvature="0"
743
     style="fill:#3e3e3e;fill-opacity:1;fill-rule:nonzero;stroke:#3e3e3e;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
744
     d="m 20.5,185.20966 c 0,0.87013 0.35019,1.65683 0.91797,2.22461 0.56778,0.56778 1.35253,0.91797 2.22265,0.91797 H 53.35547 V 20.500012 H 23.64062 c -0.87012,0 -1.65487,0.350201 -2.22265,0.918 -0.56778,0.5678 -0.91797,1.3525 -0.91797,2.2226 z"
745
     id="path10982"
746
     sodipodi:nodetypes="sssccssss" />
747
  <path
748
     sodipodi:nodetypes="sssccssss"
749
     inkscape:connector-curvature="0"
750
     style="fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2962)"
751
     d="m 557.75599,36.704447 c -0.87012,0 -1.65683,0.35019 -2.22461,0.91797 -0.56778,0.56778 -0.91797,1.35253 -0.91797,2.22265 v 29.71485 h 165.6211 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
752
     id="path4857" />
753
  <path
754
     inkscape:connector-curvature="0"
755
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2962)"
756
     d="M 720.23451,69.559917 H 554.61341 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 h 159.80469 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z"
757
     id="path4853" />
758
  <path
759
     sodipodi:nodetypes="sssccssss"
760
     inkscape:connector-curvature="0"
761
     style="fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2974)"
762
     d="m 317.13558,36.704447 c -0.87011,0 -1.65683,0.35019 -2.2246,0.91797 -0.56779,0.56778 -0.91798,1.35253 -0.91798,2.22265 v 29.71485 h 165.62111 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91798,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
763
     id="path5726" />
764
  <path
765
     inkscape:connector-curvature="0"
766
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2974)"
767
     d="M 479.61411,69.559917 H 313.993 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 H 476.7059 c 1.6112,0 2.90821,-1.29701 2.90821,-2.90821 z"
768
     id="path5724" />
769
  <path
770
     id="path4721"
771
     d="m 235.85308,44.704447 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z"
772
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
773
     inkscape:connector-curvature="0" />
774
  <path
775
     sodipodi:nodetypes="sssccssss"
776
     id="path4719"
777
     d="m 76.515197,36.704447 c -0.870125,0 -1.656831,0.35019 -2.22461,0.91797 -0.567778,0.56778 -0.917968,1.35253 -0.917968,2.22265 v 29.71485 H 238.99371 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
778
     style="fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2986)"
779
     inkscape:connector-curvature="0" />
780
  <path
781
     id="path4723"
782
     d="M 238.99372,69.559917 H 73.372613 V 169.2396 c 0,1.6112 1.29701,2.90821 2.90821,2.90821 H 236.08552 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z"
783
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2986)"
784
     inkscape:connector-curvature="0" />
785
  <path
786
     id="rect4622"
787
     d="m 76.280822,44.704447 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z"
788
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
789
     inkscape:connector-curvature="0" />
790
  <path
791
     sodipodi:nodetypes="cc"
792
     inkscape:connector-curvature="0"
793
     id="path9889"
794
     d="m 397.61301,500.62068 -0.50618,32.59418"
795
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:1.9694221;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9893)" />
796
  <path
797
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9715)"
798
     d="m 554.61351,648.83688 -69.6817,47.69253"
799
     id="path9711"
800
     inkscape:connector-curvature="0"
801
     sodipodi:nodetypes="cc" />
802
  <path
803
     sodipodi:nodetypes="cc"
804
     inkscape:connector-curvature="0"
805
     id="path9675"
806
     d="M 554.61351,567.95047 484.93181,615.643"
807
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9679)" />
808
  <rect
809
     ry="3.9839513"
810
     rx="3.9205718"
811
     y="537.09552"
812
     x="554.61353"
813
     height="32.855"
814
     width="165.621"
815
     id="rect9618"
816
     style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2842)" />
817
  <rect
818
     ry="3.9839513"
819
     rx="3.9205718"
820
     y="537.09552"
821
     x="73.372665"
822
     height="32.855"
823
     width="165.621"
824
     id="rect9614"
825
     style="opacity:1;fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2914)" />
826
  <path
827
     inkscape:connector-curvature="0"
828
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
829
     d="m 235.85308,545.09525 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z"
830
     id="path9323" />
831
  <path
832
     inkscape:connector-curvature="0"
833
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
834
     d="m 76.280823,545.09525 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z"
835
     id="path9327" />
836
  <rect
837
     style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2902)"
838
     id="rect9616"
839
     width="165.621"
840
     height="32.855"
841
     x="313.99307"
842
     y="537.09552"
843
     rx="3.9205718"
844
     ry="3.9839513" />
845
  <path
846
     sodipodi:nodetypes="cc"
847
     inkscape:connector-curvature="0"
848
     id="path9491"
849
     d="m 240.99257,554.11276 65.23376,-1.01307"
850
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9513)" />
851
  <path
852
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9640)"
853
     d="m 481.61298,554.11276 65.23376,-1.01307"
854
     id="path9501"
855
     inkscape:connector-curvature="0"
856
     sodipodi:nodetypes="cc" />
857
  <rect
858
     ry="3.9839513"
859
     rx="3.9205718"
860
     y="617.79578"
861
     x="313.99307"
862
     height="32.855"
863
     width="165.621"
864
     id="rect9620"
865
     style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2890)" />
866
  <path
867
     sodipodi:nodetypes="cc"
868
     inkscape:connector-curvature="0"
869
     id="path9681"
870
     d="m 481.61298,634.81299 65.23376,-1.01307"
871
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9685)" />
872
  <rect
873
     style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2854)"
874
     id="rect9687"
875
     width="165.621"
876
     height="32.855"
877
     x="554.61353"
878
     y="617.79578"
879
     rx="3.9205718"
880
     ry="3.9839513" />
881
  <path
882
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9750)"
883
     d="m 481.61298,715.51321 65.23376,-1.01307"
884
     id="path9734"
885
     inkscape:connector-curvature="0"
886
     sodipodi:nodetypes="cc" />
887
  <rect
888
     ry="3.9839513"
889
     rx="3.9205718"
890
     y="698.49591"
891
     x="554.61353"
892
     height="32.855"
893
     width="165.621"
894
     id="rect9736"
895
     style="opacity:1;fill:#c53bd7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2866)" />
896
  <path
897
     id="path9830"
898
     d="m 356.40451,489.45323 c -0.80426,0 -1.45167,0.64741 -1.45167,1.45166 v 0.11602 c 0,-0.43433 0.1748,-0.82605 0.45822,-1.10946 0.28341,-0.28342 0.6761,-0.45822 1.11043,-0.45822 z"
899
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.10902636;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
900
     inkscape:connector-curvature="0" />
901
  <rect
902
     style="opacity:1;fill:#ffb73a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.9391377;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
903
     id="rect9826"
904
     width="120.98324"
905
     height="24"
906
     x="336.82672"
907
     y="477.86002"
908
     rx="2.8639088"
909
     ry="2.9102066" />
910
  <path
911
     id="path10514"
912
     d="m 235.85301,637.23875 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z"
913
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
914
     inkscape:connector-curvature="0" />
915
  <rect
916
     style="opacity:1;fill:#3dd092;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.02355671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2878)"
917
     id="rect9717"
918
     width="165.621"
919
     height="32.855"
920
     x="313.99307"
921
     y="698.49591"
922
     rx="3.9205718"
923
     ry="3.9839513" />
924
  <path
925
     id="path10537"
926
     d="M 238.99366,636.97465 H 73.372671 V 729.175 c 0,1.2055 0.970418,2.17592 2.175911,2.17592 H 236.81776 c 1.20549,0 2.1759,-0.97042 2.1759,-2.17592 z"
927
     style="opacity:1;fill:#333333;fill-opacity:0.93333333;fill-rule:nonzero;stroke:none;stroke-width:0.16342013;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
928
     inkscape:connector-curvature="0"
929
     sodipodi:nodetypes="ccssssc" />
930
  <path
931
     sodipodi:nodetypes="sssccssss"
932
     id="path10516"
933
     d="m 75.723937,612.39226 c -0.651025,0 -1.239637,0.26201 -1.664447,0.68682 -0.424811,0.42482 -0.686822,1.01196 -0.686822,1.66299 v 22.23258 H 238.99366 v -22.23258 c 0,-0.65103 -0.26201,-1.23817 -0.68682,-1.66299 -0.42481,-0.42481 -1.01197,-0.68682 -1.66299,-0.68682 z"
934
     style="opacity:1;fill:#ffb73a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.16342013;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
935
     inkscape:connector-curvature="0" />
936
  <path
937
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker10933)"
938
     d="m 240.99257,715.51318 65.23376,-1.01307"
939
     id="path10929"
940
     inkscape:connector-curvature="0"
941
     sodipodi:nodetypes="cc" />
942
  <path
943
     style="fill:#df4d65;fill-opacity:1;fill-rule:evenodd;stroke-width:0.05250736"
944
     d="m 399.47357,99.155037 -0.12716,0.0127 -0.12716,0.0141 -0.12574,0.0141 -0.12716,0.0155 -0.61883,0.967813 -0.29106,0.9325 -0.17943,0.91977 -0.1215,1.09073 -0.30943,0.0636 -0.308,0.0678 -0.308,0.0735 -0.30801,0.0763 -0.58069,-0.93108 -0.5609,-0.75023 -0.66687,-0.71349 -0.97912,-0.6033 -0.12009,0.0409 -0.1201,0.0424 -0.12009,0.0424 -0.12009,0.0424 -0.38854,1.08085 -0.0763,0.97487 0.0297,0.93673 0.12575,1.08932 -0.28823,0.1314 -0.28681,0.13563 -0.28398,0.13987 -0.28117,0.14271 -0.77425,-0.77849 -0.7149,-0.60612 -0.80816,-0.54819 -1.0879,-0.37017 -0.10879,0.0678 -0.10738,0.0679 -0.10738,0.0678 -0.10738,0.0678 -0.13846,1.14018 0.14129,0.9664 0.23735,0.90706 0.36594,1.03563 -0.25149,0.19215 -0.24865,0.19497 -0.24585,0.19922 -0.24442,0.20204 -0.92825,-0.58634 -0.83077,-0.43234 -0.90988,-0.35463 -1.14301,-0.11868 -0.0904,0.0904 -0.0904,0.089 -0.089,0.0904 -0.0904,0.0904 0.11868,1.14301 0.35462,0.90988 0.43234,0.83218 0.58634,0.92684 -0.20204,0.24442 -0.19921,0.24584 -0.19498,0.24867 -0.19215,0.25148 -1.03563,-0.36593 -0.90705,-0.23736 -0.96641,-0.14128 -1.14018,0.13845 -0.0678,0.10738 -0.0678,0.10738 -0.0678,0.10738 -0.0678,0.10879 0.37016,1.0879 0.5482,0.80816 0.60612,0.71491 0.77848,0.77425 -0.1427,0.28117 -0.13987,0.28398 -0.13564,0.28681 -0.13139,0.28823 -1.09073,-0.12575 -0.93532,-0.0297 -0.97487,0.0763 -1.08084,0.38854 -0.0424,0.12009 -0.0424,0.12009 -0.0424,0.1201 -0.0409,0.12009 0.6033,0.97912 0.7135,0.66686 0.75023,0.56091 0.93107,0.58069 -0.0763,0.30801 -0.0735,0.308 -0.0678,0.308 -0.0636,0.30942 -1.09073,0.1215 -0.91977,0.17944 -0.9325,0.29105 -0.96781,0.61883 -0.0156,0.12717 -0.0141,0.12574 -0.0141,0.12716 -0.0127,0.12716 0.80533,0.81804 0.84348,0.49168 0.85619,0.38006 1.03704,0.36028 -0.006,0.31648 -0.003,0.31648 0.003,0.31648 0.006,0.31648 -1.03704,0.35887 -0.85619,0.38006 -0.84348,0.49168 -0.80533,0.81946 0.0127,0.12716 0.0141,0.12574 0.0141,0.12716 0.0156,0.12574 0.96781,0.62026 0.9325,0.29104 0.91977,0.17944 1.09073,0.12009 0.0636,0.30942 0.0678,0.30941 0.0735,0.30801 0.0763,0.30659 -0.93107,0.5821 -0.75023,0.56091 -0.7135,0.66687 -0.6033,0.97771 0.0409,0.12008 0.0424,0.12151 0.0424,0.1201 0.0424,0.11868 1.08084,0.38995 0.97487,0.0763 0.93532,-0.0297 1.09073,-0.12574 0.13139,0.28822 0.13564,0.2854 0.13987,0.28399 0.1427,0.28257 -0.77848,0.77425 -0.60612,0.7135 -0.5482,0.80957 -0.37016,1.08791 0.0678,0.10737 0.0678,0.10879 0.0678,0.10738 0.0678,0.10738 1.14018,0.13846 0.96641,-0.1427 0.90705,-0.23736 1.03563,-0.36452 0.19215,0.25149 0.19498,0.24866 0.19921,0.24584 0.20204,0.24302 -0.58634,0.92825 -0.43234,0.83076 -0.35462,0.9113 -0.11868,1.14159 0.0904,0.0918 0.089,0.089 0.0904,0.0904 0.0904,0.089 1.14301,-0.11868 0.90988,-0.35321 0.83077,-0.43375 0.92825,-0.58493 0.24442,0.20204 0.24585,0.19921 0.24865,0.19497 0.25149,0.19216 -0.36594,1.03563 -0.23735,0.90564 -0.14129,0.9664 0.13846,1.14018 0.10738,0.0692 0.10738,0.0678 0.10738,0.0678 0.10879,0.0664 1.0879,-0.37017 0.80816,-0.54677 0.7149,-0.60754 0.77425,-0.77708 0.28117,0.14271 0.28398,0.13987 0.28681,0.13422 0.28823,0.13139 -0.12575,1.09074 -0.0297,0.93673 0.0763,0.97346 0.38854,1.08084 0.12009,0.0438 0.12009,0.0424 0.1201,0.041 0.12009,0.0409 0.97912,-0.60188 0.66687,-0.71349 0.5609,-0.75165 0.58069,-0.93108 0.30801,0.0777 0.308,0.072 0.308,0.0692 0.30943,0.0636 0.1215,1.09073 0.17943,0.91978 0.29106,0.93249 0.61883,0.9664 0.12716,0.0156 0.12574,0.0141 0.12716,0.0141 0.12716,0.0141 0.81806,-0.80533 0.49167,-0.8449 0.38006,-0.85619 0.36028,-1.03704 0.31648,0.007 0.31648,0.003 0.31649,-0.003 0.31648,-0.007 0.36028,1.03704 0.37865,0.85619 0.49167,0.8449 0.81947,0.80533 0.12715,-0.0141 0.12574,-0.0141 0.12717,-0.0141 0.12574,-0.0156 0.62025,-0.9664 0.29104,-0.93249 0.17944,-0.91978 0.12009,-1.09073 0.30942,-0.0636 0.30942,-0.0692 0.30799,-0.072 0.3066,-0.0777 0.58211,0.93108 0.5609,0.75165 0.66687,0.71349 0.97771,0.60188 0.12009,-0.0409 0.1215,-0.041 0.1201,-0.0424 0.11868,-0.0438 0.38995,-1.08084 0.0763,-0.97346 -0.0297,-0.93673 -0.12574,-1.09074 0.28822,-0.13139 0.2854,-0.13422 0.28398,-0.13987 0.28258,-0.14271 0.77424,0.77708 0.7135,0.60754 0.80957,0.54677 1.08791,0.37017 0.10737,-0.0664 0.10879,-0.0678 0.10738,-0.0678 0.10738,-0.0692 0.13847,-1.14018 -0.14271,-0.9664 -0.23737,-0.90564 -0.36452,-1.03563 0.25149,-0.19216 0.24866,-0.19497 0.24585,-0.19921 0.24301,-0.20204 0.92825,0.58493 0.83077,0.43375 0.91129,0.35321 1.1416,0.11868 0.0904,-0.089 0.0904,-0.0904 0.0904,-0.089 0.089,-0.0918 -0.11868,-1.14159 -0.35321,-0.9113 -0.43375,-0.83076 -0.58492,-0.92825 0.20203,-0.24302 0.19921,-0.24584 0.19498,-0.24866 0.19215,-0.25149 1.03563,0.36452 0.90564,0.23736 0.9664,0.1427 1.14018,-0.13846 0.0692,-0.10738 0.0678,-0.10738 0.0678,-0.10879 0.0664,-0.10737 -0.37017,-1.08791 -0.54677,-0.80957 -0.60754,-0.7135 -0.77706,-0.77425 0.1427,-0.28257 0.13986,-0.28399 0.13423,-0.2854 0.13139,-0.28822 1.09073,0.12574 0.93674,0.0297 0.97345,-0.0763 1.08085,-0.38995 0.0438,-0.11868 0.0424,-0.1201 0.0409,-0.12151 0.041,-0.12008 -0.6019,-0.97771 -0.71349,-0.66687 -0.75164,-0.56091 -0.93108,-0.5821 0.0777,-0.30659 0.072,-0.30801 0.0692,-0.30941 0.0636,-0.30942 1.09073,-0.12009 0.91978,-0.17944 0.93249,-0.29104 0.9664,-0.62026 0.0155,-0.12574 0.0141,-0.12716 0.0141,-0.12574 0.0141,-0.12716 -0.80533,-0.81946 -0.8449,-0.49168 -0.85619,-0.38006 -1.03704,-0.35887 0.007,-0.31648 0.003,-0.31648 -0.003,-0.31648 -0.007,-0.31648 1.03704,-0.36028 0.85619,-0.38006 0.8449,-0.49168 0.80533,-0.81804 -0.0141,-0.12716 -0.0141,-0.12716 -0.0141,-0.12574 -0.0155,-0.12717 -0.9664,-0.61883 -0.93249,-0.29105 -0.91978,-0.17944 -1.09073,-0.1215 -0.0636,-0.30942 -0.0692,-0.308 -0.072,-0.308 -0.0777,-0.30801 0.93108,-0.58069 0.75164,-0.56091 0.71349,-0.66686 0.6019,-0.97912 -0.041,-0.12009 -0.0409,-0.1201 -0.0424,-0.12009 -0.0438,-0.12009 -1.08085,-0.38854 -0.97345,-0.0763 -0.93674,0.0297 -1.09073,0.12575 -0.13139,-0.28823 -0.13423,-0.28681 -0.13986,-0.28398 -0.1427,-0.28117 0.77706,-0.77425 0.60754,-0.71491 0.54677,-0.80816 0.37017,-1.0879 -0.0664,-0.10879 -0.0678,-0.10738 -0.0678,-0.10738 -0.0692,-0.10738 -1.14018,-0.13845 -0.9664,0.14128 -0.90564,0.23736 -1.03563,0.36593 -0.19215,-0.25148 -0.19498,-0.24867 -0.19921,-0.24584 -0.20203,-0.24442 0.58492,-0.92684 0.43375,-0.83218 0.35321,-0.90988 0.11868,-1.14301 -0.089,-0.0904 -0.0904,-0.0904 -0.0904,-0.089 -0.0904,-0.0904 -1.1416,0.11868 -0.91129,0.35463 -0.83077,0.43234 -0.92825,0.58634 -0.24301,-0.20204 -0.24585,-0.19922 -0.24866,-0.19497 -0.25149,-0.19215 0.36452,-1.03563 0.23737,-0.90706 0.14271,-0.9664 -0.13847,-1.14018 -0.10738,-0.0678 -0.10738,-0.0678 -0.10879,-0.0679 -0.10737,-0.0678 -1.08791,0.37017 -0.80957,0.54819 -0.7135,0.60612 -0.77424,0.77849 -0.28258,-0.14271 -0.28398,-0.13987 -0.2854,-0.13563 -0.28822,-0.1314 0.12574,-1.08932 0.0297,-0.93673 -0.0763,-0.97487 -0.38995,-1.08085 -0.11868,-0.0424 -0.1201,-0.0424 -0.1215,-0.0424 -0.12009,-0.0409 -0.97771,0.6033 -0.66687,0.71349 -0.5609,0.75023 -0.58211,0.93108 -0.3066,-0.0763 -0.30799,-0.0735 -0.30942,-0.0678 -0.30942,-0.0636 -0.12009,-1.09073 -0.17944,-0.91977 -0.29104,-0.9325 -0.62025,-0.967813 -0.12574,-0.0155 -0.12717,-0.0141 -0.12574,-0.0141 -0.12715,-0.0127 -0.81947,0.80533 -0.49167,0.843483 -0.37865,0.8562 -0.36028,1.03704 -0.31648,-0.006 -0.31649,-0.003 -0.31648,0.003 -0.31648,0.006 -0.36028,-1.03704 -0.38006,-0.8562 -0.49167,-0.843483 z m 2.68302,20.688573 a 5.3990039,5.3990039 0 0 1 5.39856,5.39997 5.3990039,5.3990039 0 0 1 -5.39856,5.39855 5.3990039,5.3990039 0 0 1 -5.39996,-5.39855 5.3990039,5.3990039 0 0 1 5.39996,-5.39997 z"
945
     id="path5693"
946
     inkscape:connector-curvature="0" />
947
  <path
948
     inkscape:connector-curvature="0"
949
     d="m 380.9529,101.31918 a 4.37599,4.37599 0 0 1 -4.37599,4.37599 4.37599,4.37599 0 0 1 -4.37599,-4.37599 4.37599,4.37599 0 0 1 4.37599,-4.375983 4.37599,4.37599 0 0 1 4.37599,4.375983 z m 4.63493,-1.27213 c -0.32212,-0.118873 -0.95326,0.0926 -0.92258,-0.401293 -0.13877,-0.39635 -0.21401,-0.74537 0.27363,-0.88946 0.78055,-0.47633 1.45123,-1.16128 1.74461,-2.04171 0.15411,-0.39145 -0.3432,-0.48754 -0.63657,-0.53536 -0.91614,-0.25589 -1.86519,0.0578 -2.73328,0.35995 -0.11023,-0.31345 -0.69059,-0.56868 -0.47901,-0.88657 0.56405,-0.84324 0.99162,-1.8335 0.85012,-2.86709 -0.031,-0.41955 -0.52073,-0.29038 -0.8058,-0.20618 -0.93646,0.16692 -1.65537,0.86143 -2.30642,1.51029 -0.23528,-0.23464 -0.86896,-0.21274 -0.81624,-0.59097 0.14241,-1.00446 0.0978,-2.08217 -0.47806,-2.95201 -0.20995,-0.36459 -0.59514,-0.0357 -0.81544,0.16385 -0.7713,0.55671 -1.11768,1.49434 -1.42274,2.36143 -0.3138,-0.10927 -0.87519,0.18536 -0.99181,-0.17826 -0.30757,-0.96675 -0.81528,-1.91841 -1.71153,-2.45229 -0.34734,-0.23737 -0.55176,0.22606 -0.66364,0.50149 -0.45331,0.83622 -0.35865,1.83127 -0.25723,2.74482 -0.33012,0.0378 -0.70817,0.54672 -0.97098,0.26977 -0.69651,-0.73763 -1.56687,-1.37476 -2.60601,-1.46682 -0.41593,-0.0631 -0.39904,0.443 -0.38034,0.73971 -0.0456,0.95009 0.47143,1.80554 0.95918,2.58463 -0.28104,0.17725 -0.40076,0.79984 -0.75777,0.6643 -0.94759,-0.36236 -2.00818,-0.55864 -2.98437,-0.1908 -0.40215,0.12357 -0.16723,0.57227 -0.0217,0.83147 0.37114,0.8758 1.2081,1.42221 1.98561,1.91248 -0.17632,0.28163 -0.0141,0.89453 -0.39449,0.92732 -1.01097,0.0847 -2.05173,0.367983 -2.77166,1.122963 -0.3087,0.2858 0.0976,0.58816 0.34122,0.75853 0.71439,0.62804 1.70558,0.75716 2.6188,0.8616 -0.0367,0.33025 0.37548,0.81204 0.0469,1.00665 -0.87415,0.51488 -1.68886,1.22174 -2.00994,2.2143 -0.15411,0.39144 0.3432,0.48753 0.63657,0.53535 0.91612,0.25589 1.86516,-0.0578 2.73323,-0.35995 0.11029,0.31341 0.69063,0.56869 0.47905,0.88657 -0.56412,0.8432 -0.99155,1.83352 -0.85015,2.86709 0.031,0.41952 0.52075,0.2904 0.80584,0.20618 0.93644,-0.16692 1.65537,-0.86139 2.30637,-1.51029 0.2353,0.23464 0.86901,0.21272 0.81629,0.59098 -0.14241,1.00446 -0.0978,2.08215 0.47802,2.95202 0.20997,0.36455 0.59517,0.0357 0.81548,-0.16387 0.77125,-0.55674 1.11768,-1.49435 1.42274,-2.36142 0.31379,0.10926 0.8752,-0.18537 0.99181,0.17824 0.30754,0.96678 0.81527,1.91842 1.71153,2.45229 0.34733,0.23738 0.55172,-0.22608 0.66362,-0.50146 0.45335,-0.83621 0.35866,-1.83128 0.25725,-2.74484 0.33011,-0.0378 0.70812,-0.54672 0.97093,-0.26977 0.69656,0.7376 1.5669,1.37477 2.60606,1.46683 0.41593,0.0632 0.39897,-0.44304 0.38032,-0.73972 0.0457,-0.95011 -0.4715,-1.8055 -0.95916,-2.58463 0.28105,-0.17722 0.40074,-0.79983 0.75772,-0.6643 0.94761,0.36234 2.00821,0.55865 2.98442,0.1908 0.40215,-0.12357 0.16723,-0.57228 0.0217,-0.83146 -0.37116,-0.87579 -1.20814,-1.42218 -1.98561,-1.91249 0.17632,-0.28163 0.0141,-0.89453 0.39449,-0.92732 1.01097,-0.0847 2.05173,-0.36799 2.77166,-1.12295 0.3087,-0.28581 -0.0976,-0.58817 -0.34122,-0.75854 -0.47483,-0.43652 -1.13407,-0.61787 -1.75144,-0.75008 z"
950
     style="fill:#05556e;fill-opacity:1;fill-rule:evenodd;stroke-width:0.04014921"
951
     id="path5687" />
952
  <path
953
     style="fill:#05556e;fill-opacity:1;stroke-width:0.11881336"
954
     id="path4816"
955
     d="m 619.92265,90.37586 h 2.72717 v 2.71445 h 2.51374 v -2.71445 h 2.72716 v 8.21477 h -2.72716 v -2.73825 h -2.49003 v 2.73825 h -2.75088 m 11.57268,-5.47651 h -2.40702 v -2.73826 h 7.55307 v 2.73826 h -2.41888 v 5.47651 h -2.72717 m 6.34363,-8.21477 h 2.8576 l 1.75487,2.89303 1.75487,-2.89303 h 2.8576 v 8.21477 h -2.72717 v -4.07167 l -1.90901,2.95256 -1.90902,-2.95256 v 4.07167 h -2.67974 m 10.57667,-8.21477 h 2.72717 v 5.50033 h 3.86546 v 2.71444 h -6.59263"
956
     inkscape:connector-curvature="0" />
957
  <path
958
     id="path4818"
959
     d="m 619.82779,146.45062 -3.91289,-44.09786 h 43.01811 l -3.91289,44.07405 -17.63174,4.90505"
960
     inkscape:connector-curvature="0"
961
     style="fill:#e44d26;stroke-width:0.11881336" />
962
  <path
963
     id="path4820"
964
     d="m 637.42396,147.58164 v -41.60962 h 17.5843 l -3.3556,37.62129"
965
     inkscape:connector-curvature="0"
966
     style="fill:#f16529;stroke-width:0.11881336" />
967
  <path
968
     id="path4822"
969
     d="m 623.90669,111.3652 h 13.51727 v 5.40508 h -7.61236 l 0.498,5.53605 h 7.11436 v 5.39318 h -12.04697 m 0.23714,2.71444 h 5.40691 l 0.37943,4.32169 6.02349,1.61914 v 5.64319 L 626.373,138.90255"
970
     inkscape:connector-curvature="0"
971
     style="fill:#ebebeb;stroke-width:0.11881336" />
972
  <path
973
     id="path4824"
974
     d="m 650.89379,111.3652 h -13.49355 v 5.40508 h 12.99555 m -0.48615,5.53605 h -12.5094 v 5.40508 h 6.64006 l -0.62843,7.02423 -6.01163,1.61914 v 5.61938 l 11.02724,-3.07161"
975
     inkscape:connector-curvature="0"
976
     style="fill:#ffffff;stroke-width:0.11881336" />
977
  <path
978
     sodipodi:nodetypes="cc"
979
     inkscape:connector-curvature="0"
980
     id="path5804"
981
     d="m 240.99252,105.07517 65.2338,-1.01308"
982
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9479)" />
983
  <path
984
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9505)"
985
     d="m 481.61302,105.07517 65.2337,-1.01308"
986
     id="path9497"
987
     inkscape:connector-curvature="0"
988
     sodipodi:nodetypes="cc" />
989
  <path
990
     inkscape:connector-curvature="0"
991
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
992
     d="m 235.85308,230.53494 c 0.87012,0 1.65488,0.35019 2.22266,0.91797 0.56778,0.56778 0.91797,1.35253 0.91797,2.22265 v -0.23242 c 0,-1.6112 -1.297,-2.9082 -2.9082,-2.9082 z"
993
     id="path6102" />
994
  <path
995
     inkscape:connector-curvature="0"
996
     style="opacity:1;fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.21841836;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
997
     d="m 76.280823,230.53494 c -1.611195,0 -2.908203,1.297 -2.908203,2.9082 v 0.23242 c 0,-0.87012 0.35019,-1.65487 0.917968,-2.22265 0.567779,-0.56778 1.354485,-0.91797 2.22461,-0.91797 z"
998
     id="path6106" />
999
  <path
1000
     inkscape:connector-curvature="0"
1001
     style="fill:#46c7f0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2950)"
1002
     d="m 76.515198,222.53494 c -0.870125,0 -1.656831,0.35019 -2.22461,0.91797 -0.567778,0.56778 -0.917968,1.35253 -0.917968,2.22265 v 29.71485 h 165.62109 v -29.71485 c 0,-0.87012 -0.35019,-1.65487 -0.91797,-2.22265 -0.56778,-0.56778 -1.35254,-0.91797 -2.22266,-0.91797 z"
1003
     id="path6104"
1004
     sodipodi:nodetypes="sssccssss" />
1005
  <path
1006
     sodipodi:nodetypes="ccssssc"
1007
     inkscape:connector-curvature="0"
1008
     style="fill:#e6e7e7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.218418;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter2950)"
1009
     d="M 238.99371,255.39041 H 73.37262 v 152.63392 c 0,1.6112 1.297008,2.90821 2.908203,2.90821 H 236.08551 c 1.6112,0 2.9082,-1.29701 2.9082,-2.90821 z"
1010
     id="path6100" />
1011
  <path
1012
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9509)"
1013
     d="m 240.99257,328.95043 65.23376,-1.01307"
1014
     id="path9485"
1015
     inkscape:connector-curvature="0"
1016
     sodipodi:nodetypes="cc" />
1017
  <path
1018
     sodipodi:nodetypes="cc"
1019
     inkscape:connector-curvature="0"
1020
     id="path9757"
1021
     d="m 481.61298,300.08996 65.23376,-1.01307"
1022
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9761)" />
1023
  <path
1024
     style="opacity:1;vector-effect:none;fill:#05556e;fill-opacity:1;stroke:#05556e;stroke-width:3.94158769;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#marker9767)"
1025
     d="M 552.61456,372.04139 487.3808,371.02832"
1026
     id="path9763"
1027
     inkscape:connector-curvature="0"
1028
     sodipodi:nodetypes="cc" />
1029
  <text
1030
     id="text2269"
1031
     y="62.149761"
1032
     x="115.43707"
1033
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1034
     xml:space="preserve"><tspan
1035
       y="62.149761"
1036
       x="115.43707"
1037
       id="tspan2267"
1038
       sodipodi:role="line">Text Edit</tspan></text>
1039
  <text
1040
     transform="rotate(-90)"
1041
     id="text2273"
1042
     y="43.507812"
1043
     x="-132.24059"
1044
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1045
     xml:space="preserve"><tspan
1046
       y="43.507812"
1047
       x="-132.24059"
1048
       id="tspan2271"
1049
       sodipodi:role="line">Today</tspan></text>
1050
  <text
1051
     id="text2277"
1052
     y="61.540386"
1053
     x="358.88168"
1054
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1055
     xml:space="preserve"><tspan
1056
       y="61.540386"
1057
       x="358.88168"
1058
       id="tspan2275"
1059
       sodipodi:role="line">Process</tspan></text>
1060
  <text
1061
     id="text2281"
1062
     y="59.34898"
1063
     x="605.30872"
1064
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1065
     xml:space="preserve"><tspan
1066
       y="59.34898"
1067
       x="605.30872"
1068
       id="tspan2279"
1069
       sodipodi:role="line">Output</tspan></text>
1070
  <text
1071
     id="text2285"
1072
     y="245.17946"
1073
     x="605.30872"
1074
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1075
     xml:space="preserve"><tspan
1076
       y="245.17946"
1077
       x="605.30872"
1078
       id="tspan2283"
1079
       sodipodi:role="line">Output</tspan></text>
1080
  <text
1081
     id="text2289"
1082
     y="247.37088"
1083
     x="358.88168"
1084
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1085
     xml:space="preserve"><tspan
1086
       y="247.37088"
1087
       x="358.88168"
1088
       id="tspan2287"
1089
       sodipodi:role="line">Process</tspan></text>
1090
  <text
1091
     id="text2293"
1092
     y="247.98026"
1093
     x="115.43707"
1094
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1095
     xml:space="preserve"><tspan
1096
       y="247.98026"
1097
       x="115.43707"
1098
       id="tspan2291"
1099
       sodipodi:role="line">Text Edit</tspan></text>
1100
  <text
1101
     transform="rotate(-90)"
1102
     id="text2297"
1103
     y="43.630859"
1104
     x="-363.15442"
1105
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1106
     xml:space="preserve"><tspan
1107
       y="43.630859"
1108
       x="-363.15442"
1109
       id="tspan2295"
1110
       sodipodi:role="line">Proposed</tspan></text>
1111
  <text
1112
     id="text2301"
1113
     y="314.01108"
1114
     x="98.034729"
1115
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1116
     xml:space="preserve"><tspan
1117
       id="tspan2299"
1118
       sodipodi:role="line"
1119
       x="98.034729"
1120
       y="314.01108">R Markdown</tspan></text>
1121
  <text
1122
     id="text2305"
1123
     y="285.84311"
1124
     x="107.43903"
1125
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1126
     xml:space="preserve"><tspan
1127
       id="tspan2303"
1128
       sodipodi:role="line"
1129
       x="107.43903"
1130
       y="285.84311">Markdown</tspan></text>
1131
  <text
1132
     id="text2309"
1133
     y="342.91147"
1134
     x="134.3277"
1135
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1136
     xml:space="preserve"><tspan
1137
       id="tspan2307"
1138
       sodipodi:role="line"
1139
       x="134.3277"
1140
       y="342.91147">XML</tspan></text>
1141
  <text
1142
     id="text2313"
1143
     y="370.34702"
1144
     x="113.56207"
1145
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1146
     xml:space="preserve"><tspan
1147
       id="tspan2311"
1148
       sodipodi:role="line"
1149
       x="113.56207"
1150
       y="370.34702">DocBook</tspan></text>
1151
  <text
1152
     id="text2317"
1153
     y="398.51498"
1154
     x="114.3526"
1155
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1156
     xml:space="preserve"><tspan
1157
       id="tspan2315"
1158
       sodipodi:role="line"
1159
       x="114.3526"
1160
       y="398.51498">AsciiDoc</tspan></text>
1161
  <text
1162
     transform="rotate(-90)"
1163
     id="text2329"
1164
     y="43.507812"
1165
     x="-774.87335"
1166
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1167
     xml:space="preserve"><tspan
1168
       y="43.507812"
1169
       x="-774.87335"
1170
       id="tspan2327"
1171
       sodipodi:role="line">Example Processing Combination</tspan></text>
1172
  <text
1173
     id="text2333"
1174
     y="562.05426"
1175
     x="135.31207"
1176
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#f3fbfe;fill-opacity:1;stroke:none"
1177
     xml:space="preserve"><tspan
1178
       y="562.05426"
1179
       x="135.31207"
1180
       id="tspan2331"
1181
       sodipodi:role="line">XML</tspan></text>
1182
  <text
1183
     id="text2337"
1184
     y="495.6918"
1185
     x="381.64142"
1186
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#333333;fill-opacity:0.866667;stroke:none"
1187
     xml:space="preserve"><tspan
1188
       y="495.6918"
1189
       x="381.64142"
1190
       id="tspan2335"
1191
       sodipodi:role="line">XSLT</tspan></text>
1192
  <text
1193
     id="text2341"
1194
     y="562.05426"
1195
     x="323.97742"
1196
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1197
     xml:space="preserve"><tspan
1198
       y="562.05426"
1199
       x="323.97742"
1200
       id="tspan2339"
1201
       sodipodi:role="line">XSLT Processor</tspan></text>
1202
  <text
1203
     id="text2345"
1204
     y="562.54059"
1205
     x="579.27557"
1206
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1207
     xml:space="preserve"><tspan
1208
       y="562.54059"
1209
       x="579.27557"
1210
       id="tspan2343"
1211
       sodipodi:role="line">R Markdown</tspan></text>
1212
  <text
1213
     id="text2349"
1214
     y="643.24084"
1215
     x="588.75018"
1216
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1217
     xml:space="preserve"><tspan
1218
       y="643.24084"
1219
       x="588.75018"
1220
       id="tspan2347"
1221
       sodipodi:role="line">Markdown</tspan></text>
1222
  <text
1223
     id="text2353"
1224
     y="642.63147"
1225
     x="339.61023"
1226
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1227
     xml:space="preserve"><tspan
1228
       y="642.63147"
1229
       x="339.61023"
1230
       id="tspan2351"
1231
       sodipodi:role="line">R Processor</tspan></text>
1232
  <text
1233
     id="text2357"
1234
     y="722.93903"
1235
     x="318.43912"
1236
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:21.3333px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1237
     xml:space="preserve"><tspan
1238
       y="722.93903"
1239
       x="318.43912"
1240
       id="tspan2355"
1241
       sodipodi:role="line">Variable Processor</tspan></text>
1242
  <text
1243
     id="text2361"
1244
     y="723.3316"
1245
     x="604.07831"
1246
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1247
     xml:space="preserve"><tspan
1248
       y="723.3316"
1249
       x="604.07831"
1250
       id="tspan2359"
1251
       sodipodi:role="line">HTML5</tspan></text>
1252
  <text
1253
     id="text2365"
1254
     y="630.84766"
1255
     x="81.211723"
1256
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#333333;fill-opacity:0.866667;stroke:none"
1257
     xml:space="preserve"><tspan
1258
       y="630.84766"
1259
       x="81.211723"
1260
       id="tspan2363"
1261
       sodipodi:role="line">Structured Data Source</tspan></text>
1262
  <text
1263
     id="text2369"
1264
     y="756.39404"
1265
     x="215.65826"
1266
     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1267
     xml:space="preserve"><tspan
1268
       y="756.39404"
1269
       x="215.65826"
1270
       id="tspan2367"
1271
       sodipodi:role="line">interpolated values</tspan></text>
1272
  <g
1273
     transform="translate(-0.25585322,11.831789)"
1274
     id="g2523">
1275
    <text
1276
       xml:space="preserve"
1277
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1278
       x="156.49219"
1279
       y="708.2467"
1280
       id="text2373"><tspan
1281
         sodipodi:role="line"
1282
         id="tspan2371"
1283
         x="156.49219"
1284
         y="708.2467">CSON</tspan></text>
1285
    <text
1286
       xml:space="preserve"
1287
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1288
       x="156.49219"
1289
       y="688.41504"
1290
       id="text2377"><tspan
1291
         sodipodi:role="line"
1292
         id="tspan2375"
1293
         x="156.49219"
1294
         y="688.41504">JSONNET</tspan></text>
1295
    <text
1296
       xml:space="preserve"
1297
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1298
       x="156.49219"
1299
       y="668.24695"
1300
       id="text2381"><tspan
1301
         sodipodi:role="line"
1302
         id="tspan2379"
1303
         x="156.49219"
1304
         y="668.24695">JSON5</tspan></text>
1305
    <text
1306
       xml:space="preserve"
1307
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1308
       x="156.49219"
1309
       y="648.07886"
1310
       id="text2385"><tspan
1311
         sodipodi:role="line"
1312
         id="tspan2383"
1313
         x="156.49219"
1314
         y="648.07886">JSON</tspan></text>
1315
    <text
1316
       xml:space="preserve"
1317
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1318
       x="94.110725"
1319
       y="648.41534"
1320
       id="text2389"><tspan
1321
         sodipodi:role="line"
1322
         id="tspan2387"
1323
         x="94.110725"
1324
         y="648.41534">YAML</tspan></text>
1325
    <text
1326
       xml:space="preserve"
1327
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1328
       x="94.110725"
1329
       y="668.24695"
1330
       id="text2393"><tspan
1331
         sodipodi:role="line"
1332
         id="tspan2391"
1333
         x="94.110725"
1334
         y="668.24695">TOML</tspan></text>
1335
    <text
1336
       xml:space="preserve"
1337
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:16.4059px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:0.933333;stroke:none"
1338
       x="94.110725"
1339
       y="688.41504"
1340
       id="text2397"><tspan
1341
         sodipodi:role="line"
1342
         id="tspan2395"
1343
         x="94.110725"
1344
         y="688.41504">XML</tspan></text>
1345
  </g>
1346
  <g
1347
     transform="translate(-1.2304677,-0.85937628)"
1348
     id="g2593">
1349
    <g
1350
       id="g2532">
1351
      <rect
1352
         id="rect4698"
1353
         ry="2.7292624"
1354
         y="91.740654"
1355
         x="129.16347"
1356
         height="32.205296"
1357
         width="54.039394"
1358
         style="fill:none;stroke:#05556e;stroke-width:2.72926;stroke-opacity:1" />
1359
      <path
1360
         style="fill:#05556e;fill-opacity:1;stroke-width:0.272926"
1361
         id="path4700"
1362
         d="M 135.98663,117.12279 V 98.56381 h 5.45852 l 5.45853,6.82315 5.45852,-6.82315 h 5.45853 v 18.55898 h -5.45853 v -10.64412 l -5.45852,6.82315 -5.45853,-6.82315 v 10.64412 z m 34.11578,0 -8.18779,-9.00657 h 5.45852 v -9.55241 h 5.45853 v 9.55241 h 5.45852 z"
1363
         inkscape:connector-curvature="0" />
1364
    </g>
1365
    <text
1366
       xml:space="preserve"
1367
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1368
       x="108.73981"
1369
       y="152.80437"
1370
       id="text2407"><tspan
1371
         sodipodi:role="line"
1372
         id="tspan2405"
1373
         x="108.73981"
1374
         y="152.80437">Markdown</tspan></text>
1375
  </g>
1376
  <path
1377
     inkscape:connector-curvature="0"
1378
     d="m 417.86562,272.90923 c -2.81873,0.35302 -5.58858,1.78683 -7.90222,4.10047 -1.79226,1.78682 -3.43787,4.20365 -5.01832,7.35911 -1.28173,2.56347 -2.29191,5.21927 -2.90019,7.59265 l -0.1738,0.68975 -0.68975,0.35302 c -0.96673,0.49423 -1.81398,1.01561 -2.77528,1.69993 -3.29666,2.35709 -6.15341,5.19211 -8.53222,8.46705 -0.23354,0.32586 -0.45621,0.58656 -0.49966,0.58656 -0.038,0 -0.33673,-0.0435 -0.65716,-0.0923 -0.73863,-0.11949 -3.19891,-0.13578 -4.11676,-0.0272 -3.79633,0.46164 -7.25593,1.57502 -11.41613,3.68228 -3.00339,1.5207 -4.93685,2.87304 -6.8323,4.77391 -2.37881,2.37882 -3.80176,5.01832 -4.21452,7.82076 -0.0978,0.62457 -0.0978,2.39511 0,3.0414 0.51052,3.55193 2.55804,6.94636 5.27358,8.74404 3.15003,2.08554 7.40256,2.6558 12.27424,1.65105 3.62253,-0.75492 7.20161,-2.14527 10.77526,-4.19822 3.47046,-1.99321 5.87643,-4.18193 7.57093,-6.87575 0.27155,-0.43449 0.35845,-0.52682 0.53224,-0.59199 2.79701,-1.01018 4.74677,-2.05295 6.96265,-3.72572 2.02036,-1.5207 3.43244,-2.85675 6.0991,-5.77324 0.68432,-0.74949 0.8038,-0.91785 0.84182,-1.16225 0.0326,-0.17379 0.0543,-0.20095 0.15207,-0.17922 0.51595,0.10319 2.20502,0.11948 2.94908,0.0272 2.08553,-0.25526 4.05701,-1.10251 6.01763,-2.57976 2.61778,-1.97691 5.06177,-5.27901 6.78885,-9.17853 2.59606,-5.86556 3.57908,-10.80785 3.01425,-15.19073 -0.14121,-1.12423 -0.28241,-1.74881 -0.59742,-2.71554 -0.42905,-1.29803 -1.08621,-2.55804 -1.89001,-3.62796 -0.43449,-0.57026 -1.57502,-1.70536 -2.14528,-2.12898 -1.59131,-1.17855 -3.93753,-2.13442 -6.03936,-2.46028 -0.66259,-0.10319 -2.29735,-0.14664 -2.85132,-0.0815 z m 2.44399,7.82076 c 1.94433,0.46707 3.2152,2.04751 3.5302,4.39917 0.0815,0.58656 0.0815,2.10183 0,2.7427 -0.32043,2.62864 -1.26544,5.70263 -2.61235,8.48878 -1.01561,2.10725 -1.79226,3.34011 -2.88933,4.58383 -0.32587,0.36931 -1.38493,1.31975 -1.42838,1.2763 -0.005,-0.005 0.0706,-0.34216 0.1738,-0.74406 0.24983,-0.97759 0.34215,-1.56958 0.3856,-2.41683 0.0706,-1.58044 -0.27155,-3.09571 -0.98302,-4.30684 -1.20027,-2.05295 -3.17175,-3.41072 -5.47453,-3.78547 -0.11405,-0.0163 -0.20638,-0.0489 -0.20638,-0.076 0,-0.0217 0.19552,-0.53768 0.42905,-1.15139 1.41752,-3.67684 2.66666,-5.83298 4.30142,-7.40799 1.0482,-1.01562 1.70536,-1.40665 2.73726,-1.62933 0.51596,-0.11405 1.49355,-0.0978 2.03666,0.0272 z m -10.34078,17.93885 c 0.52139,0.54311 0.56483,0.76579 0.46164,2.25933 l -0.0326,0.51596 -0.14121,-0.21725 c -0.22811,-0.34215 -0.40733,-0.72233 -0.52682,-1.1188 -0.0652,-0.20095 -0.15207,-0.43992 -0.20095,-0.53224 -0.0706,-0.13035 -0.17922,-0.91243 -0.19008,-1.34691 0,-0.11949 0.29871,0.0923 0.63,0.43991 z m -7.36997,3.01425 c 0.3856,2.28649 1.18397,4.05159 2.44941,5.40393 l 0.45078,0.47793 -0.13577,0.14664 c -0.0706,0.0815 -0.46165,0.51052 -0.86355,0.9613 -1.55328,1.73795 -2.81873,2.98167 -4.05158,3.97012 -0.41819,0.34216 -0.78208,0.61915 -0.79837,0.61915 -0.0163,0 -0.0435,-0.0923 -0.0652,-0.20638 -0.076,-0.4019 -0.46708,-1.4664 -0.8038,-2.15614 -0.54311,-1.12424 -1.14596,-2.0095 -2.08554,-3.0577 l -0.45621,-0.50509 0.41276,-0.50509 c 1.19484,-1.47182 2.92192,-3.26951 4.43177,-4.62728 0.85811,-0.76578 1.37949,-1.21656 1.39578,-1.20027 0.005,0.005 0.0597,0.315 0.11949,0.67888 z m -16.52135,9.77052 c -0.0163,0.11405 -0.0815,0.54311 -0.14664,0.9613 -0.22267,1.47182 -0.23353,3.57365 -0.0272,4.78478 0.19008,1.10251 0.57569,2.11812 1.08078,2.81873 0.27699,0.38018 0.87441,0.97759 1.22199,1.20027 l 0.23354,0.1575 -0.15207,0.12492 c -0.60285,0.48879 -2.54174,1.58044 -4.18193,2.34622 -2.4114,1.12967 -4.36659,1.7651 -6.62049,2.16157 -0.77664,0.13578 -0.99932,0.15207 -2.09096,0.15207 -0.98846,0 -1.30889,-0.0217 -1.67278,-0.0978 -1.5207,-0.33672 -2.53088,-0.97216 -3.1989,-2.0095 -0.53225,-0.82552 -0.72234,-1.48268 -0.72777,-2.43855 0,-1.56415 0.57027,-2.68296 2.17244,-4.27969 1.78682,-1.77597 3.93753,-3.05227 7.72299,-4.5784 2.01493,-0.81467 4.20366,-1.37407 5.75151,-1.4664 0.74406,-0.0434 0.66803,-0.0652 0.63544,0.16294 z m 6.13712,3.5302 c -0.0163,0.0543 -0.0272,0.0109 -0.0272,-0.0923 0,-0.10319 0.0109,-0.14664 0.0272,-0.0978 0.0109,0.0543 0.0109,0.14121 0,0.19009 z"
1379
     id="path8164"
1380
     style="fill:#df4d65;fill-opacity:1;stroke:none;stroke-width:0.00543108" />
1381
  <g
1382
     transform="translate(1.378418e-5,1.0193503)"
1383
     id="g1168">
1384
    <text
1385
       id="text1158"
1386
       y="364.17905"
1387
       x="349.05551"
1388
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1389
       xml:space="preserve"><tspan
1390
         id="tspan1156"
1391
         sodipodi:role="line"
1392
         x="349.05551"
1393
         y="364.17905">Processor</tspan></text>
1394
    <text
1395
       xml:space="preserve"
1396
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1397
       x="370.40707"
1398
       y="392.17905"
1399
       id="text1162"><tspan
1400
         y="392.17905"
1401
         x="370.40707"
1402
         sodipodi:role="line"
1403
         id="tspan1160">Chain</tspan></text>
1404
  </g>
1405
  <g
1406
     transform="translate(0,-2.3144459)"
1407
     id="g1206">
1408
    <text
1409
       xml:space="preserve"
1410
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1411
       x="586.44855"
1412
       y="327.56967"
1413
       id="text1190"><tspan
1414
         y="327.56967"
1415
         x="586.44855"
1416
         sodipodi:role="line"
1417
         id="tspan1188">Processor-</tspan></text>
1418
    <text
1419
       id="text1194"
1420
       y="355.56967"
1421
       x="588.43488"
1422
       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:condensed;font-size:24px;line-height:1.25;font-family:'Roboto Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#05556e;fill-opacity:1;stroke:none"
1423
       xml:space="preserve"><tspan
1424
         id="tspan1192"
1425
         sodipodi:role="line"
1426
         x="588.43488"
1427
         y="355.56967">dependent</tspan></text>
1428
  </g>
1429
</svg>
11430
A docs/images/architecture/logos/html5.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
	<title>HTML5 Logo</title>
3
	<path d="M108.4 0h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206 23h-20.3V0h63.7v23H229v46h-23M259.5 0h24.1l14.8 24.3L313.2 0h24.1v69h-23V34.8l-16.1 24.8l-16.1-24.8v34.2h-22.6M348.7 0h23v46.2h32.6V69h-55.6"/>
4
	<path fill="#e44d26" d="M107.6 471l-33-370.4h362.8l-33 370.2L255.7 512"/>
5
	<path fill="#f16529" d="M256 480.5V131H404.3L376 447"/>
6
	<path fill="#ebebeb" d="M142 176.3h114v45.4h-64.2l4.2 46.5h60v45.3H154.4M156.4 336.3H202l3.2 36.3 50.8 13.6v47.4l-93.2-26"/>
7
	<path fill="#fff" d="M369.6 176.3H255.8v45.4h109.6M361.3 268.2H255.8v45.4h56l-5.3 59-50.7 13.6v47.2l93-25.8"/>
8
</svg>
A docs/images/architecture/logos/links.svg
1
<?xml version="1.0" standalone="no"?>
2
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
3
 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
4
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
5
 width="1280.000000pt" height="1123.000000pt" viewBox="0 0 1280.000000 1123.000000"
6
 preserveAspectRatio="xMidYMid meet">
7
<metadata>
8
Created by potrace 1.15, written by Peter Selinger 2001-2017
9
</metadata>
10
<g transform="translate(0.000000,1123.000000) scale(0.100000,-0.100000)"
11
fill="#000000" stroke="none">
12
<path d="M10280 11220 c-519 -65 -1029 -329 -1455 -755 -330 -329 -633 -774
13
-924 -1355 -236 -472 -422 -961 -534 -1398 l-32 -127 -127 -65 c-178 -91 -334
14
-187 -511 -313 -607 -434 -1133 -956 -1571 -1559 -43 -60 -84 -108 -92 -108
15
-7 0 -62 8 -121 17 -136 22 -589 25 -758 5 -699 -85 -1336 -290 -2102 -678
16
-553 -280 -909 -529 -1258 -879 -438 -438 -700 -924 -776 -1440 -18 -115 -18
17
-441 0 -560 94 -654 471 -1279 971 -1610 580 -384 1363 -489 2260 -304 667
18
139 1326 395 1984 773 639 367 1082 770 1394 1266 50 80 66 97 98 109 515 186
19
874 378 1282 686 372 280 632 526 1123 1063 126 138 148 169 155 214 6 32 10
20
37 28 33 95 -19 406 -22 543 -5 384 47 747 203 1108 475 482 364 932 972 1250
21
1690 478 1080 659 1990 555 2797 -26 207 -52 322 -110 500 -79 239 -200 471
22
-348 668 -80 105 -290 314 -395 392 -293 217 -725 393 -1112 453 -122 19 -423
23
27 -525 15z m450 -1440 c358 -86 592 -377 650 -810 15 -108 15 -387 0 -505
24
-59 -484 -233 -1050 -481 -1563 -187 -388 -330 -615 -532 -844 -60 -68 -255
25
-243 -263 -235 -1 1 13 63 32 137 46 180 63 289 71 445 13 291 -50 570 -181
26
793 -221 378 -584 628 -1008 697 -21 3 -38 9 -38 14 0 4 36 99 79 212 261 677
27
491 1074 792 1364 193 187 314 259 504 300 95 21 275 18 375 -5z m-1904 -3303
28
c96 -100 104 -141 85 -416 l-6 -95 -26 40 c-42 63 -75 133 -97 206 -12 37 -28
29
81 -37 98 -13 24 -33 168 -35 248 0 22 55 -17 116 -81z m-1357 -555 c71 -421
30
218 -746 451 -995 l83 -88 -25 -27 c-13 -15 -85 -94 -159 -177 -286 -320 -519
31
-549 -746 -731 -77 -63 -144 -114 -147 -114 -3 0 -8 17 -12 38 -14 74 -86 270
32
-148 397 -100 207 -211 370 -384 563 l-84 93 76 93 c220 271 538 602 816 852
33
158 141 254 224 257 221 1 -1 11 -58 22 -125z m-3042 -1799 c-3 -21 -15 -100
34
-27 -177 -41 -271 -43 -658 -5 -881 35 -203 106 -390 199 -519 51 -70 161
35
-180 225 -221 l43 -29 -28 -23 c-111 -90 -468 -291 -770 -432 -444 -208 -804
36
-325 -1219 -398 -143 -25 -184 -28 -385 -28 -182 0 -241 4 -308 18 -280 62
37
-466 179 -589 370 -98 152 -133 273 -134 449 0 288 105 494 400 788 329 327
38
725 562 1422 843 371 150 774 253 1059 270 137 8 123 12 117 -30z m1130 -650
39
c-3 -10 -5 -2 -5 17 0 19 2 27 5 18 2 -10 2 -26 0 -35z"/>
40
</g>
41
</svg>
142
A docs/images/architecture/logos/markdown.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="208" height="128" viewBox="0 0 208 128"><rect width="198" height="118" x="5" y="5" ry="10" stroke="#000" stroke-width="10" fill="none"/><path d="M30 98V30h20l20 25 20-25h20v68H90V59L70 84 50 59v39zm125 0l-30-33h20V30h20v35h20z"/></svg>
A docs/images/black-text.png
Binary file
A docs/images/blocked-text.png
Binary file
A docs/images/resolved-text.png
Binary file
A docs/images/screenshots/01.png
Binary file
A docs/images/screenshots/02.png
Binary file
A docs/images/screenshots/03.png
Binary file
A docs/images/screenshots/04.png
Binary file
A docs/images/screenshots/05.png
Binary file
A docs/images/screenshots/06.png
Binary file
A docs/images/screenshots/07.png
Binary file
A docs/images/screenshots/08.png
Binary file
A docs/images/screenshots/09.png
Binary file
A docs/licenses/BEAN-VALIDATION-API.md
11
2
                                 Apache License
3
                           Version 2.0, January 2004
4
                        http://www.apache.org/licenses/
5
6
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
8
   1. Definitions.
9
10
      "Licen[][yyyy] [name of copyright owner]
A docs/licenses/FILE-ICON-IMAGES.md
1
The MIT License (MIT)
2
3
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
5
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
7
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
19
A docs/licenses/FILE-PREFERENCES.md
1
Released into the Public Domain by David Croft.
2
3
http://www.davidc.net/programming/java/java-preferences-using-file-backing-store
4
http://creativecommons.org/publicdomain/zero/1.0/
5
6
CC0 1.0 Universal (CC0 1.0)
7
8
Public Domain Dedication
9
10
This is a human-readable summary of the Legal Code (read the full text).
11
12
Disclaimer
13
14
No Copyright
15
16
* The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
17
18
* You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. See Other Information below.
19
20
This license is acceptable for Free Cultural Works.
21
22
Other Information
23
24
* In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights.
25
* Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law.
26
* When using or citing the work, you should not imply endorsement by the author or the affirmer.
27
128
A docs/licenses/FLEXMARK.md
1
Copyright (c) 2015-2016, Atlassian Pty Ltd
2
All rights reserved.
3
4
Copyright (c) 2016-2018, Vladimir Schneider,
5
All rights reserved.
6
7
Redistribution and use in source and binary forms, with or without
8
modification, are permitted provided that the following conditions are met:
19
10
* Redistributions of source code
A docs/licenses/FLOWLESS.md
1
Copyright (c) 2014, TomasMikula
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions are met:
16
7
* Redistributions of source code
A docs/licenses/FONT-AWESOME-FX.txt
11
2
                                 Apache License
3
                           Version 2.0, January 2004
4
                        http://www.apache.org/licenses/
5
6
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
8
   1. Definitions.
9
10
      "Licen[][yyyy] [name of copyright owner]
A docs/licenses/JAVA-IMAGE-SCALING.md
1
Java Image Scaling
2
3
Copyright (c) 2013, Morten Nobel-Joergensen
4
All rights reserved.
5
6
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7
following conditions are met:
8
9
Redistributions of source code must retain the above copyright notice, this list of conditions and the following
10
disclaimer.
11
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
12
disclaimer in the documentation and/or other materials provided with the distribution.
13
Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products
14
derived from this software without specific prior written permission.
15
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
122
A docs/licenses/JSYMSPELL.md
1
MIT License
2
3
Copyright (c) 2019 Raul Garcia
4
5
Permission is hereby granted, free of charge, to any person obtaining a copy
6
of this software and associated documentation files (the "Software"), to deal
7
in the Software without restriction, including without limitation the rights
8
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
copies of the Software, and to permit persons to whom the Software is
10
furnished to do so, subject to the following conditions:
11
12
The above copyright notice and this permission notice shall be included in all
13
copies or substantial portions of the Software.
14
15
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
SOFTWARE.
122
A docs/licenses/JUNIVERSAL-CHARDET.md
1
Version: MPL 1.1/GPL 2.0/LGPL 2.1
2
3
The contents of this file are subject to the Mozilla Public License Version
4
1.1 (the "License"); you may not use this file except in compliance with
5
the License. You may obtain a copy of the License at
6
http://www.mozilla.org/MPL/
7
8
Software distributed under the License is distributed on an "AS IS" basis,
9
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10
for the specific language governing rights and limitations under the
11
License.
12
13
The Original Code is Mozilla Universal charset detector code.
14
15
The Initial Developer of the Original Code is
16
Netscape Communications Corporation.
17
Portions created by the Initial Developer are Copyright (C) 2001
18
the Initial Developer. All Rights Reserved.
19
20
Contributor(s):
21
        Alberto Fernández <infjaf@gmail.com>
22
        Shy Shalom <shooshX@gmail.com>
23
        Kohei TAKETA <k-tak@void.in> (Java port)
24
25
Alternatively, the contents of this file may be used under the terms of
26
either the GNU General Public License Version 2 or later (the "GPL"), or
27
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28
in which case the provisions of the GPL or the LGPL are applicable instead
29
of those above. If you wish to allow use of your version of this file only
30
under the terms of either the GPL or the LGPL, and not to allow others to
31
use your version of this file under the terms of the MPL, indicate your
32
decision by deleting the provisions above and replace them with the notice
33
and other provisions required by the GPL or the LGPL. If you do not delete
34
the provisions above, a recipient may use your version of this file under
35
the terms of any one of the MPL, the GPL or the LGPL.
136
A docs/licenses/PREFERENCES-FX.txt
1
                                 Apache License
2
                           Version 2.0, January 2004
3
                        http://www.apache.org/licenses/
4
5
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
7
   1. Definitions.
8
9
      "License" shall mean the terms and conditions for use, reproduction,
10
      and distribution as defined by Sections 1 through 9 of this document.
11
12
      "Licensor" shall mean the copyright owner or entity authorized by
13
      the copyright owner that is granting the License.
14
15
      "Legal Entity" shall mean the union of the acting entity and all
16
      other entities that control, are controlled by, or are under common
17
      control with that entity. For the purposes of this definition,
18
      "control" means (i) the power, direct or indirect, to cause the
19
      direction or management of such entity, whether by contract or
20
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
      outstanding shares, or (iii) beneficial ownership of such entity.
22
23
      "You" (or "Your") shall mean an individual or Legal Entity
24
      exercising permissions granted by this License.
25
26
      "Source" form shall mean the preferred form for making modifications,
27
      including but not limited to software source code, documentation
28
      source, and configuration files.
29
30
      "Object" form shall mean any form resulting from mechanical
31
      transformation or translation of a Source form, including but
32
      not limited to compiled object code, generated documentation,
33
      and conversions to other media types.
34
35
      "Work" shall mean the work of authorship, whether in Source or
36
      Object form, made available under the License, as indicated by a
37
      copyright notice that is included in or attached to the work
38
      (an example is provided in the Appendix below).
39
40
      "Derivative Works" shall mean any work, whether in Source or Object
41
      form, that is based on (or derived from) the Work and for which the
42
      editorial revisions, annotations, elaborations, or other modifications
43
      represent, as a whole, an original work of authorship. For the purposes
44
      of this License, Derivative Works shall not include works that remain
45
      separable from, or merely link (or bind by name) to the interfaces of,
46
      the Work and Derivative Works thereof.
47
48
      "Contribution" shall mean any work of authorship, including
49
      the original version of the Work and any modifications or additions
50
      to that Work or Derivative Works thereof, that is intentionally
51
      submitted to Licensor for inclusion in the Work by the copyright owner
52
      or by an individual or Legal Entity authorized to submit on behalf of
53
      the copyright owner. For the purposes of this definition, "submitted"
54
      means any form of electronic, verbal, or written communication sent
55
      to the Licensor or its representatives, including but not limited to
56
      communication on electronic mailing lists, source code control systems,
57
      and issue tracking systems that are managed by, or on behalf of, the
58
      Licensor for the purpose of discussing and improving the Work, but
59
      excluding communication that is conspicuously marked or otherwise
60
      designated in writing by the copyright owner as "Not a Contribution."
61
62
      "Contributor" shall mean Licensor and any individual or Legal Entity
63
      on behalf of whom a Contribution has been received by Licensor and
64
      subsequently incorporated within the Work.
65
66
   2. Grant of Copyright License. Subject to the terms and conditions of
67
      this License, each Contributor hereby grants to You a perpetual,
68
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
      copyright license to reproduce, prepare Derivative Works of,
70
      publicly display, publicly perform, sublicense, and distribute the
71
      Work and such Derivative Works in Source or Object form.
72
73
   3. Grant of Patent License. Subject to the terms and conditions of
74
      this License, each Contributor hereby grants to You a perpetual,
75
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
      (except as stated in this section) patent license to make, have made,
77
      use, offer to sell, sell, import, and otherwise transfer the Work,
78
      where such license applies only to those patent claims licensable
79
      by such Contributor that are necessarily infringed by their
80
      Contribution(s) alone or by combination of their Contribution(s)
81
      with the Work to which such Contribution(s) was submitted. If You
82
      institute patent litigation against any entity (including a
83
      cross-claim or counterclaim in a lawsuit) alleging that the Work
84
      or a Contribution incorporated within the Work constitutes direct
85
      or contributory patent infringement, then any patent licenses
86
      granted to You under this License for that Work shall terminate
87
      as of the date such litigation is filed.
88
89
   4. Redistribution. You may reproduce and distribute copies of the
90
      Work or Derivative Works thereof in any medium, with or without
91
      modifications, and in Source or Object form, provided that You
92
      meet the following conditions:
93
94
      (a) You must give any other recipients of the Work or
95
          Derivative Works a copy of this License; and
96
97
      (b) You must cause any modified files to carry prominent notices
98
          stating that You changed the files; and
99
100
      (c) You must retain, in the Source form of any Derivative Works
101
          that You distribute, all copyright, patent, trademark, and
102
          attribution notices from the Source form of the Work,
103
          excluding those notices that do not pertain to any part of
104
          the Derivative Works; and
105
106
      (d) If the Work includes a "NOTICE" text file as part of its
107
          distribution, then any Derivative Works that You distribute must
108
          include a readable copy of the attribution notices contained
109
          within such NOTICE file, excluding those notices that do not
110
          pertain to any part of the Derivative Works, in at least one
111
          of the following places: within a NOTICE text file distributed
112
          as part of the Derivative Works; within the Source form or
113
          documentation, if provided along with the Derivative Works; or,
114
          within a display generated by the Derivative Works, if and
115
          wherever such third-party notices normally appear. The contents
116
          of the NOTICE file are for informational purposes only and
117
          do not modify the License. You may add Your own attribution
118
          notices within Derivative Works that You distribute, alongside
119
          or as an addendum to the NOTICE text from the Work, provided
120
          that such additional attribution notices cannot be construed
121
          as modifying the License.
122
123
      You may add Your own copyright statement to Your modifications and
124
      may provide additional or different license terms and conditions
125
      for use, reproduction, or distribution of Your modifications, or
126
      for any such Derivative Works as a whole, provided Your use,
127
      reproduction, and distribution of the Work otherwise complies with
128
      the conditions stated in this License.
129
130
   5. Submission of Contributions. Unless You explicitly state otherwise,
131
      any Contribution intentionally submitted for inclusion in the Work
132
      by You to the Licensor shall be under the terms and conditions of
133
      this License, without any additional terms or conditions.
134
      Notwithstanding the above, nothing herein shall supersede or modify
135
      the terms of any separate license agreement you may have executed
136
      with Licensor regarding such Contributions.
137
138
   6. Trademarks. This License does not grant permission to use the trade
139
      names, trademarks, service marks, or product names of the Licensor,
140
      except as required for reasonable and customary use in describing the
141
      origin of the Work and reproducing the content of the NOTICE file.
142
143
   7. Disclaimer of Warranty. Unless required by applicable law or
144
      agreed to in writing, Licensor provides the Work (and each
145
      Contributor provides its Contributions) on an "AS IS" BASIS,
146
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
      implied, including, without limitation, any warranties or conditions
148
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
      PARTICULAR PURPOSE. You are solely responsible for determining the
150
      appropriateness of using or redistributing the Work and assume any
151
      risks associated with Your exercise of permissions under this License.
152
153
   8. Limitation of Liability. In no event and under no legal theory,
154
      whether in tort (including negligence), contract, or otherwise,
155
      unless required by applicable law (such as deliberate and grossly
156
      negligent acts) or agreed to in writing, shall any Contributor be
157
      liable to You for damages, including any direct, indirect, special,
158
      incidental, or consequential damages of any character arising as a
159
      result of this License or out of the use or inability to use the
160
      Work (including but not limited to damages for loss of goodwill,
161
      work stoppage, computer failure or malfunction, or any and all
162
      other commercial damages or losses), even if such Contributor
163
      has been advised of the possibility of such damages.
164
165
   9. Accepting Warranty or Additional Liability. While redistributing
166
      the Work or Derivative Works thereof, You may choose to offer,
167
      and charge a fee for, acceptance of support, warranty, indemnity,
168
      or other liability obligations and/or rights consistent with this
169
      License. However, in accepting such obligations, You may act only
170
      on Your own behalf and on Your sole responsibility, not on behalf
171
      of any other Contributor, and only if You agree to indemnify,
172
      defend, and hold each Contributor harmless for any liability
173
      incurred by, or claims asserted against, such Contributor by reason
174
      of your accepting any such warranty or additional liability.
175
176
   END OF TERMS AND CONDITIONS
177
178
   APPENDIX: How to apply the Apache License to your work.
179
180
      To apply the Apache License to your work, attach the following
181
      boilerplate notice, with the fields enclosed by brackets "{}"
182
      replaced with your own identifying information. (Don't include
183
      the brackets!)  The text should be enclosed in the appropriate
184
      comment syntax for the file format. We also recommend that a
185
      file or class name and description of purpose be included on the
186
      same "printed page" as the copyright notice for easier
187
      identification within third-party archives.
188
189
   Copyright {yyyy} {name of copyright owner}
190
191
   Licensed under the Apache License, Version 2.0 (the "License");
192
   you may not use this file except in compliance with the License.
193
   You may obtain a copy of the License at
194
195
       http://www.apache.org/licenses/LICENSE-2.0
196
197
   Unless required by applicable law or agreed to in writing, software
198
   distributed under the License is distributed on an "AS IS" BASIS,
199
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
   See the License for the specific language governing permissions and
201
   limitations under the License.
1202
A docs/licenses/RENJIN.txt
1
		    GNU GENERAL PUBLIC LICENSE
2
		       Version 2, June 1991
3
4
 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5
                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
6
 Everyone is permitted to copy and distribute verbatim copies
7
 of this license document, but changing it is not allowed.
8
9
			    Preamble
10
11
  The licenses for most software are designed to take away your
12
freedom to share and change it.  By contrast, the GNU General Public
13
License is intended to guarantee your freedom to share and change free
14
software--to make sure the software is free for all its users.  This
15
General Public License applies to most of the Free Software
16
Foundation's software and to any other program whose authors commit to
17
using it.  (Some other Free Software Foundation software is covered by
18
the GNU Library General Public License instead.)  You can apply it to
19
your programs, too.
20
21
  When we speak of free software, we are referring to freedom, not
22
price.  Our General Public Licenses are designed to make sure that you
23
have the freedom to distribute copies of free software (and charge for
24
this service if you wish), that you receive source code or can get it
25
if you want it, that you can change the software or use pieces of it
26
in new free programs; and that you know you can do these things.
27
28
  To protect your rights, we need to make restrictions that forbid
29
anyone to deny you these rights or to ask you to surrender the rights.
30
These restrictions translate to certain responsibilities for you if you
31
distribute copies of the software, or if you modify it.
32
33
  For example, if you distribute copies of such a program, whether
34
gratis or for a fee, you must give the recipients all the rights that
35
you have.  You must make sure that they, too, receive or can get the
36
source code.  And you must show them these terms so they know their
37
rights.
38
39
  We protect your rights with two steps: (1) copyright the software, and
40
(2) offer you this license which gives you legal permission to copy,
41
distribute and/or modify the software.
42
43
  Also, for each author's protection and ours, we want to make certain
44
that everyone understands that there is no warranty for this free
45
software.  If the software is modified by someone else and passed on, we
46
want its recipients to know that what they have is not the original, so
47
that any problems introduced by others will not reflect on the original
48
authors' reputations.
49
50
  Finally, any free program is threatened constantly by software
51
patents.  We wish to avoid the danger that redistributors of a free
52
program will individually obtain patent licenses, in effect making the
53
program proprietary.  To prevent this, we have made it clear that any
54
patent must be licensed for everyone's free use or not licensed at all.
55
56
  The precise terms and conditions for copying, distribution and
57
modification follow.
58

59
		    GNU GENERAL PUBLIC LICENSE
60
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
62
  0. This License applies to any program or other work which contains
63
a notice placed by the copyright holder saying it may be distributed
64
under the terms of this General Public License.  The "Program", below,
65
refers to any such program or work, and a "work based on the Program"
66
means either the Program or any derivative work under copyright law:
67
that is to say, a work containing the Program or a portion of it,
68
either verbatim or with modifications and/or translated into another
69
language.  (Hereinafter, translation is included without limitation in
70
the term "modification".)  Each licensee is addressed as "you".
71
72
Activities other than copying, distribution and modification are not
73
covered by this License; they are outside its scope.  The act of
74
running the Program is not restricted, and the output from the Program
75
is covered only if its contents constitute a work based on the
76
Program (independent of having been made by running the Program).
77
Whether that is true depends on what the Program does.
78
79
  1. You may copy and distribute verbatim copies of the Program's
80
source code as you receive it, in any medium, provided that you
81
conspicuously and appropriately publish on each copy an appropriate
82
copyright notice and disclaimer of warranty; keep intact all the
83
notices that refer to this License and to the absence of any warranty;
84
and give any other recipients of the Program a copy of this License
85
along with the Program.
86
87
You may charge a fee for the physical act of transferring a copy, and
88
you may at your option offer warranty protection in exchange for a fee.
89
90
  2. You may modify your copy or copies of the Program or any portion
91
of it, thus forming a work based on the Program, and copy and
92
distribute such modifications or work under the terms of Section 1
93
above, provided that you also meet all of these conditions:
94
95
    a) You must cause the modified files to carry prominent notices
96
    stating that you changed the files and the date of any change.
97
98
    b) You must cause any work that you distribute or publish, that in
99
    whole or in part contains or is derived from the Program or any
100
    part thereof, to be licensed as a whole at no charge to all third
101
    parties under the terms of this License.
102
103
    c) If the modified program normally reads commands interactively
104
    when run, you must cause it, when started running for such
105
    interactive use in the most ordinary way, to print or display an
106
    announcement including an appropriate copyright notice and a
107
    notice that there is no warranty (or else, saying that you provide
108
    a warranty) and that users may redistribute the program under
109
    these conditions, and telling the user how to view a copy of this
110
    License.  (Exception: if the Program itself is interactive but
111
    does not normally print such an announcement, your work based on
112
    the Program is not required to print an announcement.)
113

114
These requirements apply to the modified work as a whole.  If
115
identifiable sections of that work are not derived from the Program,
116
and can be reasonably considered independent and separate works in
117
themselves, then this License, and its terms, do not apply to those
118
sections when you distribute them as separate works.  But when you
119
distribute the same sections as part of a whole which is a work based
120
on the Program, the distribution of the whole must be on the terms of
121
this License, whose permissions for other licensees extend to the
122
entire whole, and thus to each and every part regardless of who wrote it.
123
124
Thus, it is not the intent of this section to claim rights or contest
125
your rights to work written entirely by you; rather, the intent is to
126
exercise the right to control the distribution of derivative or
127
collective works based on the Program.
128
129
In addition, mere aggregation of another work not based on the Program
130
with the Program (or with a work based on the Program) on a volume of
131
a storage or distribution medium does not bring the other work under
132
the scope of this License.
133
134
  3. You may copy and distribute the Program (or a work based on it,
135
under Section 2) in object code or executable form under the terms of
136
Sections 1 and 2 above provided that you also do one of the following:
137
138
    a) Accompany it with the complete corresponding machine-readable
139
    source code, which must be distributed under the terms of Sections
140
    1 and 2 above on a medium customarily used for software interchange; or,
141
142
    b) Accompany it with a written offer, valid for at least three
143
    years, to give any third party, for a charge no more than your
144
    cost of physically performing source distribution, a complete
145
    machine-readable copy of the corresponding source code, to be
146
    distributed under the terms of Sections 1 and 2 above on a medium
147
    customarily used for software interchange; or,
148
149
    c) Accompany it with the information you received as to the offer
150
    to distribute corresponding source code.  (This alternative is
151
    allowed only for noncommercial distribution and only if you
152
    received the program in object code or executable form with such
153
    an offer, in accord with Subsection b above.)
154
155
The source code for a work means the preferred form of the work for
156
making modifications to it.  For an executable work, complete source
157
code means all the source code for all modules it contains, plus any
158
associated interface definition files, plus the scripts used to
159
control compilation and installation of the executable.  However, as a
160
special exception, the source code distributed need not include
161
anything that is normally distributed (in either source or binary
162
form) with the major components (compiler, kernel, and so on) of the
163
operating system on which the executable runs, unless that component
164
itself accompanies the executable.
165
166
If distribution of executable or object code is made by offering
167
access to copy from a designated place, then offering equivalent
168
access to copy the source code from the same place counts as
169
distribution of the source code, even though third parties are not
170
compelled to copy the source along with the object code.
171

172
  4. You may not copy, modify, sublicense, or distribute the Program
173
except as expressly provided under this License.  Any attempt
174
otherwise to copy, modify, sublicense or distribute the Program is
175
void, and will automatically terminate your rights under this License.
176
However, parties who have received copies, or rights, from you under
177
this License will not have their licenses terminated so long as such
178
parties remain in full compliance.
179
180
  5. You are not required to accept this License, since you have not
181
signed it.  However, nothing else grants you permission to modify or
182
distribute the Program or its derivative works.  These actions are
183
prohibited by law if you do not accept this License.  Therefore, by
184
modifying or distributing the Program (or any work based on the
185
Program), you indicate your acceptance of this License to do so, and
186
all its terms and conditions for copying, distributing or modifying
187
the Program or works based on it.
188
189
  6. Each time you redistribute the Program (or any work based on the
190
Program), the recipient automatically receives a license from the
191
original licensor to copy, distribute or modify the Program subject to
192
these terms and conditions.  You may not impose any further
193
restrictions on the recipients' exercise of the rights granted herein.
194
You are not responsible for enforcing compliance by third parties to
195
this License.
196
197
  7. If, as a consequence of a court judgment or allegation of patent
198
infringement or for any other reason (not limited to patent issues),
199
conditions are imposed on you (whether by court order, agreement or
200
otherwise) that contradict the conditions of this License, they do not
201
excuse you from the conditions of this License.  If you cannot
202
distribute so as to satisfy simultaneously your obligations under this
203
License and any other pertinent obligations, then as a consequence you
204
may not distribute the Program at all.  For example, if a patent
205
license would not permit royalty-free redistribution of the Program by
206
all those who receive copies directly or indirectly through you, then
207
the only way you could satisfy both it and this License would be to
208
refrain entirely from distribution of the Program.
209
210
If any portion of this section is held invalid or unenforceable under
211
any particular circumstance, the balance of the section is intended to
212
apply and the section as a whole is intended to apply in other
213
circumstances.
214
215
It is not the purpose of this section to induce you to infringe any
216
patents or other property right claims or to contest validity of any
217
such claims; this section has the sole purpose of protecting the
218
integrity of the free software distribution system, which is
219
implemented by public license practices.  Many people have made
220
generous contributions to the wide range of software distributed
221
through that system in reliance on consistent application of that
222
system; it is up to the author/donor to decide if he or she is willing
223
to distribute software through any other system and a licensee cannot
224
impose that choice.
225
226
This section is intended to make thoroughly clear what is believed to
227
be a consequence of the rest of this License.
228

229
  8. If the distribution and/or use of the Program is restricted in
230
certain countries either by patents or by copyrighted interfaces, the
231
original copyright holder who places the Program under this License
232
may add an explicit geographical distribution limitation excluding
233
those countries, so that distribution is permitted only in or among
234
countries not thus excluded.  In such case, this License incorporates
235
the limitation as if written in the body of this License.
236
237
  9. The Free Software Foundation may publish revised and/or new versions
238
of the General Public License from time to time.  Such new versions will
239
be similar in spirit to the present version, but may differ in detail to
240
address new problems or concerns.
241
242
Each version is given a distinguishing version number.  If the Program
243
specifies a version number of this License which applies to it and "any
244
later version", you have the option of following the terms and conditions
245
either of that version or of any later version published by the Free
246
Software Foundation.  If the Program does not specify a version number of
247
this License, you may choose any version ever published by the Free Software
248
Foundation.
249
250
  10. If you wish to incorporate parts of the Program into other free
251
programs whose distribution conditions are different, write to the author
252
to ask for permission.  For software which is copyrighted by the Free
253
Software Foundation, write to the Free Software Foundation; we sometimes
254
make exceptions for this.  Our decision will be guided by the two goals
255
of preserving the free status of all derivatives of our free software and
256
of promoting the sharing and reuse of software generally.
257
258
			    NO WARRANTY
259
260
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
262
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
266
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
267
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268
REPAIR OR CORRECTION.
269
270
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278
POSSIBILITY OF SUCH DAMAGES.
279
280
		     END OF TERMS AND CONDITIONS
281

282
	    How to Apply These Terms to Your New Programs
283
284
  If you develop a new program, and you want it to be of the greatest
285
possible use to the public, the best way to achieve this is to make it
286
free software which everyone can redistribute and change under these terms.
287
288
  To do so, attach the following notices to the program.  It is safest
289
to attach them to the start of each source file to most effectively
290
convey the exclusion of warranty; and each file should have at least
291
the "copyright" line and a pointer to where the full notice is found.
292
293
    <one line to give the program's name and a brief idea of what it does.>
294
    Copyright (C) <year>  <name of author>
295
296
    This program is free software; you can redistribute it and/or modify
297
    it under the terms of the GNU General Public License as published by
298
    the Free Software Foundation; either version 2 of the License, or
299
    (at your option) any later version.
300
301
    This program is distributed in the hope that it will be useful,
302
    but WITHOUT ANY WARRANTY; without even the implied warranty of
303
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
304
    GNU General Public License for more details.
305
306
    You should have received a copy of the GNU General Public License
307
    along with this program; if not, write to the Free Software
308
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
309
310
311
Also add information on how to contact you by electronic and paper mail.
312
313
If the program is interactive, make it output a short notice like this
314
when it starts in an interactive mode:
315
316
    Gnomovision version 69, Copyright (C) year name of author
317
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318
    This is free software, and you are welcome to redistribute it
319
    under certain conditions; type `show c' for details.
320
321
The hypothetical commands `show w' and `show c' should show the appropriate
322
parts of the General Public License.  Of course, the commands you use may
323
be called something other than `show w' and `show c'; they could even be
324
mouse-clicks or menu items--whatever suits your program.
325
326
You should also get your employer (if you work as a programmer) or your
327
school, if any, to sign a "copyright disclaimer" for the program, if
328
necessary.  Here is a sample; alter the names:
329
330
  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331
  `Gnomovision' (which makes passes at compilers) written by James Hacker.
332
333
  <signature of Ty Coon>, 1 April 1989
334
  Ty Coon, President of Vice
335
336
This General Public License does not permit incorporating your program into
337
proprietary programs.  If your program is a subroutine library, you may
338
consider it more useful to permit linking proprietary applications with the
339
library.  If this is what you want to do, use the GNU Library General
340
Public License instead of this License.
1341
A docs/licenses/RICH-TEXT-FX.md
1
Copyright (c) 2013-2017, Tomas Mikula and contributors
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
15
6
1. Redistributions of source code.
7
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR   IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS     
A docs/licenses/UNDO-FX.md
1
Copyright (c) 2014, TomasMikula
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without modification,
5
are permitted provided that the following conditions are met:
6
7
* Redistributions of source code
8
 .
9
10
*
11
 
12
 
13
14
15
16
17
(
18
19
20
121
A docs/licenses/WELL-BEHAVED-FX.md
1
Copyright (c) 2014, TomasMikula
2
All rights reserved.
3
4
Redistribution and use in source and binary forms, with or without
5
modification, are permitted provided that the following conditions are met:
6
7
* Redistributions of source code
8
 .
9
10
*
11
 
12
 
13
14
15
16
17
18
19
20
21
122
A docs/licenses/fonts/NOTO-CJK.md
1
URL: https://github.com/googlefonts/noto-cjk
2
3
Version: 1.002 or later
4
5
License: SIL Open Font License v1.1
6
7
License File: LICENSE
8
9
Note: prior releases of the CJK fonts were issued under the Apache 2
10
license. This was changed to the SIL OFL v1.1 starting with Version 1.002.
11
12
Description:
13
Noto CJK fonts, supporting Simplified Chinese, Traditional Chinese,
14
Japanese, and Korean. The supported scripts are Han, Hiragana, Katakana,
15
Hangul, and Bopomofo. Latin, Greek, Cyrillic, and various symbols are also
16
supported for compatibility with CJK standards.
17
18
The fonts in this directory are developed by Google and Adobe and are
19
released as open source under the Apache license version 2.0. The copyright
20
is held by Adobe, while the trademarks on the names are held by Google.
21
22
A README-formats file has been added explaining the different formats
23
provided and their features and limitations.
124
A docs/licenses/fonts/NOTO-SANS.md
11
2
                                 Apache License
3
                           Version 2.0, January 2004
4
                        http://www.apache.org/licenses/
5
6
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
8
   1. Definitions.
9
10
      "Licen[][yyyy] [name of copyright owner]
A docs/licenses/fonts/NOTO.md
1
Copyright 2018 The Noto Project Authors (https://github.com/googlei18n/noto-fonts)
2
3
This Font Software is licensed under the SIL Open Font License,
4
Version 1.1.
15
6
This license is cop
A docs/licenses/fonts/SOURCE-CODE-PRO.md
1
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
12
3
This Font Software is licensed under the SIL Open Font License, Version 
4
 
A docs/licenses/fonts/SOURCE-SERIF-4.md
1
Copyright 2014-2019 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
2
3
This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
5
This license is cop 
6
 creation
7
 provide a free and
8
 improved in partnership
9
10
to  components as
11
 deleting,
12
 components of the
13
 the Font Software to a
14
 modify,
15
16
in  corresponding
17
 primary font name as
118
19
using 
A docs/logo/font.txt
1
Merriweather Sans ExtraBold Italic
2
3
https://github.com/SorkinType/Merriweather-Sans/blob/master/fonts/otf/MerriweatherSans-ExtraBoldItalic.otf
4
5
Weight 800
6
7
https://fonts.google.com/specimen/Merriweather+Sans
8
19
A docs/logo/logo-original.svg
1
1
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
2
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1280" height="1024" viewBox="0 0 1280 1024" xml:space="preserve">
4
<desc>Created with Fabric.js 3.6.3</desc>
5
<defs>
6
</defs>
7
<g transform="matrix(1.9692780337941629 0 0 1.9692780337941629 640.0153846153846 512.012312418764)" id="background-logo"  >
8
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  x="-325" y="-260" rx="0" ry="0" width="650" height="520" />
9
</g>
10
<g transform="matrix(1.9692780337941629 0 0 1.9692780337941629 640.0170725174504 420.4016715831266)" id="logo-logo"  >
11
<g style=""  paint-order="stroke"   >
12
		<g transform="matrix(2.537 0 0 -2.537 -86.35385711719567 85.244912)"  >
13
<linearGradient id="SVGID_1_302284" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-24.348526 -27.478867 -27.478867 24.348526 138.479 129.67187)"  x1="0" y1="0" x2="1" y2="0">
14
<stop offset="0%" style="stop-color:rgb(245,132,41);stop-opacity: 1"/>
15
<stop offset="100%" style="stop-color:rgb(251,173,23);stop-opacity: 1"/>
16
</linearGradient>
17
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: url(#SVGID_1_302284); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(-127.92674550729492, -117.16399999999999)" d="m 118.951 124.648 c -9.395 -14.441 -5.243 -20.693 -5.243 -20.693 v 0 c 0 0 6.219 9.126 9.771 5.599 v 0 c 3.051 -3.023 -2.415 -8.668 -2.415 -8.668 v 0 c 0 0 33.24 13.698 17.995 28.872 v 0 c 0 0 -3.203 3.683 -7.932 3.684 v 0 c -3.46 0 -7.736 -1.97 -12.176 -8.794" stroke-linecap="round" />
18
</g>
19
		<g transform="matrix(2.537 0 0 -2.537 -84.52085711719567 70.2729119999999)"  >
20
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(250,220,153); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(11.9895, -1.2609990716440347)" d="m 0 0 c 0 0 -6.501 6.719 -11.093 5.443 c -5.584 -1.545 -12.886 -12.078 -12.886 -12.078 c 0 0 5.98 16.932 15.29 15.731 C -1.19 8.127 0 0 0 0" stroke-linecap="round" />
21
</g>
22
		<g transform="matrix(2.537 0 0 -2.537 -22.327857117195663 48.729911999999956)"  >
23
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(201,158,82); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(-4.189, -10.432)" d="m 0 0 l -0.87 16.89 l 3.995 3.974 l 6.123 -6.156 z" stroke-linecap="round" />
24
</g>
25
		<g transform="matrix(2.537 0 0 -2.537 -11.3118571171957 24.124911999999966)"  >
26
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(201,158,82); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(4.0955, -2.037)" d="m 0 0 l -2.081 -2.069 l -6.11 6.143 l 2.081 2.069 z" stroke-linecap="round" />
27
</g>
28
		<g transform="matrix(2.537 0 0 -2.537 46.27614288280432 -57.96708800000005)"  >
29
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(217,170,93); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(12.070999999999998, 9.599000000000004)" d="m 0 0 c -1.226 0.69 -2.81 0.523 -3.862 -0.524 c -1.275 -1.268 -1.28 -3.33 -0.013 -4.604 l -31.681 -31.501 l -6.11 6.143 c 19.224 19.305 25.369 35.582 25.369 35.582 c 15.857 2.364 27.851 8.624 33.821 12.335 z" stroke-linecap="round" />
30
</g>
31
		<g transform="matrix(2.537 0 0 -2.537 -26.842857117195706 8.501911999999976)"  >
32
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(217,170,93); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(4.1075, -2.0525)" d="M 0 0 L -2.081 -2.069 L -8.215 4.11 L -6.141 6.174 Z" stroke-linecap="round" />
33
</g>
34
		<g transform="matrix(2.537 0 0 -2.537 -51.495857117195726 19.491911999999985)"  >
35
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(217,170,93); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(10.434000000000001, -1.0939999999999994)" d="m 0 0 l -3.995 -3.974 l -16.873 0.96 l 14.752 9.176 z" stroke-linecap="round" />
36
</g>
37
		<g transform="matrix(2.537 0 0 -2.537 55.72014288280434 -48.441088000000036)"  >
38
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(201,158,82); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(9.671499999999998, 11.999499999999998)" d="M 0 0 L 17.536 17.443 C 13.788 11.486 7.47 -0.468 5.021 -16.312 c 0 0 -15.526 -6.982 -35.765 -25.13 l -6.135 6.168 l 31.681 31.5 c 1.273 -1.28 3.33 -1.279 4.604 -0.012 C 0.435 -2.764 0.629 -1.223 0 0" stroke-linecap="round" />
39
</g>
40
</g>
41
</g>
42
<g transform="matrix(1.9692780337941629 0 0 1.9692780337941629 643.7363123827618 766.1975713477327)" id="text-logo-path"  >
43
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(247,149,33); fill-rule: nonzero; opacity: 1;"  paint-order="stroke"  transform=" translate(-186.83999999999997, 27.08)" d="M 4.47 -6.1 L 4.47 -6.1 L 4.47 -47.5 Q 4.47 -50.27 6.43 -52.23 Q 8.39 -54.19 11.16 -54.19 L 11.16 -54.19 Q 14.01 -54.19 15.95 -52.23 Q 17.89 -50.27 17.89 -47.5 L 17.89 -47.5 L 17.89 -30.09 L 34.95 -51.97 Q 35.74 -52.97 36.94 -53.58 Q 38.13 -54.19 39.42 -54.19 L 39.42 -54.19 Q 41.77 -54.19 43.42 -52.5 Q 45.07 -50.82 45.07 -48.5 L 45.07 -48.5 Q 45.07 -46.46 43.82 -44.93 L 43.82 -44.93 L 32.93 -31.44 L 46.8 -9.81 Q 47.84 -8.11 47.84 -6.27 L 47.84 -6.27 Q 47.84 -3.33 45.9 -1.39 Q 43.96 0.55 41.19 0.55 L 41.19 0.55 Q 39.42 0.55 37.89 -0.29 Q 36.37 -1.14 35.43 -2.57 L 35.43 -2.57 L 23.78 -21.15 L 17.89 -13.9 L 17.89 -6.1 Q 17.89 -3.33 15.93 -1.39 Q 13.97 0.55 11.16 0.55 L 11.16 0.55 Q 8.39 0.55 6.43 -1.39 Q 4.47 -3.33 4.47 -6.1 Z M 50.27 -19.24 L 50.27 -19.24 Q 50.27 -25.13 52.71 -29.78 Q 55.16 -34.43 59.7 -37.06 Q 64.24 -39.69 70.27 -39.69 L 70.27 -39.69 Q 76.37 -39.69 80.78 -37.09 Q 85.18 -34.49 87.43 -30.32 Q 89.69 -26.14 89.69 -21.6 L 89.69 -21.6 Q 89.69 -18.69 88.33 -17.26 Q 86.98 -15.84 83.86 -15.84 L 83.86 -15.84 L 62.89 -15.84 Q 63.23 -12.38 65.38 -10.31 Q 67.53 -8.25 70.86 -8.25 L 70.86 -8.25 Q 72.84 -8.25 74.19 -8.91 Q 75.54 -9.57 76.62 -10.64 L 76.62 -10.64 Q 77.62 -11.58 78.42 -12.03 Q 79.22 -12.48 80.43 -12.48 L 80.43 -12.48 Q 82.61 -12.48 84.19 -10.89 Q 85.77 -9.29 85.77 -7.04 L 85.77 -7.04 Q 85.77 -4.54 83.62 -2.77 L 83.62 -2.77 Q 81.71 -1.14 78.16 -0.03 Q 74.61 1.07 70.58 1.07 L 70.58 1.07 Q 64.76 1.07 60.13 -1.42 Q 55.5 -3.92 52.89 -8.53 Q 50.27 -13.14 50.27 -19.24 Z M 62.96 -23.57 L 62.96 -23.57 L 76.96 -23.57 Q 76.82 -26.97 74.93 -28.97 Q 73.05 -30.96 70.06 -30.96 L 70.06 -30.96 Q 67.08 -30.96 65.21 -28.97 Q 63.34 -26.97 62.96 -23.57 Z M 91.63 -19.24 L 91.63 -19.24 Q 91.63 -25.13 94.07 -29.78 Q 96.52 -34.43 101.06 -37.06 Q 105.6 -39.69 111.63 -39.69 L 111.63 -39.69 Q 117.73 -39.69 122.14 -37.09 Q 126.54 -34.49 128.79 -30.32 Q 131.04 -26.14 131.04 -21.6 L 131.04 -21.6 Q 131.04 -18.69 129.69 -17.26 Q 128.34 -15.84 125.22 -15.84 L 125.22 -15.84 L 104.25 -15.84 Q 104.59 -12.38 106.74 -10.31 Q 108.89 -8.25 112.22 -8.25 L 112.22 -8.25 Q 114.2 -8.25 115.55 -8.91 Q 116.9 -9.57 117.98 -10.64 L 117.98 -10.64 Q 118.98 -11.58 119.78 -12.03 Q 120.58 -12.48 121.79 -12.48 L 121.79 -12.48 Q 123.97 -12.48 125.55 -10.89 Q 127.13 -9.29 127.13 -7.04 L 127.13 -7.04 Q 127.13 -4.54 124.98 -2.77 L 124.98 -2.77 Q 123.07 -1.14 119.52 -0.03 Q 115.96 1.07 111.94 1.07 L 111.94 1.07 Q 106.12 1.07 101.49 -1.42 Q 96.86 -3.92 94.24 -8.53 Q 91.63 -13.14 91.63 -19.24 Z M 104.32 -23.57 L 104.32 -23.57 L 118.32 -23.57 Q 118.18 -26.97 116.29 -28.97 Q 114.4 -30.96 111.42 -30.96 L 111.42 -30.96 Q 108.44 -30.96 106.57 -28.97 Q 104.7 -26.97 104.32 -23.57 Z M 135.03 -6.03 L 135.03 -6.03 L 135.03 -33.14 Q 135.03 -35.64 136.85 -37.46 Q 138.67 -39.28 141.13 -39.28 L 141.13 -39.28 Q 143.7 -39.28 145.52 -37.46 Q 147.34 -35.64 147.34 -33.14 L 147.34 -33.14 L 147.34 -32.17 Q 148.97 -35.36 152.09 -37.42 Q 155.21 -39.49 159.82 -39.49 L 159.82 -39.49 Q 166.93 -39.49 170.19 -35.47 Q 173.44 -31.44 173.44 -24.44 L 173.44 -24.44 L 173.44 -6.03 Q 173.44 -3.33 171.5 -1.39 Q 169.56 0.55 166.86 0.55 L 166.86 0.55 Q 164.15 0.55 162.19 -1.39 Q 160.24 -3.33 160.24 -6.03 L 160.24 -6.03 L 160.24 -22.36 Q 160.24 -26.35 158.54 -27.91 Q 156.84 -29.47 154.65 -29.47 L 154.65 -29.47 Q 152.02 -29.47 150.13 -27.58 Q 148.24 -25.69 148.24 -20.73 L 148.24 -20.73 L 148.24 -6.03 Q 148.24 -3.33 146.3 -1.39 Q 144.36 0.55 141.65 0.55 L 141.65 0.55 Q 138.95 0.55 136.99 -1.39 Q 135.03 -3.33 135.03 -6.03 Z M 177.71 -47.56 L 177.71 -47.56 Q 177.71 -50.34 179.63 -52.26 Q 181.56 -54.19 184.23 -54.19 L 184.23 -54.19 Q 186.58 -54.19 188.39 -52.73 Q 190.19 -51.27 190.71 -48.99 L 190.71 -48.99 L 197.88 -15.12 L 206.52 -48.64 Q 207.07 -51.07 209.12 -52.63 Q 211.16 -54.19 213.69 -54.19 L 213.69 -54.19 Q 216.26 -54.19 218.25 -52.57 Q 220.25 -50.96 220.8 -48.64 L 220.8 -48.64 L 229.4 -15.39 L 236.64 -49.33 Q 237.06 -51.38 238.76 -52.78 Q 240.46 -54.19 242.61 -54.19 L 242.61 -54.19 Q 245.17 -54.19 246.94 -52.4 Q 248.71 -50.62 248.71 -48.05 L 248.71 -48.05 Q 248.71 -47.56 248.57 -46.73 L 248.57 -46.73 L 239.69 -7.38 Q 238.9 -3.99 236.11 -1.72 Q 233.32 0.55 229.68 0.55 L 229.68 0.55 Q 226.14 0.55 223.37 -1.61 Q 220.59 -3.78 219.73 -7.11 L 219.73 -7.11 L 213.07 -33.45 L 206.38 -7.11 Q 205.51 -3.71 202.79 -1.58 Q 200.07 0.55 196.53 0.55 L 196.53 0.55 Q 192.89 0.55 190.17 -1.72 Q 187.45 -3.99 186.65 -7.38 L 186.65 -7.38 L 177.85 -46.14 Q 177.71 -47.15 177.71 -47.56 Z M 253.35 -6.03 L 253.35 -6.03 L 253.35 -33.14 Q 253.35 -35.64 255.17 -37.46 Q 256.99 -39.28 259.46 -39.28 L 259.46 -39.28 Q 262.02 -39.28 263.84 -37.46 Q 265.66 -35.64 265.66 -33.14 L 265.66 -33.14 L 265.66 -31.44 L 265.94 -31.44 Q 266.8 -33.56 268.1 -35.24 Q 269.4 -36.92 270.69 -37.61 L 270.69 -37.61 Q 271.9 -38.24 273.46 -38.27 L 273.46 -38.27 Q 276.65 -38.27 278.14 -36.45 Q 279.63 -34.63 279.63 -32.52 L 279.63 -32.52 Q 279.63 -30.33 278.11 -28.62 Q 276.58 -26.9 274.08 -26.9 L 274.08 -26.9 Q 272.59 -26.9 271.07 -26.26 Q 269.54 -25.62 268.47 -24.34 L 268.47 -24.34 Q 266.56 -21.98 266.56 -17.68 L 266.56 -17.68 L 266.56 -6.03 Q 266.56 -3.33 264.62 -1.39 Q 262.68 0.55 259.98 0.55 L 259.98 0.55 Q 257.27 0.55 255.31 -1.39 Q 253.35 -3.33 253.35 -6.03 Z M 282.41 -49.71 L 282.41 -49.71 Q 282.41 -52 284.03 -53.61 Q 285.66 -55.23 287.95 -55.23 L 287.95 -55.23 L 291.21 -55.23 Q 293.5 -55.23 295.13 -53.6 Q 296.76 -51.97 296.76 -49.71 L 296.76 -49.71 Q 296.76 -47.43 295.11 -45.8 Q 293.46 -44.17 291.21 -44.17 L 291.21 -44.17 L 287.95 -44.17 Q 285.66 -44.17 284.03 -45.8 Q 282.41 -47.43 282.41 -49.71 Z M 282.96 -6.03 L 282.96 -6.03 L 282.96 -32.66 Q 282.96 -35.36 284.92 -37.32 Q 286.88 -39.28 289.58 -39.28 L 289.58 -39.28 Q 292.29 -39.28 294.23 -37.32 Q 296.17 -35.36 296.17 -32.66 L 296.17 -32.66 L 296.17 -6.03 Q 296.17 -3.33 294.21 -1.39 Q 292.25 0.55 289.58 0.55 L 289.58 0.55 Q 286.88 0.55 284.92 -1.39 Q 282.96 -3.33 282.96 -6.03 Z M 299.43 -34.29 L 299.43 -34.29 Q 299.43 -36.12 300.71 -37.41 Q 301.99 -38.69 303.76 -38.69 L 303.76 -38.69 L 306.19 -38.69 L 306.46 -43.96 Q 306.6 -46.32 308.34 -47.98 Q 310.07 -49.64 312.5 -49.64 L 312.5 -49.64 Q 314.99 -49.64 316.76 -47.86 Q 318.53 -46.07 318.53 -43.58 L 318.53 -43.58 L 318.53 -38.69 L 322.72 -38.69 Q 324.49 -38.69 325.77 -37.41 Q 327.06 -36.12 327.06 -34.36 L 327.06 -34.36 Q 327.06 -32.52 325.77 -31.24 Q 324.49 -29.95 322.72 -29.95 L 322.72 -29.95 L 318.81 -29.95 L 318.81 -14.14 Q 318.81 -11.23 320.05 -10.02 Q 321.3 -8.81 323.83 -8.81 L 323.83 -8.81 Q 325.46 -8.46 326.61 -7.14 Q 327.75 -5.82 327.75 -4.06 L 327.75 -4.06 Q 327.75 -2.57 326.94 -1.39 Q 326.12 -0.21 324.84 0.35 L 324.84 0.35 Q 322 0.83 318.11 0.87 L 318.11 0.87 Q 311.28 0.9 308.44 -2.5 L 308.44 -2.5 Q 305.67 -5.79 305.67 -12.65 L 305.67 -12.65 Q 305.67 -12.83 305.67 -13 L 305.67 -13 L 305.74 -29.95 L 303.76 -29.95 Q 301.99 -29.95 300.71 -31.24 Q 299.43 -32.52 299.43 -34.29 Z M 329.8 -19.24 L 329.8 -19.24 Q 329.8 -25.13 332.24 -29.78 Q 334.68 -34.43 339.23 -37.06 Q 343.77 -39.69 349.8 -39.69 L 349.8 -39.69 Q 355.9 -39.69 360.3 -37.09 Q 364.71 -34.49 366.96 -30.32 Q 369.21 -26.14 369.21 -21.6 L 369.21 -21.6 Q 369.21 -18.69 367.86 -17.26 Q 366.51 -15.84 363.39 -15.84 L 363.39 -15.84 L 342.42 -15.84 Q 342.76 -12.38 344.91 -10.31 Q 347.06 -8.25 350.39 -8.25 L 350.39 -8.25 Q 352.37 -8.25 353.72 -8.91 Q 355.07 -9.57 356.14 -10.64 L 356.14 -10.64 Q 357.15 -11.58 357.95 -12.03 Q 358.74 -12.48 359.96 -12.48 L 359.96 -12.48 Q 362.14 -12.48 363.72 -10.89 Q 365.3 -9.29 365.3 -7.04 L 365.3 -7.04 Q 365.3 -4.54 363.15 -2.77 L 363.15 -2.77 Q 361.24 -1.14 357.69 -0.03 Q 354.13 1.07 350.11 1.07 L 350.11 1.07 Q 344.29 1.07 339.66 -1.42 Q 335.03 -3.92 332.41 -8.53 Q 329.8 -13.14 329.8 -19.24 Z M 342.48 -23.57 L 342.48 -23.57 L 356.49 -23.57 Q 356.35 -26.97 354.46 -28.97 Q 352.57 -30.96 349.59 -30.96 L 349.59 -30.96 Q 346.61 -30.96 344.74 -28.97 Q 342.87 -26.97 342.48 -23.57 Z" stroke-linecap="round" />
44
</g>
45
</svg>
A docs/logo/logo-text.svg
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<svg
3
   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
   xmlns:cc="http://creativecommons.org/ns#"
5
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
   xmlns:svg="http://www.w3.org/2000/svg"
7
   xmlns="http://www.w3.org/2000/svg"
8
   xmlns:xlink="http://www.w3.org/1999/xlink"
9
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
   height="197.4767"
12
   viewBox="0 0 695.99768 197.4767"
13
   width="695.99768"
14
   version="1.1"
15
   id="svg37"
16
   sodipodi:docname="new-logo-text.svg"
17
   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
18
  <metadata
19
     id="metadata43">
20
    <rdf:RDF>
21
      <cc:Work
22
         rdf:about="">
23
        <dc:format>image/svg+xml</dc:format>
24
        <dc:type
25
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
26
        <dc:title></dc:title>
27
      </cc:Work>
28
    </rdf:RDF>
29
  </metadata>
30
  <defs
31
     id="defs41">
32
    <linearGradient
33
       id="a"
34
       gradientTransform="matrix(-8.7796153,42.985832,-42.985832,-8.7796153,514.83476,136.06192)"
35
       gradientUnits="userSpaceOnUse"
36
       x1=".152358"
37
       x2=".968809"
38
       y1="-.044912"
39
       y2="-.049471">
40
      <stop
41
         offset="0"
42
         stop-color="#ec706a"
43
         id="stop2" />
44
      <stop
45
         offset="1"
46
         stop-color="#ecd980"
47
         id="stop4" />
48
    </linearGradient>
49
  </defs>
50
  <path
51
     style="fill:url(#a);fill-opacity:1.0;fill-rule:nonzero;stroke:none;stroke-width:1.226;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
52
     paint-order="stroke"
53
     d="m 496.76229,150.80474 c -4.25368,20.68081 3.28191,25.95476 3.28191,25.95476 v 0 c 0,0 3.00963,-13.19543 8.64082,-10.76172 v 0 c 4.83401,2.08299 1.12516,10.97002 1.12516,10.97002 v 0 c 0,0 31.78993,-30.5076 7.60484,-40.99434 v 0 c 0,0 -5.30287,-2.76791 -10.69842,-0.65209 v 0 c -3.94735,1.54891 -7.94375,5.71058 -9.95431,15.48337"
54
     stroke-linecap="round"
55
     id="path14" />
56
  <path
57
     d="m 530.80335,138.63592 -10.99206,-16.95952 1.75995,-6.49966 10.01483,2.71233 z"
58
     fill="#126d95"
59
     id="path9" />
60
  <path
61
     d="m 533.0598,112.36676 -0.91739,3.38458 -9.99361,-2.70665 0.91739,-3.38458 z"
62
     fill="#126d95"
63
     id="path11" />
64
  <g
65
     fill="#51a9cf"
66
     id="g19"
67
     transform="translate(-295.50101,-692.52836)">
68
    <path
69
       d="m 834.01973,741.0381 c -1.68105,0.0185 -3.22054,1.13771 -3.68367,2.84981 -0.56186,2.07405 0.665,4.21099 2.73743,4.77241 l -13.96475,51.52944 -9.99361,-2.70665 c 8.36013,-31.46487 4.99411,-51.98144 4.99411,-51.98144 14.99782,-11.92097 23.67,-25.56577 27.63101,-32.97331 z"
70
       id="path13" />
71
    <path
72
       d="m 818.56767,802.18881 -0.9174,3.38458 -10.03996,-2.72957 0.91314,-3.37522 z"
73
       id="path15" />
74
    <path
75
       d="m 817.07405,807.70594 -1.75995,6.49966 -18.03534,9.08805 9.78412,-18.31044 z"
76
       id="path17" />
77
  </g>
78
  <path
79
     d="m 540.69709,49.12083 7.72577,-28.52932 c -0.3195,8.40427 0.28451,24.55036 7.21678,42.41047 0,0 -11.89603,16.50235 -21.99788,47.3763 l -10.03442,-2.71758 13.96533,-51.5284 c 2.08221,0.56405 4.21039,-0.66603 4.77182,-2.73844 0.45427,-1.67248 -0.26571,-3.38317 -1.64739,-4.27302"
80
     fill="#126d95"
81
     id="path21" />
82
  <text
83
     transform="translate(-295.73751 -689.6407)"
84
     id="text25" />
85
  <g
86
     style="font-style:italic;font-weight:800;font-size:133.333;font-family:Merriweather Sans;letter-spacing:0;word-spacing:0;fill:#51a9cf"
87
     id="g35">
88
    <text
89
       x="16.133343"
90
       y="130.6234"
91
       id="text29"><tspan
92
         x="16.133343"
93
         y="130.6234"
94
         id="tspan27">KeenWr</tspan></text>
95
    <text
96
       x="552.53137"
97
       y="130.6234"
98
       id="text33"><tspan
99
         x="552.53137"
100
         y="130.6234"
101
         id="tspan31">te</tspan></text>
102
  </g>
103
</svg>
1104
A docs/logo/logo-text.zh-CN.svg
1
1
<svg height="197.4767" viewBox="0 0 493.25561 197.4767" width="493.25562" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientTransform="matrix(-8.7796153 42.985832 -42.985832 -8.7796153 810.33577 8312.52749 -472.07353)"/><text fill="#51a9cf" font-family="'Noto Serif CJK SC'" font-size="35.1025" letter-spacing="0" transform="matrix(3.7983969 0 0 3.7983969 -330.7653 961.00598)" word-spacing="0"><tspan x="91.011719" y="-209.05206"><tspan x="91.011719" y="-209.05206">智能写<tspan fill="#51a9cf"/></tspan></tspan><tspan x="91.011719" y="-165.17393"/></text><g transform="tran377.88503/svg>
A docs/logo/palette.txt
11
2
Blues
3
  Light - 51a9cf
4
  Dark - 126d95
5
6
Red & Yellow
7
  Light yellow - ecd980
8
  Light red - ec706a
9
  Dark red - 7e252f
10
11
Greens
12
  Light - 76A786
13
  Dark - 385742
14
15
Grayscale
16
  Light - bac2c5
17
  Dark - 394343
18
19
A docs/metadata.md
1
# Document metadata
2
3
Document metadata is information about a document. Metadata often includes
4
a title, author name, copyright date, and keywords.
5
6
# Custom metadata
7
8
The following screenshot shows example metadata preferences:
9
10
![Metadata screenshot](images/screenshots/09.png)
11
12
The **Key** column lists metadata names and the **Value** column lists
13
the metadata content for each corresponding **Key**. The content may
14
include references to variable definitions. When the document is typeset,
15
the values for the variables will be substituted upon export.
16
17
When the document is exported as XHTML, the header will include the
18
keys and values conforming to the XHTML specification. For example:
19
20
``` html
21
<head>
22
  <title>Document Title</title>
23
  <meta content="science, nature" name="keywords"/>
24
  <meta content="Penn Surnom" name="author"/>
25
  <meta content="4311" name="count"/>
26
</head>
27
```
28
29
# Special metadata
30
31
When exporting the document, note the following special metadata:
32
33
* **author** -- Included as PDF metadata
34
* **byline** -- Replaces author in PDF metadata (e.g., for pen names)
35
* **count** -- Total word count in document, automatically included
36
* **keywords** -- Included as PDF metadata
37
* **title** -- Included as a `<title>` tag, rather than a `<meta>` tag
38
139
A docs/r/INSTALL.md
1
# R Functions
2
3
Import the files in this directory into the application, which include:
4
5
* bootstrap.R
6
* pluralize.R
7
* possessive.R
8
* conversion.R
9
* csv.R
10
11
# bootstrap.R
12
13
Copy the contents of `bootstrap.R` into the R script preferences, shown in the
14
following figure, then restart the application:
15
16
# ![Bootstrap](images/bootstrap.png)
17
18
Setting the **Working Directory** allows the startup script to load files
19
using a relative to said directory.
20
21
# pluralize.R
22
23
This file defines a function that implements most of Damian Conway's [An Algorithmic Approach to English Pluralization](http://blob.perl.org/tpc/1998/User_Applications/Algorithmic%20Approach%20Plurals/Algorithmic_Plurals.html).
24
25
## Usage
26
27
Example usages of the pluralize function include:
28
29
    `r#pluralize( "mouse" )` - mice
30
    `r#pluralize( "buzz" )` - buzzes
31
    `r#pluralize( "bus" )` - buses
32
33
# possessive.R
34
35
This file defines a function that applies possessives to English words.
36
37
## Usage
38
39
Example usages of the possessive function include:
40
41
    `r#pos( "Ross" )` - Ross'
42
    `r#pos( "Ruby" )` - Ruby's
43
    `r#pos( "Lois" )` - Lois'
44
    `r#pos( "my" )` - mine
45
    `r#pos( "Your" )` - Yours
46
147
A docs/r/README.md
1
# R Scripts
12
3
These scripts illustrate how R can perform calculations using variables, to help automate repetitive tasks. Authors are free to write their own scripts date:
4
 calculate a total for all numeric columnsx =math$x )`;
5
    y =;
6
    z =
A docs/r/images/bootstrap.png
Binary file
A docs/r.md
1
# Introduction
2
3
This document describes how to use the [R](https://www.r-project.org/)
4
programming language from within the application. The application uses an
5
interpreter known as [Renjin](https://www.renjin.org/) to integrate with R.
6
7
# Hello world
18
9
Cov$application$r$working$directory );
10
    source( "library.R"v$application$r$working$directory
A docs/references.md
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 with
8
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 has been implemented.
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 preceding it. The
22
remainder of this section provides examples.
23
24
### Images
25
26
An image caption:
27
28
```
29
![image title](https://loremflickr.com/600/350)
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 or
71
ideogram. Type names are user-defined categories for the item type. Labels
72
are user-defined identifiers that must be unique per item.
73
74
Consider the following example:
75
76
```
77
In [@fig:animal], a cute animal is shown.
78
79
![image title](https://loremflickr.com/600/350)
80
81
:: World's cutest animal {#fig:animal}
82
83
There is no cuter animal than the one in [@fig:animal].
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.
1128
A docs/samples/korean.md
1
*Song of the Yellow Bird*:
2
3
	翩翩黃鳥,
4
	雌雄相依。
5
	念我之獨,
6
	誰其與歸?
7
8
English translation:
9
    	
10
	Orioles fly smoothly
11
	Female and male cuddle close together
12
	Thinking of my loneliness
13
	Whom shall I go with?
14
15
Fonts:
16
17
* Regular: 활판 인쇄술
18
* Bold: **활판 인쇄술**
19
* Monospace: `활판 인쇄술`
20
* Monospace bold: **`활판 인쇄술`**
21
* Math: $E=mc^2$
22
123
A docs/samples/math.yaml
1
---
2
formula:
3
  sqrt:
4
    value: "420"
5
  quadratic:
6
    a: "25"
7
    b: "84.906"
8
    c: "20"
19
A docs/samples/quadratic.Rmd
1
![Logo](../images/app-title)
2
3
Given the quadratic formula:
4
5
$x = \frac{-b \pm \sqrt{b^2 -4ac}}{2a}$
6
7
Formatted in an R Markdown document as follows:
8
9
    $x = \frac{-b \pm \sqrt{b^2 -4ac}}{2a}$
10
11
We can substitute the following values:
12
13
$a = `r# x(v$formula$quadratic$a)`, b = `r# x(v$formula$quadratic$b)`, c = `r# x(v$formula$quadratic$c)`$
14
15
`r# -x(v$formula$quadratic$b) + sqrt( v$formula$quadratic$b^2  - 4 * v$formula$quadratic$a * v$formula$quadratic$c )`
16
17
To arrive at two solutions:
18
19
$x = \frac{-b + \sqrt{b^2 -4ac}}{2a} = `r# (-x(v$formula$quadratic$b) + sqrt( x(v$formula$quadratic$b)^2  - 4 * x(v$formula$quadratic$a) * x(v$formula$quadratic$c) )) / (2 * x(v$formula$quadratic$a))`$
20
21
$x = \frac{-b - \sqrt{b^2 -4ac}}{2a} = `r# (-x(v$formula$quadratic$b) - sqrt( x(v$formula$quadratic$b)^2  - 4 * x(v$formula$quadratic$a) * x(v$formula$quadratic$c) )) / (2 * x(v$formula$quadratic$a))`$
22
23
Changing the variable values is reflected in the output immediately.
124
A docs/samples/tex.Rmd
1
# ![Logo](../images/app-title.png)
2
3
# Real-time equation rendering
4
5
Interpolated variables within R calculations, formatted as an equation:
6
7
$\sqrt{`r#x( v$formula$sqrt$value)`} = \pm `r# round(sqrt(x( v$formula$sqrt$value )),5)`$
8
9
# Maxwell's equations
10
11
$rot \vec{E} = \frac{1}{c} \frac{\partial{\vec{B}}}{\partial t}, div \vec{B} = 0$
12
13
$rot \vec{B} = \frac{1}{c} \frac{\partial{\vec{E}}}{\partial t} + \frac{4\pi}{c} \vec{j}, div \vec{E} = 4 \pi \rho_{\varepsilon}$
14
15
# Time-dependent Schrödinger equation
16
17
$- \frac{{\hbar ^2 }}{{2m}}\frac{{\partial ^2 \psi (x,t)}}{{\partial x^2 }} + U(x)\psi (x,t) = i\hbar \frac{{\partial \psi (x,t)}}{{\partial t}}$
18
19
# Discrete-time Fourier transforms
20
21
Unit step function: $u(n) \Leftrightarrow \frac{1}{1-e^{-jw}} + \sum_{k=-\infty}^{\infty} \pi \delta (\omega + 2\pi k)$
22
23
Shifted delta: $\delta (n - n_o ) \Leftrightarrow e^{ - j\omega n_o }$
24
25
# Faraday's Law
26
27
$\oint_C {E \cdot d\ell  =  - \frac{d}{{dt}}} \int_S {B_n dA}$
28
29
# Infinite series
30
31
$sin(x) = \sum_{n = 1}^{\infty}  {\frac{{( { - 1})^{n - 1} x^{2n - 1} }}{{( {2n - 1})!}}}$
32
33
# Magnetic flux
34
35
$\phi _m  = \int_S {N{{B}} \cdot {{\hat n}}dA = } \int_S {NB_n dA}$
36
37
# Driven oscillation amplitude
38
39
$A = \frac{{F_0 }}{{\sqrt {m^2 ( {\omega _0^2  - \omega ^2 } )^2  + b^2 \omega ^2 } }}$
40
41
# Optics
42
43
$\phi  = \frac{{2\pi }}{\lambda }a sin(\theta)$
144
A docs/screenshots.md
1
# Variables
2
3
Diagrams that include variables:
4
5
![GraphViz diagram screenshot](images/screenshots/01.png)
6
7
![Family tree diagram screenshot](images/screenshots/05.png)
8
9
# PDF themes
10
11
In the background of the following screenshot, the editor shows a novel
12
being edited:
13
14
![PDF themes](images/screenshots/08.png)
15
16
Highlighted items of note:
17
18
* PDF icon in the upper-left
19
* Novel metadata as integrated variables towards the top-left
20
* Theme selection dialog in the upper-middle
21
* Three different styles, including:
22
    * Boschet, based on Baskerville font, nicely styled
23
    * Handrit, based on Courier font, double-spaced, manuscript format
24
    * Tarmes, based on Times Roman font, minimal styling
25
* Variations in page numbers
26
* Manuscript includes word count, automatically
27
* Preferences dialog in the middle
28
29
# Internationalization
30
31
Poem with locale settings:
32
33
![Korean poem screenshot](images/screenshots/02.png)
34
35
# Equations
36
37
TeX equations with detached preview:
38
39
![TeX equations screenshot](images/screenshots/03.png)
40
41
# Dockable tabs
42
43
Document outline opened and docked in bottom-left corner:
44
45
![Document outline](images/screenshots/04.png)
46
147
A docs/skins.md
1
# Skins
2
3
The application provides bundled skins and the ability to add custom
4
skins. This document describes the interplay between bundled skins
5
and building your own look and feel.
6
7
A skin is a set of styles, similar to cascading style sheet classes,
8
that configures the user interface colours, fonts, spacing, highlights,
9
drop-shadows, gradients, and more.
10
11
For more information on CSS, see the [W3C CSS tutorial](https://www.w3.org/Style/Examples/011/firstcss).
12
13
# Order
14
15
The order that stylesheets are applied matters so that stylesheets can
16
override styles defined previously. The application's user interface
17
is made up of the following stylesheets, applied in the order listed:
18
19
* **scene.css** --- Defines toolbar styling.
20
* **markdown.css** --- Defines text editor styling.
21
* **skins/skin_name.css** --- Bundled skin selected in preferences.
22
* **custom.css** --- User-defined file set in preferences.
23
24
# Customization
25
26
Create a custom skin as follows:
27
28
1. Start the application.
29
1. Click **File → New** to create a new file.
30
1. Click **File → Save As** to rename the file.
31
1. Save the file as `custom.css`.
32
1. Change the content to the following:
33
``` css
34
.root {
35
  -fx-base: rgb( 30, 30, 30 );
36
  -fx-background: -fx-base;
37
}
38
```
39
40
Next, apply the skin as follows:
41
42
1. Click **Edit → Preferences** to open the preferences dialog.
43
1. Click **Skins** to view the available options.
44
1. Click **Browse** to select a custom file.
45
1. Browse to and select `custom.css`, created previously.
46
1. Click **Open**.
47
1. Click **Apply**.
48
49
The user interface immediately changes to a dark mode. Continue:
50
51
1. Click **OK** to close the dialog.
52
1. Change the **rgb** numbers in **custom.css** from `30` to `60`.
53
1. Click **File → Save** to save the CSS file.
54
55
The user interface immediately changes colour.
56
57
# Classes
58
59
When creating your own skin, there many classes that can be styled. The
60
previous section showed how to set up a rudimentary skin. Instead, start
61
with a template that already has a number of classes defined so that you
62
can tweak them to your taste. Accomplish this as follows:
63
64
1. Visit the [skin](https://gitlab.com/DaveJarvis/KeenWrite/-/tree/main/src/main/resources/com/keenwrite/skins) repository directory
65
1. Click one of the files (e.g., `haunted_grey.css`).
66
1. Click **Raw**.
67
1. Copy the entire text.
68
1. Return to `custom.css`.
69
1. Delete the contents.
70
1. Paste the copied text.
71
1. Save the file.
72
73
To see how the CSS styles are applied to the text editor, open
74
[markdown.css](https://gitlab.com/DaveJarvis/KeenWrite/-/blob/main/src/main/resources/com/keenwrite/editor/markdown.css), which is also in the repository.
75
76
# Modena
77
78
The basic look used by the application is _Modena Light_. Typically we
79
only need to override a few classes to completely change the application's
80
look and feel. For a full listing of available styles see the OpenJDK's
81
[Modena CSS file](https://github.com/openjdk/jfx/blob/master/modules/javafx.controls/src/main/resources/com/sun/javafx/scene/control/skin/modena/modena.css).
82
83
# JavaFX CSS
84
85
The [Java CSS Reference Guide](https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/doc-files/cssref.html) is exhaustive. In addition to showing many
86
differences between JavaFX CSS and W3C CSS, the guide introduces numerous
87
helpful functions for manipulating colours and gradients using existing
88
colour definitions.
89
90
# RichTextFX
91
92
The application uses RichTextFX to render the text editor. Styling various
93
text editor classes can require using the prefix `-rtfx` instead of the
94
regular JavaFX `-fx`.
95
96
# Submit
97
98
If you have a look that you'd like to contribute to the project, do pass
99
it along. Either open a new issue in the [issue tracker](https://gitlab.com/DaveJarvis/KeenWrite/-/issues) that contains the CSS file or submit a pull request.
100
1101
A docs/svg.md
1
# Introduction
2
3
The Scalable Vector Graphics (SVG) drawing software---[Batik](https://xmlgraphics.apache.org/batik/)---that's used by the application may be unable to read certain SVG files produced by [Inkscape](https://inkscape.org/). The result is that embedding the vector graphics files may trigger the following issues:
4
5
* Unable to create nested element
6
* Black blocks, no text displayed
7
* Black text instead of coloured
8
9
The remainder of this document explains these problems and how to fix them.
10
11
# Nested element
12
13
When referencing a vector graphic using Markdown, the status bar may show the following error:
14
15
> The current document is unable to create an element of the requested type (namespace: http://www.w3.org/2000/svg, name: flowRoot).
16
17
This error is due to a version mismatch of the `flowRoot` element that Inkscape creates.
18
19
## Fix
20
21
Resolve the issue by changing the SVG version number as follows:
22
23
1. Edit the vector graphics file using any text editor.
24
1. Find `version="1.1"` and change it to `version="1.2"`.
25
1. Save the file.
26
27
The SVG will now appear inside the application; however, the text may appear as black blocks.
28
29
# Black blocks
30
31
Depending on how text is added to a vector graphic in Inkscape, the text may be inserted within an element called a `flowRoot`. Although Batik recognizes `flowRoot` for SVG version 1.2, it cannot fully interpret the contents. Black blocks are drawn instead of the text, such as those depicted in the following figure:
32
33
![Missing text](images/blocked-text.png)
34
35
## Fix
36
37
Resolve the issue by "unflowing" all text elements as follows:
38
39
1. Start Inkscape.
40
1. Load the SVG file.
41
1. Select all the text elements.
42
1. Click **Text → Unflow**.
43
44
The text may change size and position; recreate the text without dragging using the text tool. After all the text areas have been recreated, continue as follows:
45
46
1. Click **Edit → XML Editor**.
47
1. Expand the **XML Editor** to see more elements.
48
1. Delete all elements named `svg:flowRoot`.
49
1. Save the file.
50
51
When the illustration is reloaded, the black blocks will have disappeared, but the text elements ignore any assigned colour.
52
53
# Black text
54
55
When an SVG `style` attribute contains a reference to `-inkscape-font-specification`, Batik ignores all values that follow said reference. This results in black text, such as:
56
57
![Black text](images/black-text.png)
58
59
## Fix
60
61
Resolve the issue of colourless text as follows:
62
63
1. Open the SVG file in a plain text editor.
64
1. Remove all references `-inkscape-font-specification:'<FONT>';`, including the trailing (or leading) semicolon.
65
1. Save the file.
66
67
When the illustration is reloaded, the colours will have reappeared, such as:
68
69
![Resolved text](images/resolved-text.png)
70
171
A docs/typesetting-custom.md
1
# Overview
2
3
Typesetting PDF files entails the following:
4
5
* Download and install typesetting software
6
* Download a theme pack
7
8
These are described in the subsequent sections. Once the requirements have been met, continue reading to learn how to typeset a document.
9
10
# Download typesetter
11
12
Download the typesetting software as follows:
13
14
1. Start the text editor.
15
1. Click **File → Export As → PDF**.
16
    * Note the following details (e.g., Windows X86 64-bit):
17
        * operating system name;
18
        * instruction set; and
19
        * architecture.
20
1. Click the [link](https://wiki.contextgarden.net/Installation) in the dialog.
21
1. Download the appropriate archive file.
22
23
# Install typesetter
24
25
This section describes the installation steps for various platforms. Follow the steps that apply to the computer's operating system:
26
27
* [Windows](#windows) (includes Windows 7, Windows 10, and similar)
28
* [Unix](#unix) (includes MacOS, FreeBSD, Linux, and similar)
29
30
## Windows
31
32
Proceed with a Windows installation of the typesetting software as follows:
33
34
1. Extract the `.zip` file into `C:\Users\%USERNAME%\AppData\Local\context` (the "root" directory)
35
1. Run **install.bat** to download and install the software.
36
    * If prompted, click **Run anyway** (or click **More info** first).
37
1. Right-click [localpath.bat](https://gitlab.com/DaveJarvis/KeenWrite/-/raw/main/scripts/localpath.bat).
38
1. Select **Save Link As** (or similar).
39
1. Save the file to the typesetting software's "root" directory.
40
1. Rename `localpath.bat.txt` to `localpath.bat`, if necessary.
41
1. Run `localpath.bat` (to set and save the `PATH` environment variable).
42
43
Installation is complete. Verify the installation as follows:
44
45
1. Type: `context --version`
46
1. Press `Enter`.
47
48
If version information is displayed then the software is installed correctly.
49
50
Continue by installing a [theme pack](#theme-pack).
51
52
## Unix
53
54
For Linux, MacOS, FreeBSD, and similar operating systems, proceed as follows:
55
56
1. Create `$HOME/.local/bin/context`
57
1. Extract the `.zip` file within `$HOME/.local/bin/context`
58
1. Run `sh install.sh`
59
1. Add `export PATH=$PATH:$HOME/.local/bin/context/tex/texmf-linux-64/bin` to the login script.
60
61
Installation is complete. Verify the installation as follows:
62
63
1. Open a new terminal (to export the new PATH setting).
64
1. Type: `context --version`
65
1. Press `Enter`.
66
67
If version information is displayed then the software is installed correctly.
68
69
Continue by installing a [theme pack](#theme-pack).
70
71
# Theme pack
72
73
A theme pack is a set of themes that define how documents appear when typeset. Broadly, themes are applied as follows:
74
75
* Install a theme pack
76
* Configure individual themes
77
78
## Install theme pack
79
80
Install and configure the default theme pack as follows:
81
82
1. Download the <a href="https://gitlab.com/DaveJarvis/keenwrite-themes/-/releases/permalink/latest/downloads/theme-pack.zip">theme-pack.zip</a> archive.
83
1. Extract archive into a known location.
84
1. Start the text editor, if not already running.
85
1. Click **Edit → Preferences**.
86
1. Click **Typesetting**.
87
1. Click **Browse** beside **Themes**.
88
1. Navigate to the `themes` directory.
89
1. Click **Open**.
90
1. Click **OK**.
91
92
The theme pack is installed.
93
94
Each theme has its own requirements, described below. 
95
96
## Configure Boschet theme
97
98
Download and install the following font families:
99
100
* [Libre Baskerville](https://fonts.google.com/specimen/Libre+Baskerville)
101
* [Archivo Narrow](https://fonts.google.com/specimen/Archivo+Narrow)
102
* [Inconsolata](https://fonts.google.com/specimen/Inconsolata)
103
104
The theme is configured.
105
106
# Typeset single document
107
108
Typeset a document as follows:
109
110
1. Start the text editor, if not already running.
111
1. Click **File → New** (or type `Ctrl+n`).
112
1. Type in some text.
113
1. Click **File → Export As → PDF** (or type `Ctrl+p`).
114
1. Select a theme from the drop-down list.
115
1. Click **OK** (or press `Enter`).
116
1. Set the **File name** to the PDF file name.
117
1. Click **Save**.
118
119
The document is typeset; open the PDF file in a PDF reader to view the result.
120
121
# Typeset multiple documents
122
123
Typeset multiple documents similar to single documents, with one difference:
124
125
* Click **File → Export As → Joined PDF** (or type `Ctrl+Shift+p`).
126
127
All documents having the same file name extension in the same directory
128
(or sub-directories) as the actively edited file are first concatenated then
129
typeset into a single PDF document. The order that files are concatenated
130
is numeric and alphabetic.
131
132
For example, if `1.Rmd` is a sibling of the following files in the same
133
directory, then all the files will be included in the PDF, as expected:
134
135
    chapter_1.Rmd
136
    chapter_2.Rmd
137
    chapter_2a.Rmd
138
    chapter_2b.Rmd
139
    chapter_3.Rmd
140
    chapter_10.Rmd
141
142
Basically, sorting honours numbers and letters in file names.
143
144
# Background 
145
146
This text editor helps keep content separated from presentation. Plain text documents will remain readable long after proprietary formats have become obsolete. However, we've come to expect much more in what we read than mere text: from hyperlinked tables of contents to indexes, from footers to footnotes, from mathematical expressions to complex graphics, modern documents are nuanced and multifaceted.
147
148
## History
149
150
Before computer-based typesetting, much of mathematics was put to page by hand. Professional typesetters, who were often expensive and usually not mathematicians, would inadvertently introduce typographic errors into equations. Phototypesetting technology improved upon hand-typesetting, but well-known computer scientist Donald Knuth---whose third volume of *The Art of Computer Programming* was phototypeset in 1976---expressed dissatisfaction with its typographic quality. He set himself two goals: let anyone create high-quality books without much effort and provide software that typesets consistently on all capable computers. Two years later, he released a typesetting system and a font description language: TeX and METAFONT, respectively.
151
152
In short, TeX is software that helps typeset plain text documents.
153
154
## ConTeXt
155
156
Programming computers to typeset internationalized text automatically at the level we've become accustomed takes decades of development effort. Many free and open source software solutions can typeset text, including: ConTeXt, LaTeX, Sile, and others. ConTeXt, which builds upon TeX, is ideal for typesetting plain text into beautiful documents because it is developed with a notion of *setups*. These setups can wholly describe how text is to be typeset and---by being external to the text itself---configuring setups provides ample control over the document's final appearance without changing the prose.
157
158
# Further reading
159
160
Here are a few documents that introduce the typesetting system:
161
162
* [What is ConTeXt?](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
167
The [documentation library](https://wiki.contextgarden.net/Documentation) includes the following gems:
168
169
* [ConTeXt Manual](https://www.pragma-ade.nl/general/manuals/ma-cb-en.pdf)
170
* [ConTeXt command reference](https://www.pragma-ade.nl/general/qrcs/setup-en.pdf)
171
* [METAFUN Manual](https://www.pragma-ade.nl/general/manuals/metafun-p.pdf)
172
* [It's in the Details](https://www.pragma-ade.nl/general/manuals/details.pdf)
173
* [Fonts out of ConTeXt](https://www.pragma-ade.com/general/manuals/fonts-mkiv.pdf)
174
175
Expert-level documentation includes the [LuaTeX Reference Manual](https://www.pragma-ade.nl/general/manuals/luatex.pdf).
176
1177
A docs/typesetting.md
1
# Typesetting
2
3
The application uses the [ConTeXt](https://contextgarden.net) typesetting
4
system, the [podman](https://podman.io/) container manager, various
5
[themes](https://gitlab.com/DaveJarvis/keenwrite-themes), and numerous
6
fonts to produce high-quality PDF files. The container manager significantly
7
reduces the number of manual steps in the installation process.
8
9
When exporting a document to a PDF file for the first time, a series of
10
semi-automated steps guides users through the installation process. These
11
steps differ depending on the operating system.
12
13
Run the installation wizard as follows:
14
15
1. Start the application.
16
1. Click **File → Export As → PDF**.
17
18
Follow the steps in the wizard to install the requisite software and
19
typesetting themes.
20
121
A docs/variables.md
1
# Introduction
2
3
This document describes how to use the application.
4
5
# Variable definitions
16
7
Variable definitions provide a way to insert key names having associated values into a document. The variable names and values are declared inside an external filbookending the key name within{{key}} 1
8
key_2: {{key}}{{novel.author}}Variable Files** to listVariable{{novel.author}}uble brace{{novel.title}}{{novel.title}}
A fonts/README.md
1
# Fonts
2
3
For best results, it is recommended that the Noto Font family is installed
4
on the system. The required font families include:
15
6
* Sans-serif --- editor pane
7
* Serif --- preview pane
8
* Serif monospace --- prevnt familie
A fonts/noto-hk.zip
Binary file
A fonts/noto-jp.zip
Binary file
A fonts/noto-kr.zip
Binary file
A fonts/noto-sc.zip
Binary file
A fonts/noto-tc.zip
Binary file
A gradle.properties
1
org.gradle.jvmargs=-Xmx1G
2
org.gradle.daemon=true
3
org.gradle.parallel=true
14
A images/broken-camera.svg
1
<svg height='19pt' viewBox='0 0 25 19' width='25pt' xmlns='http://www.w3.org/2000/svg'><g fill='#454545'><path d='m8.042969 11.085938c.332031 1.445312 1.660156 2.503906 3.214843 2.558593zm0 0'/><path d='m6.792969 9.621094-.300781.226562.242187.195313c.015625-.144531.03125-.28125.058594-.421875zm0 0'/><path d='m10.597656.949219-2.511718.207031c-.777344.066406-1.429688.582031-1.636719 1.292969l-.367188 1.253906-3.414062.28125c-1.027344.085937-1.792969.949219-1.699219 1.925781l.976562 10.621094c.089844.976562.996094 1.699219 2.023438 1.613281l11.710938-.972656-3.117188-2.484375c-.246094.0625-.5.109375-.765625.132812-2.566406.210938-4.835937-1.597656-5.0625-4.039062-.023437-.25-.019531-.496094 0-.738281l-.242187-.195313.300781-.226562c.359375-1.929688 2.039062-3.472656 4.191406-3.652344.207031-.015625.414063-.015625.617187-.007812l.933594-.707032zm0 0'/><path d='m10.234375 11.070312 2.964844 2.820313c.144531.015625.285156.027344.433593.027344 1.890626 0 3.429688-1.460938 3.429688-3.257813 0-1.792968-1.539062-3.257812-3.429688-3.257812-1.890624 0-3.429687 1.464844-3.429687 3.257812 0 .140625.011719.277344.03125.410156zm0 0'/><path d='m14.488281.808594 1.117188 4.554687-1.042969.546875c2.25.476563 3.84375 2.472656 3.636719 4.714844-.199219 2.191406-2.050781 3.871094-4.285157 4.039062l2.609376 2.957032 4.4375.371094c1.03125.085937 1.9375-.640626 2.027343-1.617188l.976563-10.617188c.089844-.980468-.667969-1.839843-1.699219-1.925781l-3.414063-.285156-.371093-1.253906c-.207031-.710938-.859375-1.226563-1.636719-1.289063zm0 0'/></g></svg>
12
A installer.sh
1
#!/usr/bin/env bash
12
3
# ---------------------------------------------------------------------------
4
# This script cross-compiles application launchers for different platforms.
5
#
6
# The application binaries are self-contained launchers that do not need
7
# to enable-preview"21.0.2"
8
ARG_JAVA_UPDATE="14"
A keenwrite.sh
1
#!/usr/bin/env bash
2
3
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
4
5
java \
6
  -Dprism.order=sw \
7
            =ALL-UNNAMED \
8
            =ALL-UNNAMED \
9
     \
10
  -jar ${SCRIPT_DIR}/keenwrite.jar $@
11
112
A libs/keencount.jar
Binary file
A libs/keenquotes.jar
Binary file
A libs/keenspell.jar
Binary file
A libs/keentype-lib.jar
Binary file
A publish.sh
1
#!/usr/bin/env bash
2
3
# ---------------------------------------------------------------------------
4
# This script uploads the latest application release for all supported
5
# platforms.
6
# ---------------------------------------------------------------------------
7
8
readonly RELEASE=$(git describe --abbrev=0 --tags)
9
readonly APP_NAME=$(cut -d= -f2 ./src/main/resources/bootstrap.properties)
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"
14
15
# ---------------------------------------------------------------------------
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.
57
#
58
# $1 - The relative path to the file to upload.
59
# ---------------------------------------------------------------------------
60
upload() {
61
  local -r FILENAME="${1}"
62
63
  if [ -f "${FILENAME}" ]; then
64
    scp "${FILENAME}" "${URL}"
65
  else
66
    echo "Missing ${FILE_BINARY}, continuing."
67
  fi
68
}
69
70
if [ -f "${PATH_TOKEN}" ]; then
71
  cat "${PATH_TOKEN}" | glab auth login --hostname gitlab.com --stdin
72
73
  glab release create ${RELEASE}
74
75
  release "Windows"
76
  release "MacOS"
77
  release "Linux"
78
  release "Java"
79
80
  echo "${RELEASE}" > "${FILE_VERSION}"
81
  upload "${FILE_VERSION}"
82
  mv "${FILE_VERSION}" "www/downloads"
83
else
84
  echo "Create ${PATH_TOKEN} before publishing the release."
85
fi
86
187
A release.sh
1
#!/usr/bin/env bash
2
3
# ---------------------------------------------------------------------------
4
# This script builds Windows, Linux, and Java archive binaries for a
5
# release.
6
# --------------------------------------------------------------------------.shzip,http://infozip.sourceforge.netRemove distribution directory"
7
  rm -rf "${SCRIPT_DIR}/dist"
8
9
  $log "Remove stale binaries"
10
  rm -f "${application_title,,}.jar"
11
  rm -f "${application_title,,}.bin"
12
  rm -f "${application_title}.exe",,}.jar" .MacOSmacos
113
A scripts/.gitignore
1
code-sign-cert.pfx
12
A scripts/build-template
1
#!/usr/bin/env bash
2
3
# -----------------------------------------------------------------------------
4
# Copyright 2020 Dave Jarvis
5
#
6
# Permission is hereby granted, free of charge, to any person obtaining a
7
# copy of this software and associated documentation files (the
8
# "Software"), to deal in the Software without restriction, including
9
# without limitation the rights to use, copy, modify, merge, publish,
10
# distribute, sublicense, and/or sell copies of the Software, and to
11
# permit persons to whom the Software is furnished to do so, subject to
12
# the following conditions:
13
#
14
# The above copyright notice and this permission notice shall be included
15
# in all copies or substantial portions of the Software.
16
#
17
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
# -----------------------------------------------------------------------------
25
26
set -o errexit
27
set -o nounset
28
29
readonly SCRIPT_SRC="$(dirname "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}")"
30
readonly SCRIPT_DIR="$(cd "${SCRIPT_SRC}" >/dev/null 2>&1 && pwd)"
31
readonly SCRIPT_NAME=$(basename "$0")
32
33
# -----------------------------------------------------------------------------
34
# The main entry point is responsible for parsing command-line arguments,
35
# changing to the appropriate directory, and running all commands requested
36
# by the user.
37
#
38
# $@ - Command-line arguments
39
# -----------------------------------------------------------------------------
40
main() {
41
  arguments "$@"
42
43
  $usage       && terminate 3
44
  requirements && terminate 4
45
  traps        && terminate 5
46
47
  directory    && terminate 6
48
  preprocess   && terminate 7
49
  execute      && terminate 8
50
  postprocess  && terminate 9
51
52
  terminate 0
53
}
54
55
# -----------------------------------------------------------------------------
56
# Perform all commands that the script requires.
57
#
58
# @return 0 - Indicate to terminate the script with non-zero exit level
59
# @return 1 - All tasks completed successfully (default)
60
# -----------------------------------------------------------------------------
61
execute() {
62
  return 1
63
}
64
65
# -----------------------------------------------------------------------------
66
# Changes to the script's working directory, provided it exists.
67
#
68
# @return 0 - Change directory failed
69
# @return 1 - Change directory succeeded
70
# -----------------------------------------------------------------------------
71
directory() {
72
  $log "Change directory"
73
  local result=1
74
75
  # Track whether change directory failed.
76
  cd "${SCRIPT_DIR}" > /dev/null 2>&1 || result=0
77
78
  return "${result}"
79
}
80
81
# -----------------------------------------------------------------------------
82
# Perform any initialization required prior to executing tasks.
83
#
84
# @return 0 - Preprocessing failed
85
# @return 1 - Preprocessing succeeded
86
# -----------------------------------------------------------------------------
87
preprocess() {
88
  $log "Preprocess"
89
90
  return 1
91
}
92
93
# -----------------------------------------------------------------------------
94
# Perform any clean up required prior to executing tasks.
95
#
96
# @return 0 - Postprocessing failed
97
# @return 1 - Postprocessing succeeded
98
# -----------------------------------------------------------------------------
99
postprocess() {
100
  $log "Postprocess"
101
102
  return 1
103
}
104
105
# -----------------------------------------------------------------------------
106
# Check that all required commands are available.
107
#
108
# @return 0 - At least one command is missing
109
# @return 1 - All commands are available
110
# -----------------------------------------------------------------------------
111
requirements() {
112
  $log "Verify requirements"
113
  local -r expected_count=${#DEPENDENCIES[@]}
114
  local total_count=0
115
116
  # Verify that each command exists.
117
  for dependency in "${DEPENDENCIES[@]}"; do
118
    # Extract the command name [0] and URL [1].
119
    IFS=',' read -ra dependent <<< "${dependency}"
120
121
    required "${dependent[0]}" "${dependent[1]}"
122
    total_count=$(( total_count + $? ))
123
  done
124
125
  unset IFS
126
127
  # Total dependencies found must match the expected number.
128
  # Integer-only division rounds down.
129
  return $(( total_count / expected_count ))
130
}
131
132
# -----------------------------------------------------------------------------
133
# Called before terminating the script.
134
# -----------------------------------------------------------------------------
135
cleanup() {
136
  $log "Cleanup"
137
}
138
139
# -----------------------------------------------------------------------------
140
# Terminates the program immediately.
141
# -----------------------------------------------------------------------------
142
trap_control_c() {
143
  $log "Interrupted"
144
  cleanup
145
  error "⯃"
146
  terminate 1
147
}
148
149
# -----------------------------------------------------------------------------
150
# Configure signal traps.
151
#
152
# @return 1 - Signal traps are set.
153
# -----------------------------------------------------------------------------
154
traps() {
155
  # Suppress echoing ^C if pressed.
156
  stty -echoctl
157
  trap trap_control_c INT
158
159
  return 1
160
}
161
162
# -----------------------------------------------------------------------------
163
# Check for a required command.
164
#
165
# $1 - Command or file to check for existence
166
# $2 - Command's website (e.g., download for binaries and source code)
167
#
168
# @return 0 - Command is missing
169
# @return 1 - Command exists
170
# -----------------------------------------------------------------------------
171
required() {
172
  local result=0
173
174
  test -f "$1" || \
175
  command -v "$1" > /dev/null 2>&1 && result=1 || \
176
    warning "Missing: $1 ($2)"
177
178
  return ${result}
179
}
180
181
# -----------------------------------------------------------------------------
182
# Show acceptable command-line arguments.
183
#
184
# @return 0 - Indicate script may not continue
185
# -----------------------------------------------------------------------------
186
utile_usage() {
187
  printf "Usage: %s [OPTIONS...]\n\n" "${SCRIPT_NAME}" >&2
188
189
  # Number of spaces to pad after the longest long argument.
190
  local -r PADDING=2
191
192
  # Determine the longest long argument to adjust spacing.
193
  local -r LEN=$(printf '%s\n' "${ARGUMENTS[@]}" | \
194
    awk -F"," '{print length($2)+'${PADDING}'}' | sort -n | tail -1)
195
196
  local duplicates
197
198
  for argument in "${ARGUMENTS[@]}"; do
199
    # Extract the short [0] and long [1] arguments and description [2].
200
    arg=("$(echo ${argument} | cut -d ',' -f1)" \
201
         "$(echo ${argument} | cut -d ',' -f2)" \
202
         "$(echo ${argument} | cut -d ',' -f3-)")
203
204
    duplicates+=("${arg[0]}")
205
206
    printf "  -%s, --%-${LEN}s%s\n" "${arg[0]}" "${arg[1]}" "${arg[2]}" >&2
207
  done
208
209
  # Sort the arguments to make sure no duplicates exist.
210
  duplicates=$(echo "${duplicates[@]}" | tr ' ' '\n' | sort | uniq -c -d)
211
212
  # Warn the developer that there's a duplicate command-line option.
213
  if [ -n "${duplicates}" ]; then
214
    # Trim all the whitespaces
215
    duplicates=$(echo "${duplicates}" | xargs echo -n)
216
    error "Duplicate command-line argument exists: ${duplicates}"
217
  fi
218
219
  return 0
220
}
221
222
# -----------------------------------------------------------------------------
223
# Write coloured text to standard output.
224
#
225
# $1 - Text to write
226
# $2 - Text's colour
227
# -----------------------------------------------------------------------------
228
coloured_text() {
229
  printf "%b%s%b\n" "$2" "$1" "${COLOUR_OFF}"
230
}
231
232
# -----------------------------------------------------------------------------
233
# Write a warning message to standard output.
234
#
235
# $1 - Text to write
236
# -----------------------------------------------------------------------------
237
warning() {
238
  coloured_text "$1" "${COLOUR_WARNING}"
239
}
240
241
# -----------------------------------------------------------------------------
242
# Write an error message to standard output.
243
#
244
# $1 - Text to write
245
# -----------------------------------------------------------------------------
246
error() {
247
  coloured_text "$1" "${COLOUR_ERROR}"
248
}
249
250
# -----------------------------------------------------------------------------
251
# Write a timestamp and message to standard output.
252
#
253
# $1 - Text to write
254
# -----------------------------------------------------------------------------
255
utile_log() {
256
  printf "[%s] " "$(date +%H:%M:%S.%4N)"
257
  coloured_text "$1" "${COLOUR_LOGGING}"
258
}
259
260
# -----------------------------------------------------------------------------
261
# Perform no operations.
262
#
263
# return 1 - Success
264
# -----------------------------------------------------------------------------
265
noop() {
266
  return 1
267
}
268
269
# -----------------------------------------------------------------------------
270
# Exit the program with a given exit code.
271
#
272
# $1 - Exit code
273
# -----------------------------------------------------------------------------
274
terminate() {
275
  exit "$1"
276
}
277
278
# -----------------------------------------------------------------------------
279
# Set global variables from command-line arguments.
280
# -----------------------------------------------------------------------------
281
arguments() {
282
  while [ "$#" -gt "0" ]; do
283
    local consume=1
284
285
    case "$1" in
286
      -V|--verbose)
287
        log=utile_log
288
      ;;
289
      -h|-\?|--help)
290
        usage=utile_usage
291
      ;;
292
      *)
293
        set +e
294
        argument "$@"
295
        consume=$?
296
        set -e
297
      ;;
298
    esac
299
300
    shift ${consume}
301
  done
302
}
303
304
# -----------------------------------------------------------------------------
305
# Parses a single command-line argument. This must return a value greater
306
# than or equal to 1, otherwise parsing the command-line arguments will
307
# loop indefinitely.
308
#
309
# @return The number of arguments to consume (1 by default).
310
# -----------------------------------------------------------------------------
311
argument() {
312
  return 1
313
}
314
315
# ANSI colour escape sequences.
316
readonly COLOUR_BLUE='\033[1;34m'
317
readonly COLOUR_PINK='\033[1;35m'
318
readonly COLOUR_DKGRAY='\033[30m'
319
readonly COLOUR_DKRED='\033[31m'
320
readonly COLOUR_LTRED='\033[1;31m'
321
readonly COLOUR_YELLOW='\033[1;33m'
322
readonly COLOUR_OFF='\033[0m'
323
324
# Colour definitions used by script.
325
COLOUR_LOGGING=${COLOUR_BLUE}
326
COLOUR_WARNING=${COLOUR_YELLOW}
327
COLOUR_ERROR=${COLOUR_LTRED}
328
329
# Define required commands to check when script starts.
330
DEPENDENCIES=(
331
  "awk,https://www.gnu.org/software/gawk/manual/gawk.html"
332
  "cut,https://www.gnu.org/software/coreutils"
333
)
334
335
# Define help for command-line arguments.
336
ARGUMENTS=(
337
  "V,verbose,Log messages while processing"
338
  "h,help,Show this help message then exit"
339
)
340
341
# These functions may be set to utile delegates while parsing arguments.
342
usage=noop
343
log=noop
344
1345
A scripts/font-names.sh
1
#!/usr/bin/env bash
2
3
# Outputs font names for all font files.
4
5
find src/main/resources/fonts -type f \( -name "*otf" -o -name "*ttf" \) -exec \
6
  fc-scan --format "%{foundry}: %{family}\n" {} \; | uniq | sort
7
18
A scripts/icons.sh
1
#!/bin/bash
2
3
INKSCAPE="/usr/bin/inkscape"
4
PNG_COMPRESS="optipng"
5
PNG_COMPRESS_OPTS="-o9 *png"
6
7
declare -a SIZES=("16" "32" "64" "128" "256" "512")
8
9
for i in "${SIZES[@]}"; do
10
  # -y: export background opacity 0
11
  $INKSCAPE -y 0 -w "${i}" --export-overwrite --export-type=png -o "logo${i}.png" "logo.svg" 
12
done
13
14
# Compess the PNG images.
15
which $PNG_COMPRESS && $PNG_COMPRESS $PNG_COMPRESS_OPTS
16
117
A scripts/jsign-4.2.jar
Binary file
A scripts/localpath.bat
1
@echo off
2
set "OWNPATH=%~dp0"
3
set "PLATFORM=mswin"
4
5
if defined ProgramFiles(x86)                        set "PLATFORM=win64"
6
if "%PROCESSOR_ARCHITECTURE%"=="AMD64"              set "PLATFORM=win64"
7
if exist "%OWNPATH%tex\texmf-mswin\bin\context.exe" set "PLATFORM=mswin"
8
if exist "%OWNPATH%tex\texmf-win64\bin\context.exe" set "PLATFORM=win64"
9
10
set "TeXPath=%OWNPATH%tex\texmf-%PLATFORM%\bin"
11
12
echo %PATH% | findstr "texmf-%PLATFORM%" > nul
13
14
rem Only update the PATH if not previously updated
15
if ERRORLEVEL 1 (
16
  setlocal enabledelayedexpansion
17
  set "Exists=false"
18
  set "Key=HKCU\Environment"
19
  
20
  for /F "USEBACKQ tokens=2*" %%A in (`reg query %%Key%% /v PATH 2^>nul`) do (
21
    if not "%%~B" == "" (
22
      set "Exists=true"
23
24
      rem Preserve the existing PATH
25
      echo %%B > currpath.txt
26
27
      rem Change the PATH environment variable
28
      setx PATH "%%B;%TeXPath%"
29
    )
30
  )
31
32
  rem The user-defined PATH does not exist, create it
33
  if "!Exists!" == "false" (
34
    rem Change the user PATH environment variable
35
    setx PATH "%TeXPath%"
36
  )
37
38
  endlocal
39
  
40
  rem Update the current session
41
  set "PATH=%PATH%;%TeXPath%"
42
)
43
144
A scripts/logo.ico
Binary file
A scripts/rcedit-x64.exe
Binary file
A scripts/squish.sh
1
#!/usr/bin/env bash
2
3
# TODO: This file does not work with Picocli and there are other issues.
4
# TODO: Revisit after replacing Picocli and using FastR instead of Renjin.
5
6
MODULES="${JAVA_HOME}/jmods/"
7
LIBS=$(ls -1 ../libs/*jar | sed 's/\(.*\)/-libraryjars \1/g')
8
9
java -jar ../tex/lib/proguard.jar \
10
  -libraryjars "${MODULES}java.base.jmod/(!**.jar;!module-info.class)" \
11
  -libraryjars "${MODULES}java.desktop.jmod/(!**.jar;!module-info.class)" \
12
  -libraryjars "${MODULES}java.xml.jmod/(!**.jar;!module-info.class)" \
13
  -libraryjars "${MODULES}javafx.controls.jmod/(!**.jar;!module-info.class)" \
14
  -libraryjars "${MODULES}javafx.graphics.jmod/(!**.jar;!module-info.class)" \
15
  ${LIBS} \
16
  -injars ../build/libs/keenwrite.jar \
17
  -outjars ../build/libs/keenwrite-min.jar \
18
  -keep 'class com.keenwrite.** { *; }' \
19
  -keep 'class com.whitemagicsoftware.tex.** { *; }' \
20
  -keep 'class org.renjin.** { *; }' \
21
  -keep 'class picocli.** { *; }' \
22
  -keep 'interface picocli.** { *; }' \
23
  -keep 'class picocli.CommandLine { *; }' \
24
  -keep 'class picocli.CommandLine$* { *; }' \
25
  -keepattributes '*Annotation*, Signature, Exception' \
26
  -keepclassmembers 'class * extends java.util.concurrent.Callable {
27
      public java.lang.Integer call();
28
  }' \
29
  -keepclassmembers 'class * {
30
      @javax.inject.Inject <init>(...);
31
      @picocli.CommandLine$Option *;
32
  }' \
33
  -keepclassmembers 'class * extends java.lang.Enum {
34
      <fields>;
35
      public static **[] values();
36
      public static ** valueOf(java.lang.String);
37
  }' \
38
  -keepnames \
39
    'class org.apache.lucene.analysis.tokenattributes.KeywordAttributeImpl' \
40
  -dontnote \
41
  -dontwarn \
42
  -dontoptimize \
43
  -dontobfuscate
44
145
A settings.gradle
11
A src/main/java/com/keenwrite/AppCommands.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite;
6
7
import com.keenwrite.cmdline.Arguments;
8
import com.keenwrite.commands.ConcatenateCommand;
9
import com.keenwrite.io.SysFile;
10
import com.keenwrite.processors.Processor;
11
import com.keenwrite.processors.ProcessorContext;
12
import com.keenwrite.processors.r.RBootstrapProcessor;
13
14
import java.io.IOException;
15
import java.nio.file.Path;
16
import java.util.concurrent.Callable;
17
import java.util.concurrent.CompletableFuture;
18
import java.util.concurrent.ExecutorService;
19
import java.util.concurrent.Future;
20
import java.util.concurrent.atomic.AtomicInteger;
21
22
import static com.keenwrite.Launcher.terminate;
23
import static com.keenwrite.events.StatusEvent.clue;
24
import static com.keenwrite.io.MediaType.TEXT_R_MARKDOWN;
25
import static com.keenwrite.processors.ProcessorFactory.createProcessors;
26
import static java.nio.charset.StandardCharsets.UTF_8;
27
import static java.nio.file.Files.readString;
28
import static java.nio.file.Files.writeString;
29
import static java.util.concurrent.Executors.newFixedThreadPool;
30
import static org.apache.commons.io.FilenameUtils.getExtension;
31
32
/**
33
 * Responsible for executing common commands. These commands are shared by
34
 * both the graphical and the command-line interfaces.
35
 */
36
public class AppCommands {
37
  private static final ExecutorService sExecutor = newFixedThreadPool( 1 );
38
39
  private AppCommands() {
40
  }
41
42
  public static void run( final Arguments args ) {
43
    final var exitCode = new AtomicInteger();
44
45
    final var future = new CompletableFuture<Path>() {
46
      @Override
47
      public boolean complete( final Path path ) {
48
        return super.complete( path );
49
      }
50
51
      @Override
52
      public boolean completeExceptionally( final Throwable ex ) {
53
        clue( ex );
54
        exitCode.set( 1 );
55
56
        return super.completeExceptionally( ex );
57
      }
58
    };
59
60
    file_export( args, future );
61
    sExecutor.shutdown();
62
    future.join();
63
    terminate( exitCode.get() );
64
  }
65
66
  /**
67
   * Converts one or more files into the given file format. If {@code dir}
68
   * is set to true, this will first append all files in the same directory
69
   * as the actively edited file.
70
   *
71
   * @param future Indicates whether the export succeeded or failed.
72
   * @return The path to the exported file as a {@link Future}.
73
   */
74
  @SuppressWarnings( "UnusedReturnValue" )
75
  private static Future<Path> file_export(
76
    final Arguments args, final CompletableFuture<Path> future ) {
77
    assert args != null;
78
    assert future != null;
79
80
    final Callable<Path> callableTask = () -> {
81
      try {
82
        final var context = args.createProcessorContext();
83
        final var outputPath = context.getTargetPath();
84
        final var chain = createProcessors( context );
85
        final var processor = createBootstrapProcessor( chain, context );
86
        final var inputDoc = read( context );
87
        final var outputDoc = processor.apply( inputDoc );
88
89
        // Processors can export binary files. In such cases, processors will
90
        // return null to prevent further processing.
91
        final var result = outputDoc == null
92
          ? null
93
          : writeString( outputPath, outputDoc, UTF_8 );
94
95
        future.complete( outputPath );
96
        return result;
97
      } catch( final Throwable ex ) {
98
        future.completeExceptionally( ex );
99
        return null;
100
      }
101
    };
102
103
    // Prevent the application from blocking while the processor executes.
104
    return sExecutor.submit( callableTask );
105
  }
106
107
  private static Processor<String> createBootstrapProcessor(
108
    final Processor<String> chain, final ProcessorContext context ) {
109
110
    return context.getSourceType() == TEXT_R_MARKDOWN
111
      ? new RBootstrapProcessor( chain, context )
112
      : chain;
113
  }
114
115
  /**
116
   * Concatenates all the files in the same directory as the given file into
117
   * a string. The extension is determined by the given file name pattern; the
118
   * order files are concatenated is based on their numeric sort order (this
119
   * avoids lexicographic sorting).
120
   * <p>
121
   * If the parent path to the file being edited in the text editor cannot
122
   * be found then this will return the editor's text, without iterating through
123
   * the parent directory. (Should never happen, but who knows?)
124
   * </p>
125
   * <p>
126
   * New lines are automatically appended to separate each file.
127
   * </p>
128
   *
129
   * @param context The {@link ProcessorContext} containing input path,
130
   *                and other command-line parameters.
131
   * @return All files in the same directory as the file being edited
132
   * concatenated into a single string.
133
   */
134
  private static String read( final ProcessorContext context )
135
    throws IOException {
136
    final var concat = context.getConcatenate();
137
    final var inputPath = context.getSourcePath();
138
    final var parent = inputPath.getParent();
139
    final var filename = SysFile.getFileName( inputPath );
140
    final var extension = getExtension( filename );
141
142
    // Short-circuit because: only one file was requested; there is no parent
143
    // directory to scan for files; or there's no extension for globbing.
144
    if( !concat || parent == null || extension.isBlank() ) {
145
      return readString( inputPath, UTF_8 );
146
    }
147
148
    final var command = new ConcatenateCommand(
149
      parent, extension, context.getChapters() );
150
    return command.call();
151
  }
152
}
1153
A src/main/java/com/keenwrite/Bootstrap.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite;
3
4
import com.keenwrite.constants.Constants;
5
import com.keenwrite.io.UserDataDir;
16
7
import java.io.File;
8
import java.io.InputStream;
9
import java.nio.file.Pathio.SysFile.toFilvar appVersion = "0.0.0";
10
    vartoFile( 
A src/main/java/com/keenwrite/ExportFormat.java
1
/* Copyright 2020-2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite;
6
7
import com.keenwrite.io.MediaType;
8
import com.keenwrite.io.MediaTypeExtension;
9
10
import java.io.File;
11
import java.nio.file.Path;
12
13
import static com.keenwrite.io.SysFile.toFile;
14
import static java.lang.String.format;
15
import static org.apache.commons.io.FilenameUtils.removeExtension;
16
17
/**
18
 * Provides controls for processor behaviour when transforming input documents.
19
 */
20
public enum ExportFormat {
21
22
  /**
23
   * For HTML exports, encode TeX as SVG. Treat image links relatively.
24
   */
25
  HTML_TEX_SVG( ".html" ),
26
27
  /**
28
   * For HTML exports, encode TeX using {@code $} delimiters, suitable for
29
   * rendering by an external TeX typesetting engine (or online with KaTeX).
30
   * Treat image links relatively.
31
   */
32
  HTML_TEX_DELIMITED( ".html" ),
33
34
  /**
35
   * For XHTML exports, encode TeX using {@code $} delimiters.
36
   */
37
  XHTML_TEX( ".xhtml" ),
38
39
  /**
40
   * For TEXT exports, encode TeX using {@code $} delimiters.
41
   */
42
  TEXT_TEX( ".txt" ),
43
44
  /**
45
   * Exports as PDF file format.
46
   */
47
  APPLICATION_PDF( ".pdf" ),
48
49
  /**
50
   * Indicates no special export format is to be created. No extension is
51
   * applicable. Image links must use absolute directories.
52
   */
53
  NONE( "" );
54
55
  /**
56
   * Preferred file name extension for the given file type.
57
   */
58
  private final String mExtension;
59
60
  /**
61
   * Looks up the {@link ExportFormat} based on the given path and subtype.
62
   *
63
   * @param path     The type to find.
64
   * @param modifier The subtype to find (for HTML).
65
   * @return An object to control the output file format.
66
   * @throws IllegalArgumentException The type/subtype could not be found.
67
   */
68
  public static ExportFormat valueFrom( final Path path, final String modifier )
69
    throws IllegalArgumentException {
70
    assert path != null;
71
72
    return valueFrom( MediaType.fromFilename( path ), modifier );
73
  }
74
75
  /**
76
   * Looks up the {@link ExportFormat} based on the given path and subtype.
77
   *
78
   * @param extension The type to find.
79
   * @param modifier  The subtype to find (for HTML).
80
   * @return An object to control the output file format.
81
   * @throws IllegalArgumentException The type/subtype could not be found.
82
   */
83
  public static ExportFormat valueFrom(
84
    final String extension, final String modifier )
85
    throws IllegalArgumentException {
86
    assert extension != null;
87
88
    return valueFrom( MediaTypeExtension.fromExtension( extension ), modifier );
89
  }
90
91
  /**
92
   * Looks up the {@link ExportFormat} based on the given path and subtype.
93
   *
94
   * @param type     The media type to find.
95
   * @param modifier The subtype to find (for HTML).
96
   * @return An object to control the output file format.
97
   * @throws IllegalArgumentException The type/subtype could not be found.
98
   */
99
  public static ExportFormat valueFrom(
100
    final MediaType type, final String modifier ) {
101
    return switch( type ) {
102
      case TEXT_HTML, TEXT_XHTML -> "svg".equalsIgnoreCase( modifier.trim() )
103
        ? HTML_TEX_SVG
104
        : HTML_TEX_DELIMITED;
105
      case APP_PDF -> APPLICATION_PDF;
106
      case TEXT_XML -> XHTML_TEX;
107
      default -> throw new IllegalArgumentException( format(
108
        "Unrecognized format type and subtype: '%s' and '%s'", type, modifier
109
      ) );
110
    };
111
  }
112
113
  ExportFormat( final String extension ) {
114
    mExtension = extension;
115
  }
116
117
  /**
118
   * Returns the given {@link File} with its extension replaced by one that
119
   * matches this {@link ExportFormat} extension.
120
   *
121
   * @param file The file to perform an extension swap.
122
   * @return The given file with its extension replaced.
123
   */
124
  public File toExportFilename( final File file ) {
125
    return new File( removeExtension( file.getName() ) + mExtension );
126
  }
127
128
  /**
129
   * Delegates to {@link #toExportFilename(File)} after converting the given
130
   * {@link Path} to an instance of {@link File}.
131
   *
132
   * @param path The {@link Path} to convert to a {@link File}.
133
   * @return The given path with its extension replaced.
134
   */
135
  public File toExportFilename( final Path path ) {
136
    return toExportFilename( toFile( path ) );
137
  }
138
}
1139
A src/main/java/com/keenwrite/Launcher.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite;
3
4
import com.keenwrite.cmdline.Arguments;
5
import com.keenwrite.cmdline.ColourScheme;
6
import com.keenwrite.cmdline.HeadleLaunches the application using the {@link MainApp} clas    elseoutSystem.errto standard error, suppresse  // TODO: Delete this after JavaFX/GTK 3 no longer barfs useless warnings.
7
    System.err.closeoutSystem.outout( "%noutMainApp.maimain( args}
18
A src/main/java/com/keenwrite/MainApp.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite;
3
4
import com.keenwrite.cmdline.HeadlessApp;
5
import com.keenwrite.events.HyperlinkOpenEvent;
6
import com.keenwrite.preferences.Workspace;
7
import com.keenwrite.preview.MathRenderer;
8
import com.keenwrite.spelling.impl.Lexicon;
9
import javafx.application.Application;
10
import javafx.event.Event;
11
import javafx.event.EventType;
12
import javafx.scene.input.KeyCode;
13
import javafx.scene.input.KeyEvent;
14
import javafx.stage.Stage;
15
import org.greenrobot.eventbus.Subscribe;
16
17
import java.io.PrintStream;
18
import java.util.function.BooleanSupplier;
19
20
import static com.keenwrite.Bootstrap.APP_TITLE;
21
import static com.keenwrite.constants.GraphicsConstants.LOGOS;
22
import static com.keenwrite.events.Bus.register;
23
import static com.keenwrite.preferences.AppKeys.*;
24
import static com.keenwrite.util.FontLoader.initFonts;
25
import static javafx.scene.input.KeyCode.F11;
26
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
27
import static javafx.scene.input.KeyEvent.KEY_RELEASED;
28
import static javafx.stage.WindowEvent.WINDOW_SHOWN;
29
30
/**
31
 * Application entry point. The application allows users to edit plain text
32
 * files in a markup notation and see a real-time preview of the formatted
33
 * output.
34
 */
35
public final class MainApp extends Application {
36
37
  private Workspace mWorkspace;
38
39
  /**
40
   * TODO: Delete this after JavaFX/GTK 3 no longer barfs useless warnings.
41
   */
42
  @SuppressWarnings( "SameParameterValue" )
43
  private static void stderrRedirect( final PrintStream stream ) {
44
    System.setErr( stream );
45
  }
46
47
  /**
48
   * GUI application entry point. See {@link HeadlessApp} for the entry
49
   * point to the command-line application.
50
   *
51
   * @param args Command-line arguments.
52
   */
53
  public static void main( final String[] args ) {
54
    launch( args );
55
  }
56
57
  /**
58
   * Creates an instance of {@link KeyEvent} that represents pressing a key.
59
   *
60
   * @param code  The key to simulate being pressed down.
61
   * @param shift Whether shift key modifier shall modify the key code.
62
   * @return An instance of {@link KeyEvent} that may be used to simulate
63
   * a key being pressed.
64
   */
65
  public static Event keyDown( final KeyCode code, final boolean shift ) {
66
    return keyEvent( KEY_PRESSED, code, shift );
67
  }
68
69
  /**
70
   * Creates an instance of {@link KeyEvent} that represents a key released
71
   * event without any modifier keys held.
72
   *
73
   * @param code The key code representing a key to simulate releasing.
74
   * @return An instance of {@link KeyEvent}.
75
   */
76
  public static Event keyDown( final KeyCode code ) {
77
    return keyDown( code, false );
78
  }
79
80
  /**
81
   * Creates an instance of {@link KeyEvent} that represents releasing a key.
82
   *
83
   * @param code  The key to simulate being released up.
84
   * @param shift Whether shift key modifier shall modify the key code.
85
   * @return An instance of {@link KeyEvent} that may be used to simulate
86
   * a key being released.
87
   */
88
  @SuppressWarnings( "unused" )
89
  public static Event keyUp( final KeyCode code, final boolean shift ) {
90
    return keyEvent( KEY_RELEASED, code, shift );
91
  }
92
93
  private static Event keyEvent(
94
    final EventType<KeyEvent> type, final KeyCode code, final boolean shift ) {
95
    return new KeyEvent(
96
      type, "", "", code, shift, false, false, false
97
    );
98
  }
99
100
  /**
101
   * JavaFX entry point.
102
   *
103
   * @param stage The primary application stage.
104
   */
105
  @Override
106
  public void start( final Stage stage ) {
107
    // Must be instantiated after the UI is initialized (i.e., not in main)
108
    // because it interacts with GUI properties.
109
    mWorkspace = new Workspace();
110
111
    // The locale was already loaded when the workspace was created. This
112
    // ensures that when the locale preference changes, a new spellchecker
113
    // instance will be loaded and applied.
114
    final var property = mWorkspace.localeProperty( KEY_LANGUAGE_LOCALE );
115
    property.addListener( ( _, _, _ ) -> readLexicon() );
116
117
    initFonts();
118
    initState( stage );
119
    initStage( stage );
120
    initIcons( stage );
121
    initScene( stage );
122
123
    MathRenderer.bindSize( mWorkspace.doubleProperty( KEY_UI_FONT_MATH_SIZE ) );
124
125
    // Load the lexicon and check all the documents after all files are open.
126
    stage.addEventFilter( WINDOW_SHOWN, _ -> readLexicon() );
127
    stage.show();
128
129
    stderrRedirect( System.out );
130
131
    register( this );
132
  }
133
134
  private void initState( final Stage stage ) {
135
    final var enable = createBoundsEnabledSupplier( stage );
136
137
    stage.setX( mWorkspace.getDouble( KEY_UI_WINDOW_X ) );
138
    stage.setY( mWorkspace.getDouble( KEY_UI_WINDOW_Y ) );
139
    stage.setWidth( mWorkspace.getDouble( KEY_UI_WINDOW_W ) );
140
    stage.setHeight( mWorkspace.getDouble( KEY_UI_WINDOW_H ) );
141
    stage.setMaximized( mWorkspace.getBoolean( KEY_UI_WINDOW_MAX ) );
142
    stage.setFullScreen( mWorkspace.getBoolean( KEY_UI_WINDOW_FULL ) );
143
144
    mWorkspace.listen( KEY_UI_WINDOW_X, stage.xProperty(), enable );
145
    mWorkspace.listen( KEY_UI_WINDOW_Y, stage.yProperty(), enable );
146
    mWorkspace.listen( KEY_UI_WINDOW_W, stage.widthProperty(), enable );
147
    mWorkspace.listen( KEY_UI_WINDOW_H, stage.heightProperty(), enable );
148
    mWorkspace.listen( KEY_UI_WINDOW_MAX, stage.maximizedProperty() );
149
    mWorkspace.listen( KEY_UI_WINDOW_FULL, stage.fullScreenProperty() );
150
  }
151
152
  private void initStage( final Stage stage ) {
153
    stage.setTitle( APP_TITLE );
154
    stage.addEventHandler( KEY_PRESSED, event -> {
155
      if( F11.equals( event.getCode() ) ) {
156
        stage.setFullScreen( !stage.isFullScreen() );
157
      }
158
    } );
159
  }
160
161
  private void initIcons( final Stage stage ) {
162
    stage.getIcons().addAll( LOGOS );
163
  }
164
165
  private void initScene( final Stage stage ) {
166
    final var mainScene = new MainScene( mWorkspace );
167
    stage.setScene( mainScene.getScene() );
168
  }
169
170
  /**
171
   * When a hyperlink website URL is clicked, this method is called to launch
172
   * the default browser to the event's location.
173
   *
174
   * @param event The event called when a hyperlink was clicked.
175
   */
176
  @Subscribe
177
  public void handle( final HyperlinkOpenEvent event ) {
178
    getHostServices().showDocument( event.getUri().toString() );
179
  }
180
181
  /**
182
   * This will load the lexicon for the user's preferred locale and fire
183
   * an event when the all entries in the lexicon have been loaded.
184
   */
185
  private void readLexicon() {
186
    Lexicon.read( mWorkspace.getLocale() );
187
  }
188
189
  /**
190
   * When the window is maximized, full screen, or iconified, prevent updating
191
   * the window bounds. This is used so that if the user exits the application
192
   * when full screen (or maximized), restarting the application will recall
193
   * the previous bounds, allowing for continuity of expected behaviour.
194
   *
195
   * @param stage The window to check for "normal" status.
196
   * @return {@code false} when the bounds must not be changed, ergo persisted.
197
   */
198
  private BooleanSupplier createBoundsEnabledSupplier( final Stage stage ) {
199
    return () ->
200
      !(stage.isMaximized() || stage.isFullScreen() || stage.isIconified());
201
  }
202
}
1203
A src/main/java/com/keenwrite/MainPane.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite;
13
4
import com.keenwrite.constants.Constants;
5
import com.keenwrite.editors.TextDefinition;
6
import com.keenwrite.editors.TextEditor;
7
import com.keenwrite.editoTR."\{key}prefix"TR."\{key}suffix"TR."\{prefix}.\{suffix}"TR."\{prefix}-\{i}.\{suffix}"
A src/main/java/com/keenwrite/MainScene.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite;
13
4
import com.keenwrite.io.FileModifiedListener;
5
import com.keenwrite.io.FileWatchService;
6
import com.keenwrite.preferences.Workspace;
7
import com.keenwrite.uSCROLL_LOCK
A src/main/java/com/keenwrite/Messages.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite;
13
4
import com.keenwrite.collections.InterpolatingMap;
5
import com.keenwrite.preferences.Key;
6
import com.keenwrite.sigils.PropertyKeyOperator;
7
import com.keenwnet.URI;public static URI getUriURI.create( get( key 
A src/main/java/com/keenwrite/Services.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite;
3
4
import java.util.HashMap;
5
import java.util.Map;
6
import java.util.ServiceLoader;
17
8
/**
9
 * Responsible for loading services. The services are treated as singleton
A src/main/java/com/keenwrite/cmdline/Arguments.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.cmdline;
16
7
import com.fasterxml.jackson.databind.JsonNode;
8
import com.fasterxml.jackson.databind.ObjectMapper;
9
import comTR."\{parent}.\{node.getKey()}"
A src/main/java/com/keenwrite/cmdline/ColourScheme.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.cmdline;
6
7
import static picocli.CommandLine.Help.Ansi.Style.*;
8
import static picocli.CommandLine.Help.ColorScheme;
9
import static picocli.CommandLine.Help.ColorScheme.Builder;
110
11
/**
12
 * Responsible for creating the command-line parser's
A src/main/java/com/keenwrite/cmdline/HeadlessApp.java
1
package com.keenwrite.cmdline;
2
3
import com.keenwrite.AppCommands;
4
import com.keenwrite.events.StatusEvent;
5
import org.greenrobot.eventbus.Subscribe;
6
7
import static com.keenwrite.events.Bus.register;
8
import static java.lang.String.format;
19
10
/**
11
 * Responsible for running the application in headlassert event != null;
12
final var stacktrace = event.getProblem();
13
      final var problem = stacktrace.isBlank()
14
        ? ""
15
        : format( "%n%s", stacktrace );
16
      final var msg = format( "%s%s", event, problem );
17
msg
A src/main/java/com/keenwrite/collections/BoundedCache.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.collections;
3
4
import java.util.LinkedHashMap;
5
import java.util.Map;
6
7
/**
8
 * A map that removes the oldest entry once its capacity (cache size) has
9
 * been reached.
10
 *
11
 * @param <K> The type of key mapped to a value.
12
 * @param <V> The type of value mapped to a key.
13
 */
14
public final class BoundedCache<K, V> extends LinkedHashMap<K, V> {
15
  private final int mCacheSize;
16
17
  /**
18
   * Constructs a new instance having a finite size.
19
   *
20
   * @param cacheSize The maximum number of entries.
21
   */
22
  public BoundedCache( final int cacheSize ) {
23
    mCacheSize = cacheSize;
24
  }
25
26
  @Override
27
  protected boolean removeEldestEntry( final Map.Entry<K, V> eldest ) {
28
    return size() > mCacheSize;
29
  }
30
}
131
A src/main/java/com/keenwrite/collections/CircularQueue.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.collections;
3
4
import org.jetbrains.annotations.NotNull;
5
6
import java.util.*;
7
8
import static java.lang.Math.min;
19
10
/**
11
 * Responsible for maintaining a circular queue where newly added items will
12
 * overwri@NotNull
A src/main/java/com/keenwrite/collections/InterpolatingMap.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.collections;
3
4
import com.keenwrite.sigils.PropertyKeyOperator;
5
import com.keenwrite.sigils.SigilKeyOperator;
16
7
import java.io.Serial;
8
import java.util.HashMap;
9
imSTR."Mapped value '\{mapValue}\{keyName}'"
A src/main/java/com/keenwrite/commands/ConcatenateCommand.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.commands;
16
7
import com.keenwrite.io.SysFile;
8
import com.keenwrite.util.AlphanumComparator;
9
import com.keenwrite.util.RangeValiSTR."**/*.\{mExtension}"
A src/main/java/com/keenwrite/config/PropertiesConfiguration.java
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
}
1115
A src/main/java/com/keenwrite/constants/Constants.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.constants;
6
7
import com.keenwrite.Bootstrap;
8
import com.keenwrite.Services;
9
import com.keenwrite.service.Settings;
110
11
import javTR."file.default.\{suffix}" )
A src/main/java/com/keenwrite/constants/GraphicsConstants.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.constants;
6
7
import javafx.scene.image.Image;
8
import javafx.scene.image.ImageView;
9
10
import java.util.ArrayList;
11
import java.util.List;
112
13
import static com.kee
A src/main/java/com/keenwrite/dom/DocumentConverter.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.dom;
16
7
import org.jetbrains.annotations.NotNull;
8
import org.jsoup.Jsoup;
9
import org.jsoup.helper.W3CDom;
10
import org.jsoup.node
A src/main/java/com/keenwrite/dom/DocumentParser.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.dom;
6
7
import org.w3c.dom.*;
8
import org.xml.sax.InputSource;
9
import org.xml.sax.SAXException;
10
11
import javax.xml.parsers.DocumentBuilder;
12
import javax.xml.parsers.DocumentBuilderFactory;
13
import javax.xml.transform.Transformer;
14
import javax.xml.transform.TransformerException;
15
import javax.xml.transform.TransformerFactory;
16
import javax.xml.transform.dom.DOMSource;
17
import javax.xml.transform.stream.StreamResult;
18
import javax.xml.xpath.XPath;
19
import javax.xml.xpath.XPathExpression;
20
import javax.xml.xpath.XPathExpressionException;
21
import javax.xml.xpath.XPathFactory;
22
import java.io.*;
23
import java.nio.file.Path;
24
import java.util.HashMap;
25
import java.util.Map;
26
import java.util.function.Consumer;
27
28
import static com.keenwrite.events.StatusEvent.clue;
29
import static com.keenwrite.io.SysFile.toFile;
30
import static java.nio.charset.StandardCharsets.UTF_16;
31
import static java.nio.charset.StandardCharsets.UTF_8;
32
import static java.nio.file.Files.write;
33
import static javax.xml.transform.OutputKeys.*;
34
import static javax.xml.xpath.XPathConstants.NODESET;
35
36
/**
37
 * Responsible for initializing an XML parser.
38
 */
39
public class DocumentParser {
40
  private static final String LOAD_EXTERNAL_DTD =
41
    "http://apache.org/xml/features/nonvalidating/load-external-dtd";
42
  private static final String INDENT_AMOUNT =
43
    "{http://xml.apache.org/xslt}indent-amount";
44
45
  private static final ByteArrayOutputStream sWriter =
46
    new ByteArrayOutputStream( 65536 );
47
  private static final OutputStreamWriter sOutput =
48
    new OutputStreamWriter( sWriter, UTF_8 );
49
50
  /**
51
   * Caches {@link XPathExpression}s to avoid re-compiling.
52
   */
53
  private static final Map<String, XPathExpression> sXpaths = new HashMap<>();
54
55
  private static final DocumentBuilderFactory sDocumentFactory;
56
  private static DocumentBuilder sDocumentBuilder;
57
  private static Transformer sTransformer;
58
  private static final XPath sXpath = XPathFactory.newInstance().newXPath();
59
60
  public static final DOMImplementation sDomImplementation;
61
62
  static {
63
    sDocumentFactory = DocumentBuilderFactory.newInstance();
64
65
    sDocumentFactory.setValidating( false );
66
    sDocumentFactory.setAttribute( LOAD_EXTERNAL_DTD, false );
67
    sDocumentFactory.setNamespaceAware( true );
68
    sDocumentFactory.setIgnoringComments( true );
69
    sDocumentFactory.setIgnoringElementContentWhitespace( true );
70
71
    DOMImplementation domImplementation;
72
73
    try {
74
      sDocumentBuilder = sDocumentFactory.newDocumentBuilder();
75
      domImplementation = sDocumentBuilder.getDOMImplementation();
76
      sTransformer = TransformerFactory.newInstance().newTransformer();
77
78
      // Ensure Unicode characters (emojis) are encoded correctly.
79
      sTransformer.setOutputProperty( ENCODING, UTF_16.toString() );
80
      sTransformer.setOutputProperty( OMIT_XML_DECLARATION, "yes" );
81
      sTransformer.setOutputProperty( METHOD, "xml" );
82
      sTransformer.setOutputProperty( INDENT, "no" );
83
      sTransformer.setOutputProperty( INDENT_AMOUNT, "2" );
84
    } catch( final Exception ex ) {
85
      clue( ex );
86
      domImplementation = sDocumentBuilder.getDOMImplementation();
87
    }
88
89
    sDomImplementation = domImplementation;
90
  }
91
92
  public static Document newDocument() {
93
    return sDocumentBuilder.newDocument();
94
  }
95
96
  /**
97
   * Creates a new document object model based on the given XML document
98
   * string. This will return an empty document if the document could not
99
   * be parsed.
100
   *
101
   * @param xml The document text to convert into a DOM.
102
   * @return The DOM that represents the given XML data.
103
   */
104
  public static Document parse( final String xml ) {
105
    assert xml != null;
106
107
    final var input = new InputSource();
108
109
    try( final var reader = new StringReader( xml ) ) {
110
      input.setEncoding( UTF_8.toString() );
111
      input.setCharacterStream( reader );
112
113
      return sDocumentBuilder.parse( input );
114
    } catch( final Throwable t ) {
115
      clue( t );
116
117
      return sDocumentBuilder.newDocument();
118
    }
119
  }
120
121
  /**
122
   * Parses the given file contents into a document object model.
123
   *
124
   * @param doc The source XML document to parse.
125
   * @return The file as a document object model.
126
   * @throws IOException  Could not open the document.
127
   * @throws SAXException Could not read the XML file content.
128
   */
129
  public static Document parse( final File doc )
130
    throws IOException, SAXException {
131
    assert doc != null;
132
133
    try( final var in = new FileInputStream( doc ) ) {
134
      return parse( in );
135
    }
136
  }
137
138
  /**
139
   * Parses the given file contents into a document object model. Callers
140
   * must close the stream.
141
   *
142
   * @param doc The source XML document to parse.
143
   * @return The {@link InputStream} converted to a document object model.
144
   * @throws IOException  Could not open the document.
145
   * @throws SAXException Could not read the XML file content.
146
   */
147
  public static Document parse( final InputStream doc )
148
    throws IOException, SAXException {
149
    assert doc != null;
150
151
    return sDocumentBuilder.parse( doc );
152
  }
153
154
  /**
155
   * Allows an operation to be applied for every node in the document that
156
   * matches a given tag name pattern.
157
   *
158
   * @param document Document to traverse.
159
   * @param xpath    Document elements to find via {@link XPath} expression.
160
   * @param consumer The consumer to call for each matching document node.
161
   */
162
  public static void visit(
163
    final Document document,
164
    final CharSequence xpath,
165
    final Consumer<Node> consumer ) {
166
    assert document != null;
167
    assert consumer != null;
168
169
    try {
170
      final var expr = compile( xpath );
171
      final var nodeSet = expr.evaluate( document, NODESET );
172
173
      if( nodeSet instanceof NodeList nodes ) {
174
        for( int i = 0, len = nodes.getLength(); i < len; i++ ) {
175
          consumer.accept( nodes.item( i ) );
176
        }
177
      }
178
    } catch( final Exception ex ) {
179
      clue( ex );
180
    }
181
  }
182
183
  public static Node createMeta(
184
    final Document document, final Map.Entry<String, String> entry ) {
185
    assert document != null;
186
    assert entry != null;
187
188
    final var node = document.createElement( "meta" );
189
190
    node.setAttribute( "name", entry.getKey() );
191
    node.setAttribute( "content", entry.getValue() );
192
193
    return node;
194
  }
195
196
  public static Node createEncoding(
197
    final Document document, final String encoding
198
  ) {
199
    assert document != null;
200
    assert encoding != null;
201
202
    final var node = document.createElement( "meta" );
203
204
    node.setAttribute( "charset", encoding );
205
206
    return node;
207
  }
208
209
  public static Node createElement(
210
    final Document doc, final String nodeName, final String nodeValue ) {
211
    assert doc != null;
212
    assert nodeName != null;
213
    assert !nodeName.isBlank();
214
215
    final var node = doc.createElement( nodeName );
216
217
    if( nodeValue != null ) {
218
      node.setTextContent( nodeValue );
219
    }
220
221
    return node;
222
  }
223
224
  public static String toString( final Document xhtml ) {
225
    assert xhtml != null;
226
227
    try( final var writer = new StringWriter() ) {
228
      final var result = new StreamResult( writer );
229
230
      transform( xhtml, result );
231
232
      return writer.toString();
233
    } catch( final Exception ex ) {
234
      clue( ex );
235
      return "";
236
    }
237
  }
238
239
  public static String transform( final Element root )
240
    throws IOException, TransformerException {
241
    assert root != null;
242
243
    try( final var writer = new StringWriter() ) {
244
      transform( root.getOwnerDocument(), new StreamResult( writer ) );
245
246
      return writer.toString();
247
    }
248
  }
249
250
  /**
251
   * Remove whitespace, comments, and XML/DOCTYPE declarations to make
252
   * processing work with ConTeXt.
253
   *
254
   * @param path The SVG file to process.
255
   * @throws Exception The file could not be processed.
256
   */
257
  public static void sanitize( final Path path ) throws Exception {
258
    assert path != null;
259
260
    // Preprocessing the SVG image is a single-threaded operation, no matter
261
    // how many SVG images are in the document to typeset.
262
    sWriter.reset();
263
264
    final var target = new StreamResult( sOutput );
265
    final var source = sDocumentBuilder.parse( toFile( path ) );
266
267
    transform( source, target );
268
    write( path, sWriter.toByteArray() );
269
  }
270
271
  /**
272
   * Converts a string into an {@link XPathExpression}, which may be used to
273
   * extract elements from a {@link Document} object model.
274
   *
275
   * @param cs The string to convert to an {@link XPathExpression}.
276
   * @return {@code null} if there was an error compiling the xpath.
277
   */
278
  public static XPathExpression compile( final CharSequence cs ) {
279
    assert cs != null;
280
281
    final var xpath = cs.toString();
282
283
    return sXpaths.computeIfAbsent( xpath, k -> {
284
      try {
285
        return sXpath.compile( xpath );
286
      } catch( final XPathExpressionException ex ) {
287
        clue( ex );
288
        return null;
289
      }
290
    } );
291
  }
292
293
  /**
294
   * Streams an instance of {@link Document} as a plain text XML document.
295
   *
296
   * @param src The source document to transform.
297
   * @param dst The destination location to write the transformed version.
298
   * @throws TransformerException Could not transform the document.
299
   */
300
  private static void transform( final Document src, final StreamResult dst )
301
    throws TransformerException {
302
    sTransformer.transform( new DOMSource( src ), dst );
303
  }
304
305
  /**
306
   * Use the {@code static} constants and methods, not an instance, at least
307
   * until an iterable sub-interface is written.
308
   */
309
  private DocumentParser() {}
310
}
1311
A src/main/java/com/keenwrite/editors/TextDefinition.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors;
16
7
import com.keenwrite.editors.definition.DefinitionEditor;
8
import com.keenwrite.editors.definition.DefinitionTreeItem;
9
import com.keenwrite.editors
A src/main/java/com/keenwrite/editors/TextEditor.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors;
16
7
import com.keenwrite.editors.common.Caret;
8
import javafx.scene.control.IndexRange;
9
import org.fxmisc.flowless.VirtualizedScrollPane;
10
import org.fx
A src/main/java/com/keenwrite/editors/TextResource.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors;
16
7
import com.keenwrite.io.MediaType;
8
import com.keenwrite.util.EncodingDetector;
9
import javafx.beans.property.ReadOnlyBooleanProperty;
10
import javafx.scene.Node;
11
return new EncodingDetector().detect( bytes
A src/main/java/com/keenwrite/editors/common/Caret.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.editors.common;
13
4
import com.
5
import java.util.function.Supp/**
6
     * Sets the {@link Supplier} for the cnumber.
7
     *
8
     * @param paragraph Returns the document caret paragraph index.
9
     *//**
10
     * Sets the {@link Supplier} for the total number of document paragraphs.
11
     *
12
     * @param paragraphs Returns the document paragraph count.
13
     *//**
14
     * Sets the {@link Supplier} for the caret's current character offset
15
     * into the current paragraph.
16
     *
17
     * @param paraOffset Returns the caret's paragraph character index.
18
     *//**
19
     * Sets the {@link Supplier} for the caret's current document position.
20
     * A value of 0 represents the start of the document.
21
     *
22
     * @param textOffset Returns the tex/**
23
     * Sets the {@link Supplier} for the document's total character count.
24
     *
25
     * @param textLength Returns the total character count
A src/main/java/com/keenwrite/editors/common/ScrollEventHandler.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors.common;
16
7
import com.keenwrite.events.ScrollLockEvent;
8
import javafx.beans.property.BooleanProperty;
9
import javafx.beans.property.SimpleBooleanProper
A src/main/java/com/keenwrite/editors/common/VariableNameInjector.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors.common;
16
7
import com.keenwrite.editors.TextDefinition;
8
import com.keenwrite.editors.TextEditor;
9
import com.keenwrite.editors.definition.DefinitionTre
A src/main/java/com/keenwrite/editors/definition/DefinitionEditor.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors.definition;
6
7
import com.keenwrite.constants.Constants;
8
import com.keenwrite.editors.TextDefinition;
9
import com.keenwrite.events.InsertDefinitionEvent;
10
import com.keenwrite.events.TextDefinitionFocusEvent;
11
import com.keenwrite.processors.r.Engine;
12
import com.keenwrite.ui.tree.AltTreeView;
13
import com.keenwrite.ui.tree.TreeItemConverter;
14
import javafx.beans.property.BooleanProperty;
15
import javafx.beans.property.ReadOnlyBooleanProperty;
16
import javafx.beans.property.SimpleBooleanProperty;
17
import javafx.beans.value.ObservableValue;
18
import javafx.collections.ObservableList;
19
import javafx.event.ActionEvent;
20
import javafx.event.Event;
21
import javafx.event.EventHandler;
22
import javafx.scene.Node;
23
import javafx.scene.control.*;
24
import javafx.scene.input.KeyEvent;
25
import javafx.scene.layout.BorderPane;
26
import javafx.scene.layout.HBox;
27
28
import java.io.File;
29
import java.nio.charset.Charset;
30
import java.util.*;
31
32
import static com.keenwrite.Messages.get;
33
import static com.keenwrite.constants.Constants.*;
34
import static com.keenwrite.events.StatusEvent.clue;
35
import static com.keenwrite.ui.fonts.IconFactory.createGraphic;
36
import static javafx.geometry.Pos.CENTER;
37
import static javafx.geometry.Pos.TOP_CENTER;
38
import static javafx.scene.control.SelectionMode.MULTIPLE;
39
import static javafx.scene.control.TreeItem.childrenModificationEvent;
40
import static javafx.scene.control.TreeItem.valueChangedEvent;
41
import static javafx.scene.input.KeyEvent.KEY_PRESSED;
42
43
/**
44
 * Provides the user interface that holds a {@link TreeView}, which
45
 * allows users to interact with key/value pairs loaded from the
46
 * document parser and adapted using a {@link TreeTransformer}.
47
 */
48
public final class DefinitionEditor extends BorderPane
49
  implements TextDefinition {
50
51
  /**
52
   * Contains the root that is added to the view.
53
   */
54
  private final DefinitionTreeItem<String> mTreeRoot = createRootTreeItem();
55
56
  /**
57
   * Contains a view of the definitions.
58
   */
59
  private final TreeView<String> mTreeView =
60
    new AltTreeView<>( mTreeRoot, new TreeItemConverter() );
61
62
  /**
63
   * Used to adapt the structured document into a {@link TreeView}.
64
   */
65
  private final TreeTransformer mTreeTransformer;
66
67
  /**
68
   * Handlers for key press events.
69
   */
70
  private final Set<EventHandler<? super KeyEvent>> mKeyEventHandlers
71
    = new HashSet<>();
72
73
  private final Map<String, String> mDefinitions = new HashMap<>();
74
75
  /**
76
   * Opened file's character encoding, or {@link Constants#DEFAULT_CHARSET} if
77
   * either no encoding could be determined or this is a new (empty) file.
78
   */
79
  private final Charset mEncoding;
80
81
  /**
82
   * Tracks whether the in-memory definitions have changed with respect to the
83
   * persisted definitions.
84
   */
85
  private final BooleanProperty mModified = new SimpleBooleanProperty();
86
87
  /**
88
   * File being edited by this editor instance, which may be renamed.
89
   */
90
  private File mFile;
91
92
  /**
93
   * This is provided for unit tests that are not backed by files.
94
   *
95
   * @param treeTransformer Responsible for transforming the definitions into
96
   *                        {@link TreeItem} instances.
97
   */
98
  public DefinitionEditor(
99
    final TreeTransformer treeTransformer ) {
100
    this( DEFINITION_DEFAULT, treeTransformer );
101
  }
102
103
  /**
104
   * Constructs a definition pane with a given tree view root.
105
   *
106
   * @param file The file of definitions to maintain through the UI.
107
   */
108
  public DefinitionEditor(
109
    final File file,
110
    final TreeTransformer treeTransformer ) {
111
    assert file != null;
112
    assert treeTransformer != null;
113
114
    mFile = file;
115
    mTreeTransformer = treeTransformer;
116
117
    mTreeView.setContextMenu( createContextMenu() );
118
    mTreeView.addEventFilter( KEY_PRESSED, this::keyEventFilter );
119
    mTreeView.focusedProperty().addListener( this::focused );
120
    getSelectionModel().setSelectionMode( MULTIPLE );
121
122
    final var buttonBar = new HBox();
123
    buttonBar.getChildren().addAll(
124
      createButton( "create", _ -> createDefinition() ),
125
      createButton( "rename", _ -> renameDefinition() ),
126
      createButton( "delete", _ -> deleteDefinitions() )
127
    );
128
    buttonBar.setAlignment( CENTER );
129
    buttonBar.setSpacing( UI_CONTROL_SPACING );
130
    setTop( buttonBar );
131
    setCenter( mTreeView );
132
    setAlignment( buttonBar, TOP_CENTER );
133
134
    mEncoding = open( mFile );
135
    updateDefinitions( getDefinitions(), getTreeView().getRoot() );
136
137
    // After the file is opened, watch for changes, not before. Otherwise,
138
    // upon saving, users will be prompted to save a file that hasn't had
139
    // any modifications (from their perspective).
140
    addTreeChangeHandler( _ -> {
141
      mModified.set( true );
142
      updateDefinitions( getDefinitions(), getTreeView().getRoot() );
143
    } );
144
  }
145
146
  /**
147
   * Replaces the given list of variable definitions with a flat hierarchy
148
   * of the converted {@link TreeView} root.
149
   *
150
   * @param definitions The definition map to update.
151
   * @param root        The values to flatten then insert into the map.
152
   */
153
  private void updateDefinitions(
154
    final Map<String, String> definitions,
155
    final TreeItem<String> root ) {
156
    definitions.clear();
157
    definitions.putAll( TreeItemMapper.convert( root ) );
158
    Engine.clear();
159
  }
160
161
  /**
162
   * Returns the variable definitions.
163
   *
164
   * @return The definition map.
165
   */
166
  @Override
167
  public Map<String, String> getDefinitions() {
168
    return mDefinitions;
169
  }
170
171
  @Override
172
  public void setText( final String document ) {
173
    final var foster = mTreeTransformer.transform( document );
174
    final var biological = getTreeRoot();
175
176
    for( final var child : foster.getChildren() ) {
177
      biological.getChildren().add( child );
178
    }
179
180
    getTreeView().refresh();
181
  }
182
183
  @Override
184
  public String getText() {
185
    final var result = new StringBuilder( 32768 );
186
187
    try {
188
      result.append( mTreeTransformer.transform( getTreeView().getRoot() ) );
189
190
      final var problem = isTreeWellFormed();
191
      problem.ifPresent( node -> clue( "yaml.error.tree.form", node ) );
192
    } catch( final Exception ex ) {
193
      // Catch errors while checking for a well-formed tree (e.g., stack smash).
194
      // Also catch any transformation exceptions (e.g., Json processing).
195
      clue( ex );
196
    }
197
198
    return result.toString();
199
  }
200
201
  @Override
202
  public File getFile() {
203
    return mFile;
204
  }
205
206
  @Override
207
  public void rename( final File file ) {
208
    mFile = file;
209
  }
210
211
  @Override
212
  public Charset getEncoding() {
213
    return mEncoding;
214
  }
215
216
  @Override
217
  public Node getNode() {
218
    return this;
219
  }
220
221
  @Override
222
  public ReadOnlyBooleanProperty modifiedProperty() {
223
    return mModified;
224
  }
225
226
  @Override
227
  public void clearModifiedProperty() {
228
    mModified.setValue( false );
229
  }
230
231
  private Button createButton(
232
    final String msgKey, final EventHandler<ActionEvent> eventHandler ) {
233
    final var keyPrefix = STR."\{Constants.ACTION_PREFIX}definition.\{msgKey}";
234
    final var button = new Button( get( STR."\{keyPrefix}.text" ) );
235
    final var graphic = createGraphic( get( STR."\{keyPrefix}.icon" ) );
236
237
    button.setOnAction( eventHandler );
238
    button.setGraphic( graphic );
239
    button.setTooltip( new Tooltip( get( STR."\{keyPrefix}.tooltip" ) ) );
240
241
    return button;
242
  }
243
244
  /**
245
   * Informs the caller of whenever any {@link TreeItem} in the {@link TreeView}
246
   * is modified. The modifications include: item value changes, item additions,
247
   * and item removals.
248
   * <p>
249
   * Safe to call multiple times; if a handler is already registered, the
250
   * old handler is used.
251
   * </p>
252
   *
253
   * @param handler The handler to call whenever any {@link TreeItem} changes.
254
   */
255
  public void addTreeChangeHandler(
256
    final EventHandler<TreeItem.TreeModificationEvent<Event>> handler ) {
257
    final var root = getTreeView().getRoot();
258
    root.addEventHandler( valueChangedEvent(), handler );
259
    root.addEventHandler( childrenModificationEvent(), handler );
260
  }
261
262
  /**
263
   * Answers whether the {@link TreeItem}s in the {@link TreeView} are suitably
264
   * well-formed for export. A tree is considered well-formed if the following
265
   * conditions are met:
266
   *
267
   * <ul>
268
   *   <li>The root node contains at least one child node having a leaf.</li>
269
   *   <li>There are no leaf nodes with sibling leaf nodes.</li>
270
   * </ul>
271
   *
272
   * @return {@code null} if the document is well-formed, otherwise the
273
   * problematic child {@link TreeItem}.
274
   */
275
  public Optional<TreeItem<String>> isTreeWellFormed() {
276
    final var root = getTreeView().getRoot();
277
278
    for( final var child : root.getChildren() ) {
279
      final var problemChild = isWellFormed( child );
280
281
      if( child.isLeaf() || problemChild != null ) {
282
        return Optional.ofNullable( problemChild );
283
      }
284
    }
285
286
    return Optional.empty();
287
  }
288
289
  /**
290
   * Determines whether the document is well-formed by ensuring that
291
   * child branches do not contain multiple leaves.
292
   *
293
   * @param item The subtree to check for well-formedness.
294
   * @return {@code null} when the tree is well-formed, otherwise the
295
   * problematic {@link TreeItem}.
296
   */
297
  private TreeItem<String> isWellFormed( final TreeItem<String> item ) {
298
    int childLeafs = 0;
299
    int childBranches = 0;
300
301
    for( final var child : item.getChildren() ) {
302
      if( child.isLeaf() ) {
303
        childLeafs++;
304
      }
305
      else {
306
        childBranches++;
307
      }
308
309
      final var problemChild = isWellFormed( child );
310
311
      if( problemChild != null ) {
312
        return problemChild;
313
      }
314
    }
315
316
    return ((childBranches > 0 && childLeafs == 0) ||
317
            (childBranches == 0 && childLeafs <= 1))
318
      ? null
319
      : item;
320
  }
321
322
  @Override
323
  public DefinitionTreeItem<String> findLeafExact( final String text ) {
324
    return getTreeRoot().findLeafExact( text );
325
  }
326
327
  @Override
328
  public DefinitionTreeItem<String> findLeafContains( final String text ) {
329
    return getTreeRoot().findLeafContains( text );
330
  }
331
332
  @Override
333
  public DefinitionTreeItem<String> findLeafContainsNoCase(
334
    final String text ) {
335
    return getTreeRoot().findLeafContainsNoCase( text );
336
  }
337
338
  @Override
339
  public DefinitionTreeItem<String> findLeafStartsWith( final String text ) {
340
    return getTreeRoot().findLeafStartsWith( text );
341
  }
342
343
  public void select( final TreeItem<String> item ) {
344
    getSelectionModel().clearSelection();
345
    getSelectionModel().select( getTreeView().getRow( item ) );
346
  }
347
348
  /**
349
   * Collapses the tree, recursively.
350
   */
351
  public void collapse() {
352
    collapse( getTreeRoot().getChildren() );
353
  }
354
355
  /**
356
   * Collapses the tree, recursively.
357
   *
358
   * @param <T>   The type of tree item to expand (usually String).
359
   * @param nodes The nodes to collapse.
360
   */
361
  private <T> void collapse( final ObservableList<TreeItem<T>> nodes ) {
362
    for( final var node : nodes ) {
363
      node.setExpanded( false );
364
      collapse( node.getChildren() );
365
    }
366
  }
367
368
  /**
369
   * @return {@code true} when the user is editing a {@link TreeItem}.
370
   */
371
  private boolean isEditingTreeItem() {
372
    return getTreeView().editingItemProperty().getValue() != null;
373
  }
374
375
  /**
376
   * Changes to edit mode for the selected item.
377
   */
378
  @Override
379
  public void renameDefinition() {
380
    getTreeView().edit( getSelectedItem() );
381
  }
382
383
  /**
384
   * Removes all selected items from the {@link TreeView}.
385
   */
386
  @Override
387
  public void deleteDefinitions() {
388
    for( final var item : getSelectedItems() ) {
389
      final var parent = item.getParent();
390
391
      if( parent != null ) {
392
        parent.getChildren().remove( item );
393
      }
394
    }
395
  }
396
397
  /**
398
   * Deletes the selected item.
399
   */
400
  private void deleteSelectedItem() {
401
    final var c = getSelectedItem();
402
    getSiblings( c ).remove( c );
403
  }
404
405
  private void insertSelectedItem() {
406
    if( getSelectedItem() instanceof DefinitionTreeItem<String> node ) {
407
      if( node.isLeaf() ) {
408
        InsertDefinitionEvent.fire( node );
409
      }
410
    }
411
  }
412
413
  /**
414
   * Adds a new item under the selected item (or root if nothing is selected).
415
   * There are a few conditions to consider: when adding to the root,
416
   * when adding to a leaf, and when adding to a non-leaf. Items added to the
417
   * root must contain two items: a key and a value.
418
   */
419
  @Override
420
  public void createDefinition() {
421
    final var value = createDefinitionTreeItem();
422
    getSelectedItem().getChildren().add( value );
423
    expand( value );
424
    select( value );
425
  }
426
427
  private ContextMenu createContextMenu() {
428
    final var menu = new ContextMenu();
429
    final var items = menu.getItems();
430
431
    addMenuItem( items, STR."\{ACTION_PREFIX}definition.create.text" )
432
      .setOnAction( _ -> createDefinition() );
433
    addMenuItem( items, STR."\{ACTION_PREFIX}definition.rename.text" )
434
      .setOnAction( _ -> renameDefinition() );
435
    addMenuItem( items, STR."\{ACTION_PREFIX}definition.delete.text" )
436
      .setOnAction( _ -> deleteSelectedItem() );
437
    addMenuItem( items, STR."\{ACTION_PREFIX}definition.insert.text" )
438
      .setOnAction( _ -> insertSelectedItem() );
439
440
    return menu;
441
  }
442
443
  /**
444
   * Executes hot-keys for edits to the definition tree.
445
   *
446
   * @param event Contains the key code of the key that was pressed.
447
   */
448
  private void keyEventFilter( final KeyEvent event ) {
449
    if( !isEditingTreeItem() ) {
450
      switch( event.getCode() ) {
451
        case ENTER -> {
452
          expand( getSelectedItem() );
453
          event.consume();
454
        }
455
456
        case DELETE -> deleteDefinitions();
457
        case INSERT -> createDefinition();
458
459
        case R -> {
460
          if( event.isControlDown() ) {
461
            renameDefinition();
462
          }
463
        }
464
465
        default -> {}
466
      }
467
468
      for( final var handler : getKeyEventHandlers() ) {
469
        handler.handle( event );
470
      }
471
    }
472
  }
473
474
  /**
475
   * Called when the editor's input focus changes. This will fire an event
476
   * for subscribers.
477
   *
478
   * @param ignored Not used.
479
   * @param o       The old input focus property value.
480
   * @param n       The new input focus property value.
481
   */
482
  private void focused(
483
    final ObservableValue<? extends Boolean> ignored,
484
    final Boolean o,
485
    final Boolean n ) {
486
    if( n != null && n ) {
487
      TextDefinitionFocusEvent.fire( this );
488
    }
489
  }
490
491
  /**
492
   * Adds a menu item to a list of menu items.
493
   *
494
   * @param items    The list of menu items to append to.
495
   * @param labelKey The resource bundle key name for the menu item's label.
496
   * @return The menu item added to the list of menu items.
497
   */
498
  private MenuItem addMenuItem(
499
    final List<MenuItem> items, final String labelKey ) {
500
    final MenuItem menuItem = createMenuItem( labelKey );
501
    items.add( menuItem );
502
    return menuItem;
503
  }
504
505
  private MenuItem createMenuItem( final String labelKey ) {
506
    return new MenuItem( get( labelKey ) );
507
  }
508
509
  /**
510
   * Creates a new {@link TreeItem} that is intended to be the root-level item
511
   * added to the {@link TreeView}. This allows the root item to be
512
   * distinguished from the other items so that reference keys do not include
513
   * "Definition" as part of their name.
514
   *
515
   * @return A new {@link TreeItem}, never {@code null}.
516
   */
517
  private RootTreeItem<String> createRootTreeItem() {
518
    return new RootTreeItem<>( get( "Pane.definition.node.root.title" ) );
519
  }
520
521
  private DefinitionTreeItem<String> createDefinitionTreeItem() {
522
    return new DefinitionTreeItem<>( get( "Definition.menu.add.default" ) );
523
  }
524
525
  @Override
526
  public void requestFocus() {
527
    getTreeView().requestFocus();
528
  }
529
530
  /**
531
   * Expands the node to the root, recursively.
532
   *
533
   * @param <T>  The type of tree item to expand (usually String).
534
   * @param node The node to expand.
535
   */
536
  @Override
537
  public <T> void expand( final TreeItem<T> node ) {
538
    if( node != null ) {
539
      expand( node.getParent() );
540
      node.setExpanded( !node.isLeaf() );
541
    }
542
  }
543
544
  /**
545
   * Answers whether there are any definitions in the tree.
546
   *
547
   * @return {@code true} when there are no definitions; {@code false} when
548
   * there's at least one definition.
549
   */
550
  @Override
551
  public boolean isEmpty() {
552
    return getTreeRoot().isEmpty();
553
  }
554
555
  /**
556
   * Returns the actively selected item in the tree.
557
   *
558
   * @return The selected item, or the tree root item if no item is selected.
559
   */
560
  public TreeItem<String> getSelectedItem() {
561
    final var item = getSelectionModel().getSelectedItem();
562
    return item == null ? getTreeRoot() : item;
563
  }
564
565
  /**
566
   * Returns the {@link TreeView} that contains the definition hierarchy.
567
   *
568
   * @return A non-null instance.
569
   */
570
  private TreeView<String> getTreeView() {
571
    return mTreeView;
572
  }
573
574
  /**
575
   * Returns the root of the tree.
576
   *
577
   * @return The first node added to the definition tree.
578
   */
579
  private DefinitionTreeItem<String> getTreeRoot() {
580
    return mTreeRoot;
581
  }
582
583
  private ObservableList<TreeItem<String>> getSiblings(
584
    final TreeItem<String> item ) {
585
    final var root = getTreeView().getRoot();
586
    final var parent = (item == null || item == root)
587
      ? root
588
      : item.getParent();
589
590
    return parent.getChildren();
591
  }
592
593
  private MultipleSelectionModel<TreeItem<String>> getSelectionModel() {
594
    return getTreeView().getSelectionModel();
595
  }
596
597
  /**
598
   * Returns a copy of all the selected items.
599
   *
600
   * @return A list, possibly empty, containing all selected items in the
601
   * {@link TreeView}.
602
   */
603
  private List<TreeItem<String>> getSelectedItems() {
604
    return new ArrayList<>( getSelectionModel().getSelectedItems() );
605
  }
606
607
  private Set<EventHandler<? super KeyEvent>> getKeyEventHandlers() {
608
    return mKeyEventHandlers;
609
  }
610
}
1611
A src/main/java/com/keenwrite/editors/definition/DefinitionTreeItem.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors.definition;
6
7
import com.keenwrite.util.Diacritics;
8
import javafx.scene.control.TreeItem;
19
10
import java.util.Stack;
11
import java.util.function.BiFuncti
A src/main/java/com/keenwrite/editors/definition/RootTreeItem.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors.definition;
6
7
import javafx.scene.control.TreeItem;
8
import javafx.scene.control.TreeView;
19
10
/**
11
 * Marker interface for top-most {@link TreeItem}. Thi
A src/main/java/com/keenwrite/editors/definition/TreeItemMapper.java
1
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editorskeenwrite.preview.HtmlPreviewutil.InterpolatingMapHashMap;
A src/main/java/com/keenwrite/editors/definition/TreeTransformer.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors.definition;
6
7
import javafx.scene.control.TreeItem;
18
9
/**
10
 * 
A src/main/java/com/keenwrite/editors/definition/package-info.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
3
/**
4
 * This package contains classes that pertain to hierarchical, structured
5
 * data formats, which can be used as interpolated variables.
6
 */
7
package com.keenwrite.editors.definition;
18
A src/main/java/com/keenwrite/editors/definition/yaml/YamlTreeTransformer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.editors.definition.yaml;
3
4
import com.fasterxml.jackson.databind.JsonNode;
5
import com.fasterxml.jackson.databind.ObjectMapper;
6
import com.fasterxml.jackson.databind.node.ObjectNode;
7
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
8
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
9
import com.keenwrite.editors.definition.DefinitionTreeItem;
10
import com.keenwrite.editors.definition.TreeTransformer;
11
import javafx.scene.control.TreeItem;
12
import javafx.scene.control.TreeView;
13
14
import java.util.Map.Entry;
15
16
import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.*;
17
import static com.keenwrite.events.StatusEvent.clue;
18
19
/**
20
 * Transforms a {@link JsonNode} hierarchy into a tree that can be displayed
21
 * in a user interface and vice-versa.
22
 */
23
public final class YamlTreeTransformer implements TreeTransformer {
24
  private static final YAMLFactory sFactory;
25
  private static final YAMLMapper sMapper;
26
27
  static {
28
    sFactory = new YAMLFactory();
29
    sFactory.configure( MINIMIZE_QUOTES, true );
30
    sFactory.configure( SPLIT_LINES, false );
31
    sMapper = new YAMLMapper( sFactory );
32
  }
33
34
  /**
35
   * Constructs a new instance that will use the given path to read the object
36
   * hierarchy from a data source.
37
   */
38
  public YamlTreeTransformer() {
39
  }
40
41
  @Override
42
  public String transform( final TreeItem<String> treeItem ) {
43
    try {
44
      final var root = sMapper.createObjectNode();
45
46
      // Iterate over the root item's children. The root item is used by the
47
      // application to ensure definitions can always be added to a tree, as
48
      // such it is not meant to be exported, only its children.
49
      for( final var child : treeItem.getChildren() ) {
50
        transform( child, root );
51
      }
52
53
      return sMapper.writeValueAsString( root );
54
    } catch( final Exception ex ) {
55
      clue( ex );
56
      throw new RuntimeException( ex );
57
    }
58
  }
59
60
  /**
61
   * Converts a YAML document to a {@link TreeItem} based on the document
62
   * keys.
63
   *
64
   * @param document The YAML document to convert to a hierarchy of
65
   *                 {@link TreeItem} instances.
66
   * @throws StackOverflowError If infinite recursion is encountered.
67
   */
68
  @Override
69
  public TreeItem<String> transform( final String document ) {
70
    final var jsonNode = toJson( document );
71
    final var rootItem = createTreeItem( "root" );
72
73
    transform( jsonNode, rootItem );
74
75
    return rootItem;
76
  }
77
78
  private JsonNode toJson( final String yaml ) {
79
    try {
80
      return new ObjectMapper( sFactory ).readTree( yaml );
81
    } catch( final Exception ex ) {
82
      // Ensure that a document root node exists.
83
      return new ObjectMapper().createObjectNode();
84
    }
85
  }
86
87
  /**
88
   * Recursive method to generate an object hierarchy that represents the
89
   * given {@link TreeItem} hierarchy.
90
   *
91
   * @param item The {@link TreeItem} to reproduce as an object hierarchy.
92
   * @param node The {@link ObjectNode} to update to reflect the
93
   *             {@link TreeItem} hierarchy.
94
   */
95
  private void transform( final TreeItem<String> item, ObjectNode node ) {
96
    final var children = item.getChildren();
97
98
    // If the current item has more than one non-leaf child, it's an
99
    // object node and must become a new nested object.
100
    if( !(children.size() == 1 && children.getFirst().isLeaf()) ) {
101
      node = node.putObject( item.getValue() );
102
    }
103
104
    for( final var child : children ) {
105
      if( child.isLeaf() ) {
106
        node.put( item.getValue(), child.getValue() );
107
      }
108
      else {
109
        transform( child, node );
110
      }
111
    }
112
  }
113
114
  /**
115
   * Iterate over a given root node (at any level of the tree) and adapt each
116
   * leaf node.
117
   *
118
   * @param node A JSON node (YAML node) to adapt.
119
   * @param item The tree item to use as the root when processing the node.
120
   * @throws StackOverflowError If infinite recursion is encountered.
121
   */
122
  private void transform( final JsonNode node, final TreeItem<String> item ) {
123
    node.fields().forEachRemaining( leaf -> transform( leaf, item ) );
124
  }
125
126
  /**
127
   * Recursively adapt each rootNode to a corresponding rootItem.
128
   *
129
   * @param node The node to adapt.
130
   * @param item The item to adapt using the node's key.
131
   * @throws StackOverflowError If infinite recursion is encountered.
132
   */
133
  private void transform(
134
    final Entry<String, JsonNode> node, final TreeItem<String> item ) {
135
    final var leafNode = node.getValue();
136
    final var key = node.getKey();
137
    final var leaf = createTreeItem( key );
138
139
    if( leafNode.isValueNode() ) {
140
      leaf.getChildren().add( createTreeItem( node.getValue().asText() ) );
141
    }
142
143
    item.getChildren().add( leaf );
144
145
    if( leafNode.isObject() ) {
146
      transform( leafNode, leaf );
147
    }
148
  }
149
150
  /**
151
   * Creates a new {@link TreeItem} that can be added to the {@link TreeView}.
152
   *
153
   * @param value The node's value.
154
   * @return A new {@link TreeItem}, never {@code null}.
155
   */
156
  private TreeItem<String> createTreeItem( final String value ) {
157
    return new DefinitionTreeItem<>( value );
158
  }
159
}
1160
A src/main/java/com/keenwrite/editors/definition/yaml/package-info.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
3
/**
4
 * This package contains classes that can parse YAML documents into a GUI
5
 * representation.
6
 */
7
package com.keenwrite.editors.def.yaml;
18
A src/main/java/com/keenwrite/editors/markdown/LinkVisitor.java
1
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
A src/main/java/com/keenwrite/editors/markdown/MarkdownEditor.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.editors.markdown;
16
7
import com.keenwrite.constants.Constants;
8
import com.keenwrite.editors.TextEditor;
9
import com.keenwrite.ed
A src/main/java/com/keenwrite/events/AppEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
16
7
import static com.keenwrite.
8
/**
9
 * Marker interface for all application events.
10
 */
11
public interface AppEvent {
A src/main/java/com/keenwrite/events/Bus.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.events;
3
4
import org.greenrobot.eventbus.EventBus;
15
6
/**
7
 * Responsible for delegating interactions to the event bus library. This
8
 * class decouples the rest
9
    .builder().logNoSubscriberMessages( false ).installDefaultEventBus
A src/main/java/com/keenwrite/events/CaretMovedEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import com.keenwrite.editors.common.Caret;
18
9
/**
10
 * Responsible for notifying when the caret has moved, which includes giving
11
 * focus to a different edi
A src/main/java/com/keenwrite/events/CaretNavigationEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import com.keenwrite.ui.outline.DocumentOutline;
18
9
/**
10
 * Collates information about a caret event, which is typically triggered when
11
 * the user do
A src/main/java/com/keenwrite/events/DocumentChangedEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
16
7
/**
8
 * Col
A src/main/java/com/keenwrite/events/ExportFailedEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
16
7
/**
8
 * Responsible for kicking off an alert message when exporting (e.g., to PDF)
9
 * fails. This can happen when the executable to typeset the docu
A src/main/java/com/keenwrite/events/FileOpenEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import java.net.URI;
18
9
/**
10
 * Collates information about a file requested to be opened. This can be calHTML preview panel
A src/main/java/com/keenwrite/events/FocusEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
16
7
/**
8
 * Collates information about an object that has gained focus. This is typically
9
 * used by text resource editors (such as text editors and def
A src/main/java/com/keenwrite/events/HyperlinkOpenEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import java.io.IOException;
8
import java.net.URI;
9
10
import static com.keenwrite.events.StatusEvent.clue;
111
12
/**
13
 * Collates information about a URL reqpublish()
A src/main/java/com/keenwrite/events/InsertDefinitionEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import com.keenwrite.editors.definition.DefinitionTreeItem;
8
9
/**
10
 * Collates information about a request to insert a reference to a
11
 * definition value into the active document.
12
 */
13
public class InsertDefinitionEvent<T> implements AppEvent {
14
15
  private final DefinitionTreeItem<T> mLeaf;
16
17
  private InsertDefinitionEvent( final DefinitionTreeItem<T> leaf ) {
18
    mLeaf = leaf;
19
  }
20
21
  public static <T> void fire( final DefinitionTreeItem<T> leaf ) {
22
    assert leaf != null;
23
    assert leaf.isLeaf();
24
25
    new InsertDefinitionEvent<>( leaf ).publish();
26
  }
27
28
  /**
29
   * Returns the {@link DefinitionTreeItem} that is to be inserted into the
30
   * active document.
31
   *
32
   * @return The item to insert (as a variable).
33
   */
34
  public DefinitionTreeItem<T> getLeaf() {
35
    return mLeaf;
36
  }
37
}
138
A src/main/java/com/keenwrite/events/ParseHeadingEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import com.keenwrite.processors.Processor;
18
9
/**
10
 * Collates information about a document heading that has been parsed, after
11
 * all pertinent {@lin
A src/main/java/com/keenwrite/events/ScrollLockEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import static java.awt.Toolkit.getDefaultToolkit;
8
import static java.awt.event.KeyEvent.VK_SCROLL_LOCK;
19
10
/**
11
 * Collates information about the scro
A src/main/java/com/keenwrite/events/StatusEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import java.util.List;
8
9
import static com.keenwrite.Messages.get;
10
import static com.keenwrite.constants.Constants.NEWLINE;
11
import static com.keenwrite.constants.Constants.STATUS_BAR_OK;
12
import static java.lang.String.format;
13
import static java.lang.String.join;
14
import static java.util.Arrays.stream;
15
16
/**
17
 * Collates information about an application issue. The issues can be
18
 * exceptions, state problems, parsing errors, and so forth.
19
 */
20
public final class StatusEvent implements AppEvent {
21
  private static final String ENGLISHIFY =
22
    "(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])";
23
24
  /**
25
   * Detailed information about a problem.
26
   */
27
  private final String mMessage;
28
29
  /**
30
   * Provides stack trace information that isolates the cause.
31
   */
32
  private final Throwable mProblem;
33
34
  /**
35
   * Constructs a new event that contains a problem description to help the
36
   * user resolve an issue encountered while using the application.
37
   *
38
   * @param message The human-readable message, typically displayed on-screen.
39
   */
40
  public StatusEvent( final String message ) {
41
    this( message, null );
42
  }
43
44
  /**
45
   * Constructs a new event that contains information about an unexpected issue.
46
   *
47
   * @param problem The issue encountered by the software, never {@code null}.
48
   */
49
  public StatusEvent( final Throwable problem ) {
50
    this( problem.getMessage(), problem );
51
  }
52
53
  /**
54
   * @param message The human-readable message text.
55
   * @param problem May be {@code null} if no exception was thrown.
56
   */
57
  public StatusEvent( final String message, final Throwable problem ) {
58
    mMessage = message == null ? "" : message;
59
    mProblem = problem;
60
  }
61
62
  /**
63
   * Returns the stack trace information for the issue encountered. This is
64
   * optional because usually a status message isn't an application error.
65
   *
66
   * @return Optional stack trace to pinpoint the problem area in the code.
67
   */
68
  public String getProblem() {
69
    // Arbitrary limit.
70
    final var sb = new StringBuilder( 1024 );
71
    final var trace = mProblem;
72
73
    if( trace != null ) {
74
      stream( trace.getStackTrace() )
75
        .limit( 150 )
76
        .toList()
77
        .forEach( e -> sb.append( e.toString() ).append( NEWLINE ) );
78
    }
79
80
    return sb.toString();
81
  }
82
83
  @Override
84
  public String toString() {
85
    // Not exactly sure how the message can be null, but it happened once!
86
    final var message = mMessage == null ? "UNKNOWN" : mMessage;
87
88
    return format( "%s%s%s",
89
                   message,
90
                   message.isBlank() ? "" : " ",
91
                   mProblem == null ? "" : toEnglish( mProblem ) );
92
  }
93
94
  /**
95
   * Separates the exception class name from TitleCase into lowercase,
96
   * space-separated words. This makes the exception look a little more like
97
   * English. Any {@link RuntimeException} instances passed into this method
98
   * will have the cause extracted, if possible.
99
   *
100
   * @param problem The exception that triggered the status event change.
101
   * @return A human-readable message with the exception name and the
102
   * exception's message.
103
   */
104
  private static String toEnglish( Throwable problem ) {
105
    assert problem != null;
106
107
    // Subclasses of RuntimeException must be subject to Englishification.
108
    if( problem.getClass().equals( RuntimeException.class ) ) {
109
      final var cause = problem.getCause();
110
      return cause == null ? problem.getMessage() : cause.getMessage();
111
    }
112
113
    final var className = problem.getClass().getSimpleName();
114
    final var words = join( " ", className.split( ENGLISHIFY ) );
115
    return format( "(%s: %s)", words.toLowerCase(), problem.getMessage() );
116
  }
117
118
  /**
119
   * Returns the message used to construct the event.
120
   *
121
   * @return The message for this event.
122
   */
123
  public String getMessage() {
124
    return mMessage;
125
  }
126
127
  /**
128
   * Resets the status bar to a default message. Indicates that there are no
129
   * issues to bring to the user's attention.
130
   */
131
  public static void clue() {
132
    fire( get( STATUS_BAR_OK, "OK" ) );
133
  }
134
135
  /**
136
   * Notifies listeners of a series of messages. This is useful when providing
137
   * users feedback of how third-party executables have failed.
138
   *
139
   * @param messages The lines of text to display.
140
   */
141
  public static void clue( final List<String> messages ) {
142
    messages.forEach( StatusEvent::fire );
143
  }
144
145
  /**
146
   * Notifies listeners of an error.
147
   *
148
   * @param key The message bundle key to look up.
149
   * @param t   The exception that caused the error.
150
   */
151
  public static void clue( final String key, final Throwable t ) {
152
    fire( get( key ), t );
153
  }
154
155
  /**
156
   * Notifies listeners of a custom message.
157
   *
158
   * @param key  The property key having a value to populate with arguments.
159
   * @param args The placeholder values to substitute into the key's value.
160
   */
161
  public static void clue( final String key, final Object... args ) {
162
    fire( get( key, args ) );
163
  }
164
165
  /**
166
   * Notifies listeners of a custom message.
167
   *
168
   * @param ex   The exception that warranted calling this method.
169
   * @param fmt  The string format specifier.
170
   * @param args The arguments to weave into the format specifier.
171
   */
172
  public static void clue(
173
    final Exception ex,
174
    final String fmt,
175
    final Object... args ) {
176
    final var msg = format( fmt, args );
177
    clue( msg, ex );
178
  }
179
180
  /**
181
   * Notifies listeners of an exception occurs that warrants the user's
182
   * attention.
183
   *
184
   * @param problem The exception with a message to display to the user.
185
   */
186
  public static void clue( final Throwable problem ) {
187
    fire( problem );
188
  }
189
190
  public static void fire( final String message ) {
191
    new StatusEvent( message ).publish();
192
  }
193
194
  private static void fire( final Throwable problem ) {
195
    new StatusEvent( problem ).publish();
196
  }
197
198
  private static void fire(
199
    final String message, final Throwable problem ) {
200
    new StatusEvent( message, problem ).publish();
201
  }
202
}
1203
A src/main/java/com/keenwrite/events/TextDefinitionFocusEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import com.keenwrite.editors.TextDefinition;
18
9
public class TextDefinitionDefinition> {
10
  protected TextDefinition
A src/main/java/com/keenwrite/events/TextEditorFocusEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
6
7
import com.keenwrite.editors.TextEditor;
8
9
/**
10
 * Collates information about the text editor that has gained focus.
11
 */
12
public class TextEditorFocusEvent extends FocusEvent<TextEditor> {
13
  protected TextEditorFocusEvent( final TextEditor editor ) {
14
    super( editor );
15
  }
16
17
  /**
18
   * When the {@link TextEditor} has focus, fire an event so that subscribers
19
   * may perform an action---such as parsing and rendering the contents.
20
   *
21
   * @param editor The instance of editor that has gained input focus.
22
   */
23
  public static void fire( final TextEditor editor ) {
24
    new TextEditorFocusEvent( editor ).publish();
25
  }
26
}
127
A src/main/java/com/keenwrite/events/WordCountEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events;
16
7
/**
8
 * Collates information about the word count changing.
9
 */
10
public class WordCountEvent implements AppEvent {
11
  /**
12
   * Number of words in the 
A src/main/java/com/keenwrite/events/spelling/LexiconEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events.spelling;
6
7
import com.keenwrite.events.AppEvent;
8
9
public abstract class LexiconEvent implements AppEvent {
10
}
111
A src/main/java/com/keenwrite/events/spelling/LexiconLoadedEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events.spelling;
6
7
import java.util.Map;
18
9
/**
10
 * Collates information about the lexicon. Fired when the lexicon has been
11
 * fully loaded into memory.
12
 */
13
public c
A src/main/java/com/keenwrite/events/workspace/WorkspaceEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events.workspace;
6
7
import com.keenwrite.events.AppEvent;
8
9
/**
10
 * Superclass of all events related to the workspace.
11
 */
12
public abstract class WorkspaceEvent implements AppEvent {
13
}
114
A src/main/java/com/keenwrite/events/workspace/WorkspaceLoadedEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.events.workspace;
6
7
import com.keenwrite.preferences.Workspace;
8
9
/**
10
 * Indicates that the {@link Workspace} has been loaded.
11
 */
12
public class WorkspaceLoadedEvent extends WorkspaceEvent {
13
  private final Workspace mWorkspace;
14
15
  private WorkspaceLoadedEvent( final Workspace workspace ) {
16
    assert workspace != null;
17
18
    mWorkspace = workspace;
19
  }
20
21
  /**
22
   * Publishes an event that indicates a new {@link Workspace} has been loaded.
23
   */
24
  public static void fire( final Workspace workspace ) {
25
    new WorkspaceLoadedEvent( workspace ).publish();
26
  }
27
28
  /**
29
   * Returns a reference to the {@link Workspace} that was loaded.
30
   *
31
   * @return The {@link Workspace} that has loaded user preferences.
32
   */
33
  @SuppressWarnings( "unused" )
34
  private Workspace getWorkspace() {
35
    return mWorkspace;
36
  }
37
}
138
A src/main/java/com/keenwrite/exceptions/MissingFileException.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
14
A src/main/java/com/keenwrite/heuristics/package-info.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
3
/**
4
 * This package contains classes to help with word count. In logographic,
5
 * or other non-alphabetic languages, word tokenization cannot rely on
6
 * spaces. Instead, we need to employ a more sophisticated approach using
7
 * natural language parsing (NLP).
8
 */
9
package com.keenwrite.heuristics;
110
A src/main/java/com/keenwrite/io/CommandNotFoundException.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import java.io.FileNotFoundException;
5
6
/**
7
 * Indicates a command could not be found to run.
8
 */
9
public class CommandNotFoundException extends FileNotFoundException {
10
  /**
11
   * Creates a new exception indicating that the given command could not be
12
   * found (or executed).
13
   *
14
   * @param command The binary file's command name that could not be run.
15
   */
16
  public CommandNotFoundException( final String command ) {
17
    super( command );
18
  }
19
}
120
A src/main/java/com/keenwrite/io/FileEvent.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import java.io.File;
5
import java.util.EventObject;
6
7
/**
8
 * Responsible for indicating that a file has been modified by the file system.
9
 */
10
public class FileEvent extends EventObject {
11
12
  /**
13
   * Constructs a new event that indicates the source of a file system event.
14
   *
15
   * @param file The {@link File} that has succumbed to a file system event.
16
   */
17
  public FileEvent( final File file ) {
18
    super( file );
19
  }
20
21
  /**
22
   * Returns the source as an instance of {@link File}.
23
   *
24
   * @return The {@link File} being watched.
25
   */
26
  public File getFile() {
27
    return (File) getSource();
28
  }
29
}
130
A src/main/java/com/keenwrite/io/FileModifiedListener.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import java.util.EventListener;
5
import java.util.function.Consumer;
6
7
/**
8
 * Responsible for informing listeners when a file has been modified.
9
 */
10
public interface FileModifiedListener
11
  extends EventListener, Consumer<FileEvent> {
12
}
113
A src/main/java/com/keenwrite/io/FileType.java
1
1
/* Copyright 2020-2021 -- All rights reserved. */
2
package com.keenwrite.io {@code
3
 * settings.properties}-
A src/main/java/com/keenwrite/io/FileWatchService.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import org.renjin.repackaged.guava.collect.BiMap;
5
import org.renjin.repackaged.guava.collect.HashBiMap;
16
7
import java.io.File;
8
import java.io.IOExceptiocom.keenwrite.io.SysFile.toFiletoFile( 
A src/main/java/com/keenwrite/io/MediaType.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import java.io.File;
5
import java.io.IOException;
6
import java.net.URL;
7
import java.nio.file.Path;
18
9
import static com.keenwrite.io.MediaType.TypeName.*;
10
SysFile.toFiletoFile( directory 
A src/main/java/com/keenwrite/io/MediaTypeExtension.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import org.apache.commons.io.FilenameUtils;
5
6
import java.io.File;
7
import java.nio.file.Path;
8
import java.util.List;
19
10
import static com.keenwrite.io.Medcom.keenwrite.io.SysFile.toFiletoFile( path 
A src/main/java/com/keenwrite/io/MediaTypeSniffer.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io;
6
7
import java.io.*;
8
import java.util.LinkedHashMap;
9
import java.util.Map;
10
11
import static com.keenwrite.io.MediaType.*;
12
import static java.lang.Math.min;
13
import static java.lang.System.arraycopy;
14
import static java.util.Arrays.fill;
15
16
/**
17
 * Associates file signatures with IANA-defined {@link MediaType}s. See:
18
 * <a href="https://www.garykessler.net/library/file_sigs.html">
19
 * Gary Kessler's List
20
 * </a>,
21
 * <a href="https://en.wikipedia.org/wiki/List_of_file_signatures">
22
 * Wikipedia's List
23
 * </a>, and
24
 * <a href="https://github.com/veniware/Space-Maker/blob/master/FileSignatures.cs">
25
 * Space Maker's List
26
 * </a>
27
 */
28
public class MediaTypeSniffer {
29
  /**
30
   * The maximum buffer size of magic bytes to analyze.
31
   */
32
  private static final int BUFFER = 12;
33
34
  /**
35
   * The media type data can have any value at a corresponding offset.
36
   */
37
  private static final int ANY = -1;
38
39
  /**
40
   * Denotes there are fewer than {@link #BUFFER} bytes to compare.
41
   */
42
  private static final int EOS = -2;
43
44
  private static final Map<int[], MediaType> FORMAT = new LinkedHashMap<>();
45
46
  private static void put( final int[] data, final MediaType mediaType ) {
47
    FORMAT.put( data, mediaType );
48
  }
49
50
  /* The insertion order attempts to approximate the real-world likelihood of
51
   * encountering particular file formats in an application.
52
   */
53
  static {
54
    //@formatter:off
55
    put( ints( 0x3C, 0x73, 0x76, 0x67, 0x20 ), IMAGE_SVG_XML );
56
    put( ints( 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), IMAGE_PNG );
57
    put( ints( 0xFF, 0xD8, 0xFF, 0xE0 ), IMAGE_JPEG );
58
    put( ints( 0xFF, 0xD8, 0xFF, 0xEE ), IMAGE_JPEG );
59
    put( ints( 0xFF, 0xD8, 0xFF, 0xE1, ANY, ANY, 0x45, 0x78, 0x69, 0x66, 0x00 ), IMAGE_JPEG );
60
    put( ints( 0x3C, 0x21 ), TEXT_HTML );
61
    put( ints( 0x3C, 0x68, 0x74, 0x6D, 0x6C ), TEXT_HTML );
62
    put( ints( 0x3C, 0x68, 0x65, 0x61, 0x64 ), TEXT_HTML );
63
    put( ints( 0x3C, 0x62, 0x6F, 0x64, 0x79 ), TEXT_HTML );
64
    put( ints( 0x3C, 0x48, 0x54, 0x4D, 0x4C ), TEXT_HTML );
65
    put( ints( 0x3C, 0x48, 0x45, 0x41, 0x44 ), TEXT_HTML );
66
    put( ints( 0x3C, 0x42, 0x4F, 0x44, 0x59 ), TEXT_HTML );
67
    put( ints( 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20 ), TEXT_XML );
68
    put( ints( 0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78 ), TEXT_XML );
69
    put( ints( 0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00 ), TEXT_XML );
70
    put( ints( 0x47, 0x49, 0x46, 0x38 ), IMAGE_GIF );
71
    put( ints( 0x42, 0x4D ), IMAGE_BMP );
72
    put( ints( 0x49, 0x49, 0x2A, 0x00 ), IMAGE_TIFF );
73
    put( ints( 0x4D, 0x4D, 0x00, 0x2A ), IMAGE_TIFF );
74
    put( ints( 0x52, 0x49, 0x46, 0x46, ANY, ANY, ANY, ANY, 0x57, 0x45, 0x42, 0x50 ), IMAGE_WEBP );
75
    put( ints( 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E ), APP_PDF );
76
    put( ints( 0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D ), APP_EPS );
77
    put( ints( 0x25, 0x21, 0x50, 0x53 ), APP_PS );
78
    put( ints( 0x38, 0x42, 0x50, 0x53, 0x00, 0x01 ), IMAGE_PHOTOSHOP );
79
    put( ints( 0xFF, 0xFB, 0x30 ), AUDIO_MP3 );
80
    put( ints( 0x49, 0x44, 0x33 ), AUDIO_MP3 );
81
    put( ints( 0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), VIDEO_MNG );
82
    put( ints( 0x23, 0x64, 0x65, 0x66 ), IMAGE_X_BITMAP );
83
    put( ints( 0x21, 0x20, 0x58, 0x50, 0x4D, 0x32 ), IMAGE_X_PIXMAP );
84
    put( ints( 0x2E, 0x73, 0x6E, 0x64 ), AUDIO_SIMPLE );
85
    put( ints( 0x64, 0x6E, 0x73, 0x2E ), AUDIO_SIMPLE );
86
    put( ints( 0x52, 0x49, 0x46, 0x46 ), AUDIO_WAV );
87
    put( ints( 0x50, 0x4B ), APP_ZIP );
88
    put( ints( 0x41, 0x43, ANY, ANY, ANY, ANY, 0x00, 0x00, 0x00, 0x00, 0x00 ), APP_ACAD );
89
    put( ints( 0xCA, 0xFE, 0xBA, 0xBE ), APP_JAVA );
90
    put( ints( 0xAC, 0xED ), APP_JAVA_OBJECT );
91
    //@formatter:on
92
  }
93
94
  /**
95
   * Returns the {@link MediaType} for a given set of bytes.
96
   *
97
   * @param data Binary data to compare against the list of known formats.
98
   * @return The IANA-defined {@link MediaType}, or
99
   * {@link MediaType#UNDEFINED} if indeterminate.
100
   */
101
  public static MediaType getMediaType( final byte[] data ) {
102
    assert data != null;
103
    assert data.length > 0;
104
105
    final var source = new int[]{
106
      0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
107
      0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
108
      0xFF, 0XFF, EOS
109
    };
110
111
    final int length = min( data.length, source.length );
112
113
    for( int i = 0; i < length; i++ ) {
114
      source[ i ] = data[ i ] & 0xFF;
115
    }
116
117
    for( final var entry : FORMAT.entrySet() ) {
118
      final var key = entry.getKey();
119
120
      int i = -1;
121
      boolean matches = true;
122
123
      while( ++i < BUFFER && key[ i ] != EOS && matches ) {
124
        matches = key[ i ] == source[ i ] || key[ i ] == ANY;
125
      }
126
127
      if( matches ) {
128
        return entry.getValue();
129
      }
130
    }
131
132
    return UNDEFINED;
133
  }
134
135
  /**
136
   * Convenience method to return the probed media type for the given
137
   * {@link SysFile} instance by delegating to
138
   * {@link #getMediaType(InputStream)}.
139
   *
140
   * @param file File to ascertain the {@link MediaType}.
141
   * @return The IANA-defined {@link MediaType}, or
142
   * {@link MediaType#UNDEFINED} if indeterminate.
143
   * @throws IOException Could not read from the {@link File}.
144
   */
145
  public static MediaType getMediaType( final File file )
146
    throws IOException {
147
    try( final var fis = new FileInputStream( file ) ) {
148
      return getMediaType( fis );
149
    }
150
  }
151
152
  /**
153
   * Convenience method to return the probed media type for the given
154
   * {@link BufferedInputStream} instance. <strong>This resets the stream
155
   * pointer</strong> making the call idempotent. Prefer calling this
156
   * method when operating on streams to avoid advancing the stream.
157
   *
158
   * @param bis Data source to ascertain the {@link MediaType}.
159
   * @return The IANA-defined {@link MediaType}, or
160
   * {@link MediaType#UNDEFINED} if indeterminate.
161
   * @throws IOException Could not read from the stream.
162
   */
163
  public static MediaType getMediaType( final BufferedInputStream bis )
164
    throws IOException {
165
    bis.mark( BUFFER );
166
    final var result = getMediaType( (InputStream) bis );
167
    bis.reset();
168
169
    return result;
170
  }
171
172
  /**
173
   * Returns the probed media type for the given {@link InputStream} instance.
174
   * The caller is responsible for closing the stream. <strong>This advances
175
   * the stream.</strong> Use {@link #getMediaType(BufferedInputStream)} to
176
   * perform a non-destructive read.
177
   *
178
   * @param is Data source to ascertain the {@link MediaType}.
179
   * @return The IANA-defined {@link MediaType}, or
180
   * {@link MediaType#UNDEFINED} if indeterminate.
181
   * @throws IOException Could not read from the {@link InputStream}.
182
   */
183
  private static MediaType getMediaType( final InputStream is )
184
    throws IOException {
185
    final var input = new byte[ BUFFER ];
186
    final var count = is.read( input, 0, BUFFER );
187
188
    if( count > 1 ) {
189
      final var available = new byte[ count ];
190
      arraycopy( input, 0, available, 0, count );
191
      return getMediaType( available );
192
    }
193
194
    return UNDEFINED;
195
  }
196
197
  /**
198
   * Creates an integer array from the given data, padded with {@link #EOS}
199
   * values up to {@link #BUFFER} in length.
200
   *
201
   * @param data The input byte values to pad.
202
   * @return The data with padding.
203
   */
204
  private static int[] ints( final int... data ) {
205
    assert data != null;
206
207
    final var magic = new int[ data.length + 1 ];
208
209
    fill( magic, EOS );
210
    arraycopy( data, 0, magic, 0, data.length );
211
212
    return magic;
213
  }
214
}
1215
A src/main/java/com/keenwrite/io/PollingWatchService.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import java.nio.file.WatchEvent;
5
import java.nio.file.WatchKey;
6
import java.nio.file.WatchService;
7
import java.nio.file.Watchable;
8
import java.util.List;
9
import java.util.concurrent.TimeUnit;
10
11
/**
12
 * Responsible for polling the file system to see whether a file has been
13
 * updated. This is instantiated when an instance of {@link WatchService}
14
 * cannot be created using the Java API.
15
 * <p>
16
 * This is a skeleton class to avoid {@code null} references. In theory,
17
 * it should never get instantiated. If the application is run on a system
18
 * that does not support file system events, this should eliminate NPEs.
19
 * </p>
20
 */
21
public class PollingWatchService implements WatchService {
22
  private final WatchKey EMPTY_KEY = new WatchKey() {
23
    private final Watchable WATCHABLE = new Watchable() {
24
      @Override
25
      public WatchKey register(
26
        final WatchService watcher,
27
        final WatchEvent.Kind<?>[] events,
28
        final WatchEvent.Modifier... modifiers ) {
29
        return EMPTY_KEY;
30
      }
31
32
      @Override
33
      public WatchKey register(
34
        final WatchService watcher, final WatchEvent.Kind<?>... events ) {
35
        return EMPTY_KEY;
36
      }
37
    };
38
39
    @Override
40
    public boolean isValid() {
41
      return false;
42
    }
43
44
    @Override
45
    public List<WatchEvent<?>> pollEvents() {
46
      return List.of();
47
    }
48
49
    @Override
50
    public boolean reset() {
51
      return false;
52
    }
53
54
    @Override
55
    public void cancel() {
56
    }
57
58
    @Override
59
    public Watchable watchable() {
60
      return WATCHABLE;
61
    }
62
  };
63
64
  @Override
65
  public void close() {
66
  }
67
68
  @Override
69
  public WatchKey poll() {
70
    return EMPTY_KEY;
71
  }
72
73
  @Override
74
  public WatchKey poll( final long timeout, final TimeUnit unit ) {
75
    return EMPTY_KEY;
76
  }
77
78
  @Override
79
  public WatchKey take() {
80
    return EMPTY_KEY;
81
  }
82
}
183
A src/main/java/com/keenwrite/io/StreamGobbler.java
1
package com.keenwrite.io;
2
3
import java.io.BufferedReader;
4
import java.io.IOException;
5
import java.io.InputStream;
6
import java.io.InputStreamReader;
7
import java.util.concurrent.Callable;
8
import java.util.function.Consumer;
9
10
import static java.nio.charset.StandardCharsets.UTF_8;
11
import static java.util.concurrent.Executors.newFixedThreadPool;
12
13
/**
14
 * Consumes the standard output of a {@link Process} created from a
15
 * {@link ProcessBuilder}. Directs the output to a {@link Consumer} of
16
 * strings. This will run on its own thread and close the stream when
17
 * no more data can be processed.
18
 * <p>
19
 * <strong>Warning:</strong> Do not use this with binary data, it is only
20
 * meant for text streams, such as standard out from running command-line
21
 * applications.
22
 * </p>
23
 */
24
public class StreamGobbler implements Callable<Boolean> {
25
  private final InputStream mInput;
26
  private final Consumer<String> mConsumer;
27
28
  /**
29
   * Constructs a new instance of {@link StreamGobbler} that is capable of
30
   * reading an {@link InputStream} and passing each line of textual data from
31
   * that stream over to a string {@link Consumer}.
32
   *
33
   * @param input    The stream having input to pass to the consumer.
34
   * @param consumer The {@link Consumer} that receives each line.
35
   */
36
  private StreamGobbler(
37
    final InputStream input,
38
    final Consumer<String> consumer ) {
39
    assert input != null;
40
    assert consumer != null;
41
42
    mInput = input;
43
    mConsumer = consumer;
44
  }
45
46
  /**
47
   * Consumes the input until no more data is available. Closes the stream.
48
   *
49
   * @return {@link Boolean#TRUE} always.
50
   * @throws IOException Could not read from the stream.
51
   */
52
  @Override
53
  public Boolean call() throws IOException {
54
    try( final var input = new InputStreamReader( mInput, UTF_8 );
55
         final var buffer = new BufferedReader( input ) ) {
56
      buffer.lines().forEach( mConsumer );
57
    }
58
59
    return Boolean.TRUE;
60
  }
61
62
  /**
63
   * Reads the given {@link InputStream} on a separate thread and passes
64
   * each line of text input to the given {@link Consumer}.
65
   *
66
   * @param inputStream The stream having input to pass to the consumer.
67
   * @param consumer    The {@link Consumer} that receives each line.
68
   */
69
  public static void gobble(
70
    final InputStream inputStream, final Consumer<String> consumer ) {
71
    try( final var executor = newFixedThreadPool( 1 ) ) {
72
      executor.submit( new StreamGobbler( inputStream, consumer ) );
73
    }
74
  }
75
}
176
A src/main/java/com/keenwrite/io/SysFile.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import org.jetbrains.annotations.NotNull;
15
6
import java.io.File;
7
import java.io.FileInputStream;
8
import java.io.IOException;
9
import java.nio.file.Path;
10
  };
11
  _
12
   
A src/main/java/com/keenwrite/io/UserDataDir.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io;
6
7
import java.nio.file.Path;
8
9
import static com.keenwrite.io.SysFile.toFile;
10
import static com.keenwrite.util.SystemUtils.*;
11
import static java.lang.System.getProperty;
12
import static java.lang.System.getenv;
13
14
/**
15
 * Responsible for determining the directory to write application data, across
16
 * multiple platforms. See also:
17
 *
18
 * <ul>
19
 * <li>
20
 *   <a href="https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html">
21
 *     Linux: XDG Base Directory Specification
22
 *   </a>
23
 * </li>
24
 * <li>
25
 *   <a href="https://learn.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables">
26
 *     Windows: Recognized environment variables
27
 *   </a>
28
 * </li>
29
 * <li>
30
 *   <a href="https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html">
31
 *     macOS: File System Programming Guide
32
 *   </a>
33
 * </li>
34
 * </ul>
35
 * </p>
36
 */
37
public final class UserDataDir {
38
39
  private static final Path UNDEFINED = Path.of( "/" );
40
41
  private static final String PROP_USER_HOME = getProperty( "user.home" );
42
  private static final String PROP_USER_DIR = getProperty( "user.dir" );
43
  private static final String PROP_OS_VERSION = getProperty( "os.version" );
44
  private static final String ENV_APPDATA = getenv( "AppData" );
45
  private static final String ENV_XDG_DATA_HOME = getenv( "XDG_DATA_HOME" );
46
47
  private UserDataDir() { }
48
49
  public static String getUserHome() {
50
    return PROP_USER_HOME;
51
  }
52
53
  /**
54
   * Makes a valiant attempt at determining where to create application-specific
55
   * files, regardless of operating system.
56
   *
57
   * @param appName The application name that seeks to create files.
58
   * @return A fully qualified {@link Path} to a directory wherein files may
59
   * be created that are user- and application-specific.
60
   */
61
  public static Path getAppPath( final String appName ) {
62
    final var osPath = isWindows()
63
      ? getWinAppPath()
64
      : isMacOs()
65
      ? getMacAppPath()
66
      : isUnix()
67
      ? getUnixAppPath()
68
      : UNDEFINED;
69
70
    final var path = osPath.equals( UNDEFINED )
71
      ? getDefaultAppPath( appName )
72
      : osPath.resolve( appName );
73
74
    final var alternate = Path.of( PROP_USER_DIR, appName );
75
76
    return ensureExists( path )
77
      ? path
78
      : ensureExists( alternate )
79
      ? alternate
80
      : Path.of( PROP_USER_DIR );
81
  }
82
83
  private static Path getWinAppPath() {
84
    return isValid( ENV_APPDATA )
85
      ? Path.of( ENV_APPDATA )
86
      : home( getWinVerAppPath() );
87
  }
88
89
  /**
90
   * Gets the application path with respect to the Windows version.
91
   *
92
   * @return The directory name paths relative to the user's home directory.
93
   */
94
  private static String[] getWinVerAppPath() {
95
    return PROP_OS_VERSION.startsWith( "5." )
96
      ? new String[]{"Application Data"}
97
      : new String[]{"AppData", "Roaming"};
98
  }
99
100
  private static Path getMacAppPath() {
101
    final var path = home( "Library", "Application Support" );
102
103
    return ensureExists( path ) ? path : UNDEFINED;
104
  }
105
106
  private static Path getUnixAppPath() {
107
    // Fallback in case the XDG data directory is undefined.
108
    var path = home( ".local", "share" );
109
110
    if( isValid( ENV_XDG_DATA_HOME ) ) {
111
      final var xdgPath = Path.of( ENV_XDG_DATA_HOME );
112
113
      path = ensureExists( xdgPath ) ? xdgPath : path;
114
    }
115
116
    return path;
117
  }
118
119
  /**
120
   * Returns a hidden directory relative to the user's home directory.
121
   *
122
   * @param appName The application name.
123
   * @return A suitable directory for storing application files.
124
   */
125
  private static Path getDefaultAppPath( final String appName ) {
126
    return home( '.' + appName );
127
  }
128
129
  private static Path home( final String... paths ) {
130
    return Path.of( PROP_USER_HOME, paths );
131
  }
132
133
  /**
134
   * Verifies whether the path exists or was created.
135
   *
136
   * @param path The directory to verify.
137
   * @return {@code true} if the path already exists or was created,
138
   * {@code false} if the directory doesn't exist and couldn't be created.
139
   */
140
  private static boolean ensureExists( final Path path ) {
141
    final var file = toFile( path );
142
    return file.exists() || file.mkdirs();
143
  }
144
145
  /**
146
   * Answers whether the given string contains content.
147
   *
148
   * @param s The string to check, may be {@code null}.
149
   * @return {@code true} if the string is neither {@code null} nor blank.
150
   */
151
  private static boolean isValid( final String s ) {
152
    return !(s == null || s.isBlank());
153
  }
154
155
  private static boolean isWindows() {
156
    return IS_OS_WINDOWS;
157
  }
158
159
  private static boolean isMacOs() {
160
    return IS_OS_MAC;
161
  }
162
163
  private static boolean isUnix() {
164
    return IS_OS_UNIX;
165
  }
166
}
1167
A src/main/java/com/keenwrite/io/WindowsRegistry.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import java.io.IOException;
5
import java.util.function.Function;
6
import java.util.regex.Pattern;
17
8
import static java.io.File.pathSeparator;
9
import static jav.*
A src/main/java/com/keenwrite/io/Zip.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io;
16
7
import java.io.IOException;
8
import java.io.UncheckedIOException;
9
import java.nio.charset.StandardCharsets;
10
import java.nio.file.Files;
11
import java.nio.file.
A src/main/java/com/keenwrite/io/downloads/DownloadManager.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io.downloads;
6
7
import com.keenwrite.io.MediaType;
8
import com.keenwrite.io.MediaTypeSniffer;
9
import com.keenwrite.io.SysFile;
10
import javafx.concurrent.Task;
11
12
import java.io.*;
13
import java.net.HttpURLConnection;
14
import java.net.URI;
15
import java.net.URISyntaxException;
16
import java.net.URL;
17
import java.nio.file.Paths;
18
import java.time.Duration;
19
import java.util.concurrent.Callable;
20
import java.util.zip.GZIPInputStream;
21
22
import static java.lang.Math.toIntExact;
23
import static java.lang.String.format;
24
import static java.lang.System.getProperty;
25
import static java.lang.System.setProperty;
26
import static java.net.HttpURLConnection.HTTP_OK;
27
import static java.net.HttpURLConnection.setFollowRedirects;
28
29
/**
30
 * Responsible for downloading files and publishing status updates. This will
31
 * download a resource provided by an instance of {@link URL} into a given
32
 * {@link OutputStream}.
33
 */
34
public final class DownloadManager {
35
  static {
36
    setProperty( "http.keepAlive", "false" );
37
    setFollowRedirects( true );
38
  }
39
40
  /**
41
   * Number of bytes to read at a time.
42
   */
43
  private static final int BUFFER_SIZE = 16384;
44
45
  /**
46
   * HTTP request timeout.
47
   */
48
  private static final Duration TIMEOUT = Duration.ofSeconds( 30 );
49
50
  /**
51
   * Use any of the static methods for opening by URI, URL, or string.
52
   */
53
  private DownloadManager() {}
54
55
  @FunctionalInterface
56
  public interface ProgressListener {
57
    /**
58
     * Called when a chunk of data has been read. This is called synchronously
59
     * when downloading the data; do not execute long-running tasks in this
60
     * method (a few milliseconds is fine).
61
     *
62
     * @param percentage A value between 0 and 100, inclusive, represents the
63
     *                   percentage of bytes downloaded relative to the total.
64
     *                   A value of -1 means the total number of bytes to
65
     *                   download is unknown.
66
     * @param bytes      When {@code percentage} is greater than or equal to
67
     *                   zero, this is the total number of bytes. When {@code
68
     *                   percentage} equals -1, this is the number of bytes
69
     *                   read so far.
70
     */
71
    void update( int percentage, long bytes );
72
  }
73
74
  /**
75
   * Callers may check the value of isSuccessful
76
   */
77
  public static final class DownloadToken implements Closeable {
78
    private final HttpURLConnection mConn;
79
    private final BufferedInputStream mInput;
80
    private final MediaType mMediaType;
81
    private final long mBytesTotal;
82
83
    private DownloadToken(
84
      final HttpURLConnection conn,
85
      final BufferedInputStream input,
86
      final MediaType mediaType
87
    ) {
88
      assert conn != null;
89
      assert input != null;
90
      assert mediaType != null;
91
92
      mConn = conn;
93
      mInput = input;
94
      mMediaType = mediaType;
95
      mBytesTotal = conn.getContentLength();
96
    }
97
98
    /**
99
     * Provides the ability to download remote files asynchronously while
100
     * being updated regarding the download progress. The given {@link File}
101
     * will have the contents of the URL to download upon completion.
102
     *
103
     * @param file     Where to write the file contents.
104
     * @param listener Receives download progress status updates.
105
     * @return A {@link Runnable} task that can be executed in the background
106
     * to download the resource for this {@link DownloadToken}.
107
     */
108
    public Runnable download(
109
      final File file,
110
      final ProgressListener listener ) {
111
      return () -> {
112
        final var buffer = new byte[ BUFFER_SIZE ];
113
        final var stream = getInputStream();
114
        final var bytesTotal = mBytesTotal;
115
116
        long bytesTally = 0;
117
        int bytesRead;
118
119
        try( final var output = new FileOutputStream( file ) ) {
120
          while( (bytesRead = stream.read( buffer )) != -1 ) {
121
            if( Thread.currentThread().isInterrupted() ) {
122
              throw new InterruptedException();
123
            }
124
125
            bytesTally += bytesRead;
126
127
            if( bytesTotal > 0 ) {
128
              listener.update(
129
                toIntExact( bytesTally * 100 / bytesTotal ),
130
                bytesTotal
131
              );
132
            }
133
            else {
134
              listener.update( -1, bytesRead );
135
            }
136
137
            output.write( buffer, 0, bytesRead );
138
          }
139
        } catch( final Exception ex ) {
140
          throw new RuntimeException( ex );
141
        } finally {
142
          close();
143
        }
144
      };
145
    }
146
147
    public void close() {
148
      try {
149
        getInputStream().close();
150
      } catch( final Exception ignored ) {
151
      } finally {
152
        mConn.disconnect();
153
      }
154
    }
155
156
    /**
157
     * Returns the input stream to the resource to download.
158
     *
159
     * @return The stream to read.
160
     */
161
    public BufferedInputStream getInputStream() {
162
      return mInput;
163
    }
164
165
    public MediaType getMediaType() {
166
      return mMediaType;
167
    }
168
169
    /**
170
     * Answers whether the type of content associated with the download stream
171
     * is a scalable vector graphic.
172
     *
173
     * @return {@code true} if the given {@link MediaType} has SVG contents.
174
     */
175
    public boolean isSvg() {
176
      return getMediaType().isSvg();
177
    }
178
  }
179
180
  /**
181
   * Opens the input stream for the resource to download.
182
   *
183
   * @param uri The {@link URI} resource to download.
184
   * @return A token that can be used for downloading the content with
185
   * periodic updates or retrieving the stream for downloading the content.
186
   * @throws IOException        The stream could not be opened.
187
   * @throws URISyntaxException Invalid URI.
188
   */
189
  public static DownloadToken open( final String uri )
190
    throws IOException, URISyntaxException {
191
    // Pass an undefined media type so that any type of file can be retrieved.
192
    return open( new URI( uri ) );
193
  }
194
195
  public static DownloadToken open( final URI uri )
196
    throws IOException {
197
    return open( uri.toURL() );
198
  }
199
200
  /**
201
   * Opens the input stream for the resource to download and verifies that
202
   * the given {@link MediaType} matches the requested type. Callers are
203
   * responsible for closing the {@link DownloadManager} to close the
204
   * underlying stream and the HTTP connection. Connections must be closed by
205
   * callers if {@link DownloadToken#download(File, ProgressListener)}
206
   * isn't called (i.e., {@link DownloadToken#getMediaType()} is called
207
   * after the transport layer's Content-Type is requested but not contents
208
   * are downloaded).
209
   *
210
   * @param url The {@link URL} resource to download.
211
   * @return A token that can be used for downloading the content with
212
   * periodic updates or retrieving the stream for downloading the content.
213
   * @throws IOException The resource could not be downloaded.
214
   */
215
  public static DownloadToken open( final URL url ) throws IOException {
216
    final var conn = connect( url );
217
    final var contentType = conn.getContentType();
218
219
    MediaType remoteType;
220
221
    try {
222
      remoteType = MediaType.valueFrom( contentType );
223
    } catch( final Exception ex ) {
224
      // If the media type couldn't be detected, try using the stream.
225
      remoteType = MediaType.UNDEFINED;
226
    }
227
228
    final var input = open( conn );
229
230
    // Peek at the magic header bytes to determine the media type.
231
    final var magicType = MediaTypeSniffer.getMediaType( input );
232
233
    // If the transport protocol's Content-Type doesn't align with the
234
    // media type for the magic header, defer to the transport protocol (so
235
    // long as the content type was sent from the remote side).
236
    final MediaType mediaType = remoteType.equals( magicType )
237
      ? remoteType
238
      : contentType != null && !contentType.isBlank()
239
      ? remoteType
240
      : magicType.isUndefined()
241
      ? remoteType
242
      : magicType;
243
244
    return new DownloadToken( conn, input, mediaType );
245
  }
246
247
  /**
248
   * Establishes a connection to the remote {@link URL} resource.
249
   *
250
   * @param url The {@link URL} representing a resource to download.
251
   * @return The connection manager for the {@link URL}.
252
   * @throws IOException         Could not establish a connection.
253
   * @throws ArithmeticException Could not compute a timeout value (this
254
   *                             should never happen because the timeout is
255
   *                             less than a minute).
256
   * @see #TIMEOUT
257
   */
258
  private static HttpURLConnection connect( final URL url )
259
    throws IOException, ArithmeticException {
260
    // Both HTTP and HTTPS are covered by this condition.
261
    if( url.openConnection() instanceof HttpURLConnection conn ) {
262
      conn.setUseCaches( false );
263
      conn.setInstanceFollowRedirects( true );
264
      conn.setRequestProperty( "Accept-Encoding", "gzip" );
265
      conn.setRequestProperty( "User-Agent", getProperty( "http.agent" ) );
266
      conn.setRequestMethod( "GET" );
267
      conn.setConnectTimeout( toIntExact( TIMEOUT.toMillis() ) );
268
      conn.setRequestProperty( "connection", "close" );
269
      conn.connect();
270
271
      final var code = conn.getResponseCode();
272
273
      if( code != HTTP_OK ) {
274
        final var message = format(
275
          "%s [HTTP %d: %s]",
276
          url.getFile(),
277
          code,
278
          conn.getResponseMessage()
279
        );
280
281
        throw new IOException( message );
282
      }
283
284
      return conn;
285
    }
286
287
    throw new UnsupportedOperationException( url.toString() );
288
  }
289
290
  /**
291
   * Returns a stream in an open state. Callers are responsible for closing.
292
   *
293
   * @param conn The connection to open, which could be compressed.
294
   * @return The open stream.
295
   * @throws IOException Could not open the stream.
296
   */
297
  private static BufferedInputStream open( final HttpURLConnection conn )
298
    throws IOException {
299
    return open( conn.getContentEncoding(), conn.getInputStream() );
300
  }
301
302
  /**
303
   * Returns a stream in an open state. Callers are responsible for closing.
304
   * The input stream may be compressed.
305
   *
306
   * @param encoding The content encoding for the stream.
307
   * @param is       The stream to wrap with a suitable decoder.
308
   * @return The open stream, with any gzip content-encoding decoded.
309
   * @throws IOException Could not open the stream.
310
   */
311
  private static BufferedInputStream open(
312
    final String encoding, final InputStream is ) throws IOException {
313
    return new BufferedInputStream(
314
      "gzip".equalsIgnoreCase( encoding )
315
        ? new GZIPInputStream( is )
316
        : is
317
    );
318
  }
319
320
  public static <T> Task<T> createTask( final Callable<T> callable ) {
321
    return new Task<>() {
322
      @Override
323
      protected T call() throws Exception {
324
        return callable.call();
325
      }
326
    };
327
  }
328
329
  public static <T> Thread createThread( final Task<T> task ) {
330
    final var thread = new Thread( task );
331
    thread.setDaemon( true );
332
    return thread;
333
  }
334
335
  /**
336
   * Downloads a resource to a local file in a separate {@link Thread}.
337
   *
338
   * @param uri      The resource to download.
339
   * @param file     The destination mTarget for the resource.
340
   * @param listener Receives updates as the download proceeds.
341
   */
342
  public static Task<Void> downloadAsync(
343
    final URI uri,
344
    final File file,
345
    final ProgressListener listener ) {
346
    final Task<Void> task = createTask( () -> {
347
      try( final var token = DownloadManager.open( uri ) ) {
348
        token.download( file, listener ).run();
349
      }
350
351
      return null;
352
    } );
353
354
    createThread( task ).start();
355
    return task;
356
  }
357
358
  public static String toFilename( final URI uri ) {
359
    return toFile( uri ).getName();
360
  }
361
362
  public static File toFile( final URI uri ) {
363
    return SysFile.toFile( Paths.get( uri.getPath() ) );
364
  }
365
}
1366
A src/main/java/com/keenwrite/io/downloads/events/DownloadConnectionFailedEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io.downloads.events;
6
7
import java.net.URL;
18
9
/**
10
 * Collates information about an HTTP connection that could not be established.
11
 */
12
public class DownloadConnecti
A src/main/java/com/keenwrite/io/downloads/events/DownloadEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io.downloads.events;
6
7
import com.keenwrite.events.AppEvent;
8
9
import java.net.URL;
10
import java.time.Instant;
111
12
/**
13
 * The parent class to all download-related stat
A src/main/java/com/keenwrite/io/downloads/events/DownloadFailedEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io.downloads.events;
6
7
import java.net.URL;
8
9
public class DownloadFailedEvent extends DownloadEvent {
10
11
  private final int mResponseCode;
12
13
  /**
14
   * Constructs a new event that indicates downloading a file was not
15
   * successful.
16
   *
17
   * @param url          The {@link URL} that has triggered a download event.
18
   * @param responseCode The HTTP response code associated with the failure.
19
   */
20
  public DownloadFailedEvent( final URL url, final int responseCode ) {
21
    super( url );
22
23
    mResponseCode = responseCode;
24
  }
25
26
  public static void fire( final URL url, final int responseCode ) {
27
    new DownloadFailedEvent( url, responseCode ).publish();
28
  }
29
30
  /**
31
   * Returns the HTTP response code for a failed download.
32
   *
33
   * @return An HTTP response code.
34
   */
35
  public int getResponseCode() {
36
    return mResponseCode;
37
  }
38
}
139
A src/main/java/com/keenwrite/io/downloads/events/DownloadStartedEvent.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io.downloads.events;
6
7
import java.net.URL;
8
9
/**
10
 * Collates information about a document that has started downloading.
11
 */
12
public class DownloadStartedEvent extends DownloadEvent {
13
14
  public DownloadStartedEvent( final URL url ) {
15
    super( url );
16
  }
17
18
  public static void fire( final URL url ) {
19
    new DownloadStartedEvent( url ).publish();
20
  }
21
}
122
A src/main/java/com/keenwrite/predicates/PredicateFactory.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.final
A src/main/java/com/keenwrite/preferences/AppKeys.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import static com.keenwrite.preferences.Key.key;
5
6
/**
7
 * Responsible for defining constants used throughout the application that
8
 * represent persisted preferences.
9
 */
10
public final class AppKeys {
11
  //@formatter:off
12
  private static final Key KEY_ROOT = key( "workspace" );
13
14
  public static final Key KEY_META = key( KEY_ROOT, "meta" );
15
  public static final Key KEY_META_NAME = key( KEY_META, "name" );
16
  public static final Key KEY_META_VERSION = key( KEY_META, "version" );
17
18
  public static final Key KEY_DOC = key( KEY_ROOT, "document" );
19
  public static final Key KEY_DOC_META = key( KEY_DOC, "meta" );
20
21
  public static final Key KEY_EDITOR = key( KEY_ROOT, "editor" );
22
  public static final Key KEY_EDITOR_AUTOSAVE = key( KEY_EDITOR, "autosave" );
23
24
  public static final Key KEY_R = key( KEY_ROOT, "r" );
25
  public static final Key KEY_R_SCRIPT = key( KEY_R, "script" );
26
  public static final Key KEY_R_DIR = key( KEY_R, "dir" );
27
  public static final Key KEY_R_DELIM = key( KEY_R, "delimiter" );
28
  public static final Key KEY_R_DELIM_BEGAN = key( KEY_R_DELIM, "began" );
29
  public static final Key KEY_R_DELIM_ENDED = key( KEY_R_DELIM, "ended" );
30
31
32
  public static final Key KEY_IMAGE = key( KEY_ROOT, "images" );
33
  public static final Key KEY_CACHE_DIR = key( KEY_IMAGE, "cache" );
34
  public static final Key KEY_IMAGE_DIR = key( KEY_IMAGE, "dir" );
35
  public static final Key KEY_IMAGE_ORDER = key( KEY_IMAGE, "order" );
36
  public static final Key KEY_IMAGE_RESIZE = key( KEY_IMAGE, "resize" );
37
  public static final Key KEY_IMAGE_SERVER = key( KEY_IMAGE, "server" );
38
39
  public static final Key KEY_DEF = key( KEY_ROOT, "definition" );
40
  public static final Key KEY_DEF_PATH = key( KEY_DEF, "path" );
41
  public static final Key KEY_DEF_DELIM = key( KEY_DEF, "delimiter" );
42
  public static final Key KEY_DEF_DELIM_BEGAN = key( KEY_DEF_DELIM, "began" );
43
  public static final Key KEY_DEF_DELIM_ENDED = key( KEY_DEF_DELIM, "ended" );
44
45
  public static final Key KEY_UI = key( KEY_ROOT, "ui" );
46
47
  public static final Key KEY_UI_RECENT = key( KEY_UI, "recent" );
48
  public static final Key KEY_UI_RECENT_DIR = key( KEY_UI_RECENT, "dir" );
49
  public static final Key KEY_UI_RECENT_DOCUMENT = key( KEY_UI_RECENT, "document" );
50
  public static final Key KEY_UI_RECENT_OFFSET = key( KEY_UI_RECENT, "offset" );
51
  public static final Key KEY_UI_RECENT_DEFINITION = key( KEY_UI_RECENT, "definition" );
52
  public static final Key KEY_UI_RECENT_EXPORT = key( KEY_UI_RECENT, "export" );
53
  public static final Key KEY_UI_RECENT_OPEN = key( KEY_UI_RECENT, "files" );
54
  public static final Key KEY_UI_RECENT_OPEN_PATH = key( KEY_UI_RECENT_OPEN, "path" );
55
56
  public static final Key KEY_UI_FONT = key( KEY_UI, "font" );
57
  public static final Key KEY_UI_FONT_EDITOR = key( KEY_UI_FONT, "editor" );
58
  public static final Key KEY_UI_FONT_EDITOR_NAME = key( KEY_UI_FONT_EDITOR, "name" );
59
  public static final Key KEY_UI_FONT_EDITOR_SIZE = key( KEY_UI_FONT_EDITOR, "size" );
60
  public static final Key KEY_UI_FONT_PREVIEW = key( KEY_UI_FONT, "preview" );
61
  public static final Key KEY_UI_FONT_PREVIEW_NAME = key( KEY_UI_FONT_PREVIEW, "name" );
62
  public static final Key KEY_UI_FONT_PREVIEW_SIZE = key( KEY_UI_FONT_PREVIEW, "size" );
63
  public static final Key KEY_UI_FONT_PREVIEW_MONO = key( KEY_UI_FONT_PREVIEW, "mono" );
64
  public static final Key KEY_UI_FONT_PREVIEW_MONO_NAME = key( KEY_UI_FONT_PREVIEW_MONO, "name" );
65
  public static final Key KEY_UI_FONT_PREVIEW_MONO_SIZE = key( KEY_UI_FONT_PREVIEW_MONO, "size" );
66
  public static final Key KEY_UI_FONT_MATH = key( KEY_UI_FONT, "math" );
67
  public static final Key KEY_UI_FONT_MATH_SIZE = key( KEY_UI_FONT_MATH, "size" );
68
69
  public static final Key KEY_UI_WINDOW = key( KEY_UI, "window" );
70
  public static final Key KEY_UI_WINDOW_X = key( KEY_UI_WINDOW, "x" );
71
  public static final Key KEY_UI_WINDOW_Y = key( KEY_UI_WINDOW, "y" );
72
  public static final Key KEY_UI_WINDOW_W = key( KEY_UI_WINDOW, "width" );
73
  public static final Key KEY_UI_WINDOW_H = key( KEY_UI_WINDOW, "height" );
74
  public static final Key KEY_UI_WINDOW_MAX = key( KEY_UI_WINDOW, "maximized" );
75
  public static final Key KEY_UI_WINDOW_FULL = key( KEY_UI_WINDOW, "full" );
76
77
  public static final Key KEY_UI_SKIN = key( KEY_UI, "skin" );
78
  public static final Key KEY_UI_SKIN_SELECTION = key( KEY_UI_SKIN, "selection" );
79
  public static final Key KEY_UI_SKIN_CUSTOM = key( KEY_UI_SKIN, "custom" );
80
81
  public static final Key KEY_UI_PREVIEW = key( KEY_UI, "preview" );
82
  public static final Key KEY_UI_PREVIEW_STYLESHEET = key( KEY_UI_PREVIEW, "stylesheet" );
83
84
  public static final Key KEY_LANGUAGE = key( KEY_ROOT, "language" );
85
  public static final Key KEY_LANGUAGE_LOCALE = key( KEY_LANGUAGE, "locale" );
86
87
  public static final Key KEY_TYPESET = key( KEY_ROOT, "typeset" );
88
  public static final Key KEY_TYPESET_CONTEXT = key( KEY_TYPESET, "context" );
89
  public static final Key KEY_TYPESET_CONTEXT_FONTS = key( KEY_TYPESET_CONTEXT, "fonts" );
90
  public static final Key KEY_TYPESET_CONTEXT_FONTS_DIR = key( KEY_TYPESET_CONTEXT_FONTS, "dir" );
91
  public static final Key KEY_TYPESET_CONTEXT_THEMES = key( KEY_TYPESET_CONTEXT, "themes" );
92
  public static final Key KEY_TYPESET_CONTEXT_THEMES_PATH = key( KEY_TYPESET_CONTEXT_THEMES, "path" );
93
  public static final Key KEY_TYPESET_CONTEXT_THEME_SELECTION = key( KEY_TYPESET_CONTEXT_THEMES, "selection" );
94
  public static final Key KEY_TYPESET_CONTEXT_CLEAN = key( KEY_TYPESET_CONTEXT, "clean" );
95
  public static final Key KEY_TYPESET_CONTEXT_CHAPTERS = key( KEY_TYPESET_CONTEXT, "chapters" );
96
  public static final Key KEY_TYPESET_TYPOGRAPHY = key( KEY_TYPESET, "typography" );
97
  public static final Key KEY_TYPESET_TYPOGRAPHY_QUOTES = key( KEY_TYPESET_TYPOGRAPHY, "quotes" );
98
  public static final Key KEY_TYPESET_MODES = key( KEY_TYPESET, "modes" );
99
  public static final Key KEY_TYPESET_MODES_ENABLED = key( KEY_TYPESET_MODES, "enabled" );
100
  //@formatter:on
101
102
  /**
103
   * Only for constants, do not instantiate.
104
   */
105
  private AppKeys() { }
106
}
1107
A src/main/java/com/keenwrite/preferences/FileProperty.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import javafx.beans.property.SimpleObjectProperty;
5
6
import java.io.File;
17
8
public final class FileProperty extends SimpleObjectProperty<File
A src/main/java/com/keenwrite/preferences/Key.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import java.util.Stack;
5
import java.util.function.Consumer;
6
7
/**
8
 * Responsible for creating a type hierarchy of preference storage keys.
9
 */
10
public class Key {
11
  private final Key mParent;
12
  private fin;
13
    assert !name.isBlank();
14
Answers whether more {@link Key}s exist above this one in the hierarchy.
15
   *
16
   * @return {@code true} means this {@link Key} has a parent {@link Key}.
17
   */
18
  public boolean hasP != null;
19
  }
20
21
  /**
22
   * Visits every key in the hierarchy, starting at the topmost {@link Key} and
23
   * ending with the current {@link Key}.
24
   *
25
   * @param consumer  Receives the name of each visited node.
26
   * @param separator Characters to insert between each node.
27
   */
28
  public void walk( final Consumer<String> consumer, final String separator ) {
29
    var key = this;
30
31
    final var stack = new Stack<String>();
32
33
    while( key != null ) {
34
      stack.push( key.name() );
35
      key = key.parent();
36
    }
37
38
    var sep = "";
39
40
    while( !stack.empty() ) {
41
      consumer.accept( sep + stack.pop() );
42
      sep = separator;
43
    }
44
  }
45
46
  public void walk( final Consumer<String> consumer ) {
47
    walk( consumer, "" );
48
  }
49
50
  public Key parent() {
51
    returublicfinal var sb = new StringBuilder( 128 );
52
53
    walk( sb::append, "." );
54
55
    return sb.toString();
56
  }
57
}
158
A src/main/java/com/keenwrite/preferences/LocaleProperty.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import javafx.beans.property.SimpleObjectProperty;
5
import javafx.collections.ObservableList;
16
7
import java.util.LinkedHashMap;
8
import java.utilThe {@link Locale}s are used for multiple purposes, including:
9
   *
10
   * <ul>
11
   *   <li>supported text editor font listing in preferences dialog;</li>
12
   *   <li>text editor CSS;</li>
13
   *   <li>preview window CSS; and</li>
14
   *   <li>lexicon to load for spellcheck.</li>
15
   * </ul>
16
   *www.9-suported-9 Supported@SuppressWarnings( "SpellCheckingInspection" )// English// German
17
      "de-Latn-AT",
18
      "de-Latn-DE",
19
      "de-Latn-LU",
20
      "de-Latn-CH",
21
      // Spanish
22
      "es-Latn-AR",
23
      "es-Latn-BO",
24
      "es-Latn-CL",
25
      "es-Latn-CO",
26
      "es-Latn-CR",
27
      "es-Latn-DO",
28
      "es-Latn-EC",
29
      "es-Latn-SV",
30
      "es-Latn-GT",
31
      "es-Latn-HN",
32
      "es-Latn-MX",
33
      "es-Latn-NI",
34
      "es-Latn-PA",
35
      "es-Latn-PY",
36
      "es-Latn-PE",
37
      "es-Latn-PR",
38
      "es-Latn-ES",
39
      "es-Latn-US",
40
      "es-Latn-UY",
41
      "es-Latn-VE",
42
      // French
43
      "fr-Latn-BE",
44
      "fr-Latn-CA",
45
      "fr-Latn-FR",
46
      "fr-Latn-LU",
47
      "fr-Latn-CH",
48
      // Hebrew
49
      //"iw-Hebr-IL",
50
      // Italian
51
      "it-Latn-IT",
52
      "it-Latn-CH",
53
      // Japanese
54
      "ja-Jpan-JP",
55
      // Korean
56
      "ko-Kore-KR",
57
      // Chineseund
A src/main/java/com/keenwrite/preferences/LocaleScripts.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import java.util.Collections;
5
import java.util.HashMap;
6
import java.util.Locale;
7
import java.util.Map;
18
9
import static java.util.Arrays.asList;key
A src/main/java/com/keenwrite/preferences/PreferencesController.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
13
4
import com.dlsc.formsfx.model.structure.StringField;
5
import com.dlsc.preferencesfx.PreferencesFx;
6
import com.dlsc.preferencesfx.model.CategoryTR."\{key.toString()}.desc"TR."\{key.toString()}.title"
A src/main/java/com/keenwrite/preferences/SimpleFontControl.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import com.dlsc.formsfx.model.structure.StringField;
5
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl;
6
import javafx.beans.property.DoubleProperty;
7
import javafx.beans.property.SimpleDoubleProperty;
8
import javafx.scene.control.Button;
9
import javafx.scene.control.ListView;
10
import javafx.scene.control.TextField;
11
import javafx.scene.input.KeyEvent;
12
import javafx.scene.layout.HBox;
13
import javafx.scene.layout.Region;
14
import javafx.scene.layout.StackPane;
15
import javafx.scene.text.Font;
16
import javafx.stage.Stage;
17
import org.controlsfx.dialog.FontSelectorDialog;
18
19
import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG;
20
import static com.keenwrite.events.StatusEvent.clue;
21
import static java.lang.System.currentTimeMillis;
22
import static javafx.geometry.Pos.CENTER_LEFT;
23
import static javafx.scene.control.ButtonType.CANCEL;
24
import static javafx.scene.control.ButtonType.OK;
25
import static javafx.scene.input.KeyCode.ENTER;
26
import static javafx.scene.input.KeyCode.ESCAPE;
27
import static javafx.scene.layout.Priority.ALWAYS;
28
import static javafx.scene.text.Font.font;
29
import static javafx.scene.text.Font.getDefault;
30
31
/**
32
 * Responsible for provide users the ability to select a font using a friendly
33
 * font dialog.
34
 */
35
public class SimpleFontControl extends SimpleControl<StringField, StackPane> {
36
  private final Button mButton = new Button();
37
  private final String mButtonText;
38
  private final DoubleProperty mFontSize = new SimpleDoubleProperty();
39
  private final TextField mFontName = new TextField();
40
41
  public SimpleFontControl( final String buttonText ) {
42
    mButtonText = buttonText;
43
  }
44
45
  @Override
46
  public void initializeParts() {
47
    super.initializeParts();
48
49
    mFontName.setText( field.getValue() );
50
    mFontName.setPromptText( field.placeholderProperty().getValue() );
51
52
    final var fieldProperty = field.valueProperty();
53
    if( fieldProperty.get().equals( "null" ) ) {
54
      fieldProperty.set( "" );
55
    }
56
57
    mButton.setText( mButtonText );
58
    mButton.setOnAction( event -> {
59
      final var selected = !fieldProperty.get().trim().isEmpty();
60
      var initialFont = getDefault();
61
      if( selected ) {
62
        final var previousValue = fieldProperty.get();
63
        initialFont = font( previousValue );
64
      }
65
66
      createFontSelectorDialog( initialFont )
67
        .showAndWait()
68
        .ifPresent( font -> {
69
          mFontName.setText( font.getFamily() );
70
          mFontSize.set( font.getSize() );
71
        } );
72
    } );
73
74
    node = new StackPane();
75
  }
76
77
  @Override
78
  public void layoutParts() {
79
    node.getStyleClass().add( "simple-text-control" );
80
    fieldLabel.getStyleClass().addAll( field.getStyleClass() );
81
    fieldLabel.getStyleClass().add( "read-only-label" );
82
83
    final var box = new HBox();
84
    HBox.setHgrow( mFontName, ALWAYS );
85
    box.setAlignment( CENTER_LEFT );
86
    box.getChildren().addAll( fieldLabel, mFontName, mButton );
87
88
    node.getChildren().add( box );
89
  }
90
91
  @Override
92
  public void setupBindings() {
93
    super.setupBindings();
94
    mFontName.textProperty().bindBidirectional( field.userInputProperty() );
95
  }
96
97
  public DoubleProperty fontSizeProperty() {
98
    return mFontSize;
99
  }
100
101
  /**
102
   * Creates a dialog that displays a list of available font families,
103
   * sizes, and a button for font selection.
104
   *
105
   * @param font The default font to select initially.
106
   * @return A dialog to help the user select a different {@link Font}.
107
   */
108
  private FontSelectorDialog createFontSelectorDialog( final Font font ) {
109
    final var dialog = new FontSelectorDialog( font );
110
    final var pane = dialog.getDialogPane();
111
    final var buttonOk = (Button) pane.lookupButton( OK );
112
    final var buttonCancel = (Button) pane.lookupButton( CANCEL );
113
114
    buttonOk.setDefaultButton( true );
115
    buttonCancel.setCancelButton( true );
116
    pane.setOnKeyReleased( keyEvent -> {
117
      switch( keyEvent.getCode() ) {
118
        case ENTER -> buttonOk.fire();
119
        case ESCAPE -> buttonCancel.fire();
120
        default -> { }
121
      }
122
    } );
123
124
    final var stage = (Stage) pane.getScene().getWindow();
125
    stage.getIcons().add( ICON_DIALOG );
126
127
    final var frontPanel = (Region) pane.getContent();
128
    for( final var node : frontPanel.getChildrenUnmodifiable() ) {
129
      if( node instanceof final ListView<?> listView ) {
130
        final var handler = new ListViewHandler<>( listView );
131
        listView.setOnKeyPressed( handler::handle );
132
      }
133
    }
134
135
    return dialog;
136
  }
137
138
  /**
139
   * Responsible for handling key presses when selecting a font. Based on
140
   * <a href="https://stackoverflow.com/a/43604223/59087">Martin Široký</a>'s
141
   * answer.
142
   *
143
   * @param <T> The type of {@link ListView} to search.
144
   */
145
  private static final class ListViewHandler<T> {
146
    /**
147
     * Amount of time to wait between key presses that typing a subsequent
148
     * key is considered part of the same search, in milliseconds.
149
     */
150
    private static final int RESET_DELAY_MS = 1250;
151
152
    private String mNeedle = "";
153
    private int mSearchSkip = 0;
154
    private long mLastTyped = currentTimeMillis();
155
    private final ListView<T> mHaystack;
156
157
    private ListViewHandler( final ListView<T> listView ) {
158
      mHaystack = listView;
159
    }
160
161
    private void handle( final KeyEvent key ) {
162
      var ch = key.getText();
163
      final var code = key.getCode();
164
165
      if( ch == null || ch.isEmpty() || code == ESCAPE || code == ENTER ) {
166
        return;
167
      }
168
169
      ch = ch.toUpperCase();
170
171
      if( mNeedle.equals( ch ) ) {
172
        mSearchSkip++;
173
      }
174
      else {
175
        mNeedle = currentTimeMillis() - mLastTyped > RESET_DELAY_MS
176
          ? ch : mNeedle + ch;
177
      }
178
179
      mLastTyped = currentTimeMillis();
180
181
      boolean found = false;
182
      int skipped = 0;
183
184
      for( final T item : mHaystack.getItems() ) {
185
        final var straw = item.toString().toUpperCase();
186
187
        if( straw.startsWith( mNeedle ) ) {
188
          if( mSearchSkip > skipped ) {
189
            skipped++;
190
            continue;
191
          }
192
193
          mHaystack.getSelectionModel().select( item );
194
          final int index = mHaystack.getSelectionModel().getSelectedIndex();
195
          mHaystack.getFocusModel().focus( index );
196
          mHaystack.scrollTo( index );
197
          found = true;
198
          break;
199
        }
200
      }
201
202
      if( !found ) {
203
        clue( "Main.status.font.search.missing", mNeedle );
204
        mSearchSkip = 0;
205
      }
206
    }
207
  }
208
}
1209
A src/main/java/com/keenwrite/preferences/SimpleTableControl.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl;
5
import com.keenwrite.ui.cells.AltTableCell;
6
import javafx.beans.property.SimpleO field = getField();
7
    final var table = field.createTableView(field }
8
}
19
A src/main/java/com/keenwrite/preferences/SkeletonStorageHandler.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import com.dlsc.preferencesfx.PreferencesFx;
5
import com.dlsc.preferencesfx.util.StorageHandler;
6
import javafx.collections.ObservableList;
7
8
import java.util.prefs.Preferences;
9
10
/**
11
 * Prevents {@link PreferencesFx} from saving. Saving and loading preferences
12
 * and application window state is accomplished by the {@link Workspace}. This
13
 * is required to change the user preferences file location and data format.
14
 *
15
 * @see XmlStore
16
 * @see Workspace
17
 */
18
public final class SkeletonStorageHandler implements StorageHandler {
19
  @Override
20
  public void saveSelectedCategory( final String breadcrumb ) {}
21
22
  @Override
23
  public String loadSelectedCategory() {
24
    return "";
25
  }
26
27
  @Override
28
  public void saveDividerPosition( final double dividerPosition ) {}
29
30
  @Override
31
  public double loadDividerPosition() {
32
    return 0;
33
  }
34
35
  @Override
36
  public void saveWindowWidth( final double windowWidth ) {}
37
38
  @Override
39
  public double loadWindowWidth() {
40
    return 0;
41
  }
42
43
  @Override
44
  public void saveWindowHeight( final double windowHeight ) {}
45
46
  @Override
47
  public double loadWindowHeight() {
48
    return 0;
49
  }
50
51
  @Override
52
  public void saveWindowPosX( final double windowPosX ) {}
53
54
  @Override
55
  public double loadWindowPosX() {
56
    return 0;
57
  }
58
59
  @Override
60
  public void saveWindowPosY( final double windowPosY ) {}
61
62
  @Override
63
  public double loadWindowPosY() {
64
    return 0;
65
  }
66
67
  @Override
68
  public void saveObject( final String breadcrumb, final Object object ) {}
69
70
  @Override
71
  public Object loadObject(
72
    final String breadcrumb, final Object defaultObject ) {
73
    return defaultObject;
74
  }
75
76
  @Override
77
  public <T> T loadObject(
78
    final String breadcrumb, final Class<T> type, final T defaultObject ) {
79
    return defaultObject;
80
  }
81
82
  @Override
83
  @SuppressWarnings( "rawtypes" )
84
  public ObservableList loadObservableList(
85
    final String breadcrumb, final ObservableList defaultObservableList ) {
86
    return defaultObservableList;
87
  }
88
89
  @Override
90
  public <T> ObservableList<T> loadObservableList(
91
    final String breadcrumb,
92
    final Class<T> type,
93
    final ObservableList<T> defaultObservableList ) {
94
    return defaultObservableList;
95
  }
96
97
  @Override
98
  public boolean clearPreferences() {
99
    return false;
100
  }
101
102
  public Preferences getPreferences() {
103
    return null;
104
  }
105
}
1106
A src/main/java/com/keenwrite/preferences/SkinProperty.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import com.keenwrite.constants.Constants;
5
import javafx.beans.property.SimpleObjectProperty;
6
import javafx.collections.ObservableList;
17
8
import"Monokai
A src/main/java/com/keenwrite/preferences/TableField.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import com.dlsc.formsfx.model.structure.Field;
5
import com.dlsc.formsfx.model.util.BindingMode;
6
import javafx.beans.property.ListProperty;
7
import javafx.beans.property.Property;
8
import javafx.beans.property.SimpleListProperty;
9
import javafx.scene.control.TableView;
10
11
import java.util.ArrayList;
12
13
import static com.dlsc.formsfx.model.util.BindingMode.CONTINUOUS;
14
import static javafx.collections.FXCollections.observableList;
15
16
/**
17
 * Responsible for binding a form field to a map of values that, ultimately,
18
 * users may edit.
19
 *
20
 * @param <P> The type of {@link Property} to store in the list.
21
 */
22
public class TableField<P> extends Field<TableField<P>> {
23
24
  /**
25
   * Create a writeable list as the data model.
26
   */
27
  private final ListProperty<P> mViewProperty = new SimpleListProperty<>(
28
    observableList( new ArrayList<>() )
29
  );
30
31
  /**
32
   * Contains the data model entries to persist.
33
   */
34
  private final ListProperty<P> mSaveProperty;
35
36
  /**
37
   * Creates a new {@link TableField} with a reference to the list that is to
38
   * be persisted.
39
   *
40
   * @param persist A list of items that will be persisted.
41
   * @param <P>     The type of elements in the list to persist.
42
   * @return A new {@link TableField} used to help render a UI widget.
43
   */
44
  public static <P> TableField<P> ofListType( final ListProperty<P> persist ) {
45
    return new TableField<>( persist );
46
  }
47
48
  private TableField( final ListProperty<P> property ) {
49
    mSaveProperty = property;
50
  }
51
52
  public TableView<P> createTableView() {
53
    return new TableView<>( mViewProperty );
54
  }
55
56
  public void add( final P entry ) {
57
    mViewProperty.add( entry );
58
  }
59
60
  /**
61
   * Called when a new UI instance is opened.
62
   *
63
   * @param bindingMode Indicates how the view data model is bound to the
64
   *                    persistence data model.
65
   */
66
  @Override
67
  public void setBindingMode( final BindingMode bindingMode ) {
68
    if( CONTINUOUS.equals( bindingMode ) ) {
69
      mViewProperty.addAll( mSaveProperty );
70
    }
71
  }
72
73
  /**
74
   * Answers whether the user input is valid.
75
   *
76
   * @return {@code true} Users may provide any key or value strings.
77
   */
78
  @Override
79
  protected boolean validate() {
80
    return true;
81
  }
82
83
  /**
84
   * Update the properties to save by copying the properties updated in the
85
   * user interface (i.e., the view). To be clear, the properties are not
86
   * persisted after calling this method, merely moved out of the UI data
87
   * model and into the to-be-saved data model.
88
   */
89
  @Override
90
  public void persist() {
91
    mSaveProperty.clear();
92
    mSaveProperty.addAll( mViewProperty );
93
  }
94
95
  /**
96
   * The {@link TableField} doesn't bind values, as such the reset can be
97
   * a no-op because only {@link #persist()} will update the properties to
98
   * save.
99
   */
100
  @Override
101
  public void reset() {}
102
}
1103
A src/main/java/com/keenwrite/preferences/Workspace.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preferences;
3
4
import com.keenwrite.events.workspace.WorkspaceLoadedEvent;
5
import javafx.application.Platform;
6
import javafx.beans.property.*;
7
import javafx.collections.ObservableList;
8
9
import java.io.File;
10
import java.nio.file.Path;
11
import java.util.*;
12
import java.util.Map.Entry;
13
import java.util.function.BooleanSupplier;
14
import java.util.function.Function;
15
16
import static com.keenwrite.Bootstrap.*;
17
import static com.keenwrite.Launcher.getVersion;
18
import static com.keenwrite.constants.Constants.*;
19
import static com.keenwrite.events.StatusEvent.clue;
20
import static com.keenwrite.preferences.AppKeys.*;
21
import static java.util.Map.entry;
22
import static javafx.application.Platform.runLater;
23
import static javafx.collections.FXCollections.observableArrayList;
24
import static javafx.collections.FXCollections.observableSet;
25
26
/**
27
 * Responsible for defining behaviours for separate projects. A workspace has
28
 * the ability to save and restore a session, including the window dimensions,
29
 * tab setup, files, and user preferences.
30
 * <p>
31
 * The configuration must support hierarchical (nested) configuration nodes
32
 * to persist the user interface state. Although possible with a flat
33
 * configuration file, it's not nearly as simple or elegant.
34
 * </p>
35
 * <p>
36
 * Neither JSON nor HOCON support schema validation and versioning, which makes
37
 * XML the more suitable configuration file format. Schema validation and
38
 * versioning provide future-proofing and ease of reading and upgrading previous
39
 * versions of the configuration file.
40
 * </p>
41
 * <p>
42
 * Persistent preferences may be set directly by the user or indirectly by
43
 * the act of using the application.
44
 * </p>
45
 * <p>
46
 * Note the following definitions:
47
 * </p>
48
 * <dl>
49
 *   <dt>File</dt>
50
 *   <dd>References a file name (no path), path, or directory.</dd>
51
 *   <dt>Path</dt>
52
 *   <dd>Fully qualified file name, which includes all parent directories.</dd>
53
 *   <dt>Dir</dt>
54
 *   <dd>Directory without file name ({@link File#isDirectory()} is true).</dd>
55
 * </dl>
56
 */
57
public final class Workspace {
58
59
  /**
60
   * Main configuration values, single text strings.
61
   */
62
  private final Map<Key, Property<?>> mValues = Map.ofEntries(
63
    entry( KEY_META_VERSION, asStringProperty( getVersion() ) ),
64
    entry( KEY_META_NAME, asStringProperty( "default" ) ),
65
66
    entry( KEY_EDITOR_AUTOSAVE, asIntegerProperty( 30 ) ),
67
68
    entry( KEY_R_SCRIPT, asStringProperty( "" ) ),
69
    entry( KEY_R_DIR, asFileProperty( USER_DIRECTORY ) ),
70
    entry( KEY_R_DELIM_BEGAN, asStringProperty( R_DELIM_BEGAN_DEFAULT ) ),
71
    entry( KEY_R_DELIM_ENDED, asStringProperty( R_DELIM_ENDED_DEFAULT ) ),
72
73
    entry( KEY_CACHE_DIR, asFileProperty( USER_CACHE_DIR ) ),
74
    entry( KEY_IMAGE_DIR, asFileProperty( USER_DIRECTORY ) ),
75
    entry( KEY_IMAGE_ORDER, asStringProperty( PERSIST_IMAGES_DEFAULT ) ),
76
    entry( KEY_IMAGE_RESIZE, asBooleanProperty( true ) ),
77
    entry( KEY_IMAGE_SERVER, asStringProperty( DIAGRAM_SERVER_NAME ) ),
78
79
    entry( KEY_DEF_PATH, asFileProperty( DEFINITION_DEFAULT ) ),
80
    entry( KEY_DEF_DELIM_BEGAN, asStringProperty( DEF_DELIM_BEGAN_DEFAULT ) ),
81
    entry( KEY_DEF_DELIM_ENDED, asStringProperty( DEF_DELIM_ENDED_DEFAULT ) ),
82
83
    entry( KEY_UI_RECENT_DIR, asFileProperty( USER_DIRECTORY ) ),
84
    entry( KEY_UI_RECENT_OFFSET, asIntegerProperty( DOCUMENT_OFFSET ) ),
85
    entry( KEY_UI_RECENT_DOCUMENT, asFileProperty( DOCUMENT_DEFAULT ) ),
86
    entry( KEY_UI_RECENT_DEFINITION, asFileProperty( DEFINITION_DEFAULT ) ),
87
    entry( KEY_UI_RECENT_EXPORT, asFileProperty( PDF_DEFAULT ) ),
88
89
    //@formatter:off
90
    entry(
91
      KEY_UI_FONT_EDITOR_NAME,
92
      asStringProperty( FONT_NAME_EDITOR_DEFAULT )
93
    ),
94
    entry(
95
     KEY_UI_FONT_EDITOR_SIZE,
96
     asDoubleProperty( FONT_SIZE_EDITOR_DEFAULT )
97
    ),
98
    entry(
99
     KEY_UI_FONT_PREVIEW_NAME,
100
     asStringProperty( FONT_NAME_PREVIEW_DEFAULT )
101
    ),
102
    entry(
103
     KEY_UI_FONT_PREVIEW_SIZE,
104
     asDoubleProperty( FONT_SIZE_PREVIEW_DEFAULT )
105
    ),
106
    entry(
107
     KEY_UI_FONT_PREVIEW_MONO_NAME,
108
     asStringProperty( FONT_NAME_PREVIEW_MONO_NAME_DEFAULT )
109
    ),
110
    entry(
111
     KEY_UI_FONT_PREVIEW_MONO_SIZE,
112
     asDoubleProperty( FONT_SIZE_PREVIEW_MONO_SIZE_DEFAULT )
113
    ),
114
    entry(
115
      KEY_UI_FONT_MATH_SIZE,
116
      asDoubleProperty( FONT_SIZE_MATH_DEFAULT )
117
    ),
118
119
    entry( KEY_UI_WINDOW_X, asDoubleProperty( WINDOW_X_DEFAULT ) ),
120
    entry( KEY_UI_WINDOW_Y, asDoubleProperty( WINDOW_Y_DEFAULT ) ),
121
    entry( KEY_UI_WINDOW_W, asDoubleProperty( WINDOW_W_DEFAULT ) ),
122
    entry( KEY_UI_WINDOW_H, asDoubleProperty( WINDOW_H_DEFAULT ) ),
123
    entry( KEY_UI_WINDOW_MAX, asBooleanProperty() ),
124
    entry( KEY_UI_WINDOW_FULL, asBooleanProperty() ),
125
126
    entry( KEY_UI_SKIN_SELECTION, asSkinProperty( SKIN_DEFAULT ) ),
127
    entry( KEY_UI_SKIN_CUSTOM, asFileProperty( SKIN_CUSTOM_DEFAULT ) ),
128
129
    entry(
130
      KEY_UI_PREVIEW_STYLESHEET, asFileProperty( PREVIEW_CUSTOM_DEFAULT )
131
    ),
132
133
    entry( KEY_LANGUAGE_LOCALE, asLocaleProperty( LOCALE_DEFAULT ) ),
134
135
    entry( KEY_TYPESET_CONTEXT_CLEAN, asBooleanProperty( true ) ),
136
    entry( KEY_TYPESET_CONTEXT_FONTS_DIR, asFileProperty( getFontDirectory() ) ),
137
    entry( KEY_TYPESET_CONTEXT_THEMES_PATH, asFileProperty( USER_DIRECTORY ) ),
138
    entry( KEY_TYPESET_CONTEXT_THEME_SELECTION, asStringProperty( "boschet" ) ),
139
    entry( KEY_TYPESET_CONTEXT_CHAPTERS, asStringProperty( "" ) ),
140
    entry( KEY_TYPESET_TYPOGRAPHY_QUOTES, asBooleanProperty( true ) ),
141
    entry( KEY_TYPESET_MODES_ENABLED, asStringProperty( "" ) )
142
    //@formatter:on
143
  );
144
145
  /**
146
   * Sets of configuration values, all the same type (e.g., file names),
147
   * where the key name doesn't change per set.
148
   */
149
  private final Map<Key, SetProperty<?>> mSets = Map.ofEntries(
150
    entry(
151
      KEY_UI_RECENT_OPEN_PATH,
152
      createSetProperty( new HashSet<String>() )
153
    )
154
  );
155
156
  /**
157
   * Lists of configuration values, such as key-value pairs where both the
158
   * key name and the value must be preserved per list.
159
   */
160
  private final Map<Key, ListProperty<?>> mLists = Map.ofEntries(
161
    entry(
162
      KEY_DOC_META,
163
      createListProperty( new LinkedList<Entry<String, String>>() )
164
    )
165
  );
166
167
  /**
168
   * Helps instantiate {@link Property} instances for XML configuration items.
169
   */
170
  private static final Map<Class<?>, Function<String, Object>> UNMARSHALL =
171
    Map.of(
172
      LocaleProperty.class, LocaleProperty::parseLocale,
173
      SimpleBooleanProperty.class, Boolean::parseBoolean,
174
      SimpleIntegerProperty.class, Integer::parseInt,
175
      SimpleDoubleProperty.class, Double::parseDouble,
176
      SimpleFloatProperty.class, Float::parseFloat,
177
      SimpleStringProperty.class, String::new,
178
      SimpleObjectProperty.class, String::new,
179
      SkinProperty.class, String::new,
180
      FileProperty.class, File::new
181
    );
182
183
  /**
184
   * The asymmetry with respect to {@link #UNMARSHALL} is because most objects
185
   * can simply call {@link Object#toString()} to convert the value to a string.
186
   */
187
  private static final Map<Class<?>, Function<String, Object>> MARSHALL =
188
    Map.of(
189
      LocaleProperty.class, LocaleProperty::toLanguageTag
190
    );
191
192
  /**
193
   * Converts the given {@link Property} value to a string.
194
   *
195
   * @param property The {@link Property} to convert.
196
   * @return A string representation of the given property, or the empty
197
   * string if no conversion was possible.
198
   */
199
  private static String marshall( final Property<?> property ) {
200
    final var v = property.getValue();
201
202
    return v == null
203
      ? ""
204
      : MARSHALL
205
      .getOrDefault( property.getClass(), _ -> property.getValue() )
206
      .apply( v.toString() )
207
      .toString();
208
  }
209
210
  private static Object unmarshall(
211
    final Property<?> property, final Object configValue ) {
212
    final var v = configValue.toString();
213
214
    return UNMARSHALL
215
      .getOrDefault( property.getClass(), _ -> property.getValue() )
216
      .apply( v );
217
  }
218
219
  /**
220
   * Creates an instance of {@link ObservableList} that is based on a
221
   * modifiable observable array list for the given items.
222
   *
223
   * @param items The items to wrap in an observable list.
224
   * @param <E>   The type of items to add to the list.
225
   * @return An observable property that can have its contents modified.
226
   */
227
  public static <E> ObservableList<E> listProperty( final Set<E> items ) {
228
    return new SimpleListProperty<>( observableArrayList( items ) );
229
  }
230
231
  private static <E> SetProperty<E> createSetProperty( final Set<E> set ) {
232
    return new SimpleSetProperty<>( observableSet( set ) );
233
  }
234
235
  private static <E> ListProperty<E> createListProperty( final List<E> list ) {
236
    return new SimpleListProperty<>( observableArrayList( list ) );
237
  }
238
239
  private static StringProperty asStringProperty( final String value ) {
240
    return new SimpleStringProperty( value );
241
  }
242
243
  private static BooleanProperty asBooleanProperty() {
244
    return new SimpleBooleanProperty();
245
  }
246
247
  /**
248
   * @param value Default value.
249
   */
250
  @SuppressWarnings( "SameParameterValue" )
251
  private static BooleanProperty asBooleanProperty( final boolean value ) {
252
    return new SimpleBooleanProperty( value );
253
  }
254
255
  /**
256
   * @param value Default value.
257
   */
258
  @SuppressWarnings( "SameParameterValue" )
259
  private static IntegerProperty asIntegerProperty( final int value ) {
260
    return new SimpleIntegerProperty( value );
261
  }
262
263
  /**
264
   * @param value Default value.
265
   */
266
  private static DoubleProperty asDoubleProperty( final double value ) {
267
    return new SimpleDoubleProperty( value );
268
  }
269
270
  /**
271
   * @param value Default value.
272
   */
273
  private static FileProperty asFileProperty( final File value ) {
274
    return new FileProperty( value );
275
  }
276
277
  /**
278
   * @param value Default value.
279
   */
280
  @SuppressWarnings( "SameParameterValue" )
281
  private static LocaleProperty asLocaleProperty( final Locale value ) {
282
    return new LocaleProperty( value );
283
  }
284
285
  /**
286
   * @param value Default value.
287
   */
288
  @SuppressWarnings( "SameParameterValue" )
289
  private static SkinProperty asSkinProperty( final String value ) {
290
    return new SkinProperty( value );
291
  }
292
293
  /**
294
   * Creates a new {@link Workspace} that will attempt to load the users'
295
   * preferences. If the configuration file cannot be loaded, the workspace
296
   * settings returns default values.
297
   */
298
  public Workspace() {
299
    load();
300
  }
301
302
  /**
303
   * Attempts to load the app's configuration file.
304
   */
305
  private void load() {
306
    final var store = createXmlStore();
307
    store.load( FILE_PREFERENCES );
308
309
    mValues.keySet().forEach( key -> {
310
      try {
311
        final var storeValue = store.getValue( key );
312
        final var property = valuesProperty( key );
313
        final var unmarshalled = unmarshall( property, storeValue );
314
315
        property.setValue( unmarshalled );
316
      } catch( final NoSuchElementException ex ) {
317
        // When no configuration (item), use the default value.
318
        clue( ex );
319
      }
320
    } );
321
322
    mSets.keySet().forEach( key -> {
323
      final var set = store.getSet( key );
324
      final SetProperty<String> property = setsProperty( key );
325
326
      property.setValue( observableSet( set ) );
327
    } );
328
329
    mLists.keySet().forEach( key -> {
330
      final var map = store.getMap( key );
331
      final ListProperty<Entry<String, String>> property = listsProperty( key );
332
      final var list = map
333
        .entrySet()
334
        .stream()
335
        .toList();
336
337
      property.setValue( observableArrayList( list ) );
338
    } );
339
340
    WorkspaceLoadedEvent.fire( this );
341
  }
342
343
  /**
344
   * Saves the current workspace.
345
   */
346
  public void save() {
347
    final var store = createXmlStore();
348
349
    try {
350
      // Update the string values to include the application version.
351
      valuesProperty( KEY_META_VERSION ).setValue( getVersion() );
352
353
      mValues.forEach( ( k, v ) -> store.setValue( k, marshall( v ) ) );
354
      mSets.forEach( store::setSet );
355
      mLists.forEach( store::setMap );
356
357
      store.save( FILE_PREFERENCES );
358
    } catch( final Exception ex ) {
359
      clue( ex );
360
    }
361
  }
362
363
  /**
364
   * Returns a value that represents a setting in the application that the user
365
   * may configure, either directly or indirectly.
366
   *
367
   * @param key The reference to the users' preference stored in deference
368
   *            of app reëntrance.
369
   * @return An observable property to be persisted.
370
   */
371
  @SuppressWarnings( "unchecked" )
372
  public <T, U extends Property<T>> U valuesProperty( final Key key ) {
373
    assert key != null;
374
    return (U) mValues.get( key );
375
  }
376
377
  /**
378
   * Returns a set of values that represent a setting in the application that
379
   * the user may configure, either directly or indirectly. The property
380
   * returned is backed by a {@link Set}.
381
   *
382
   * @param key The {@link Key} associated with a preference value.
383
   * @return An observable property to be persisted.
384
   */
385
  @SuppressWarnings( "unchecked" )
386
  public <T> SetProperty<T> setsProperty( final Key key ) {
387
    assert key != null;
388
    return (SetProperty<T>) mSets.get( key );
389
  }
390
391
  /**
392
   * Returns a list of values that represent a setting in the application that
393
   * the user may configure, either directly or indirectly. The property
394
   * returned is backed by a mutable {@link List}.
395
   *
396
   * @param key The {@link Key} associated with a preference value.
397
   * @return An observable property to be persisted.
398
   */
399
  @SuppressWarnings( "unchecked" )
400
  public <K, V> ListProperty<Entry<K, V>> listsProperty( final Key key ) {
401
    assert key != null;
402
    return (ListProperty<Entry<K, V>>) mLists.get( key );
403
  }
404
405
  /**
406
   * Returns the {@link String} {@link Property} associated with the given
407
   * {@link Key} from the internal list of preference values. The caller
408
   * must be sure that the given {@link Key} is associated with a {@link File}
409
   * {@link Property}.
410
   *
411
   * @param key The {@link Key} associated with a preference value.
412
   * @return The value associated with the given {@link Key}.
413
   */
414
  public StringProperty stringProperty( final Key key ) {
415
    assert key != null;
416
    return valuesProperty( key );
417
  }
418
419
  /**
420
   * Returns the {@link Boolean} {@link Property} associated with the given
421
   * {@link Key} from the internal list of preference values. The caller
422
   * must be sure that the given {@link Key} is associated with a {@link File}
423
   * {@link Property}.
424
   *
425
   * @param key The {@link Key} associated with a preference value.
426
   * @return The value associated with the given {@link Key}.
427
   */
428
  public BooleanProperty booleanProperty( final Key key ) {
429
    assert key != null;
430
    return valuesProperty( key );
431
  }
432
433
  /**
434
   * Returns the {@link Integer} {@link Property} associated with the given
435
   * {@link Key} from the internal list of preference values. The caller
436
   * must be sure that the given {@link Key} is associated with a {@link File}
437
   * {@link Property}.
438
   *
439
   * @param key The {@link Key} associated with a preference value.
440
   * @return The value associated with the given {@link Key}.
441
   */
442
  public IntegerProperty integerProperty( final Key key ) {
443
    assert key != null;
444
    return valuesProperty( key );
445
  }
446
447
  /**
448
   * Returns the {@link Double} {@link Property} associated with the given
449
   * {@link Key} from the internal list of preference values. The caller
450
   * must be sure that the given {@link Key} is associated with a {@link File}
451
   * {@link Property}.
452
   *
453
   * @param key The {@link Key} associated with a preference value.
454
   * @return The value associated with the given {@link Key}.
455
   */
456
  public DoubleProperty doubleProperty( final Key key ) {
457
    assert key != null;
458
    return valuesProperty( key );
459
  }
460
461
  /**
462
   * Returns the {@link File} {@link Property} associated with the given
463
   * {@link Key} from the internal list of preference values. The caller
464
   * must be sure that the given {@link Key} is associated with a {@link File}
465
   * {@link Property}.
466
   *
467
   * @param key The {@link Key} associated with a preference value.
468
   * @return The value associated with the given {@link Key}.
469
   */
470
  public ObjectProperty<File> fileProperty( final Key key ) {
471
    assert key != null;
472
    return valuesProperty( key );
473
  }
474
475
  /**
476
   * Returns the {@link Locale} {@link Property} associated with the given
477
   * {@link Key} from the internal list of preference values. The caller
478
   * must be sure that the given {@link Key} is associated with a {@link File}
479
   * {@link Property}.
480
   *
481
   * @param key The {@link Key} associated with a preference value.
482
   * @return The value associated with the given {@link Key}.
483
   */
484
  public LocaleProperty localeProperty( final Key key ) {
485
    assert key != null;
486
    return valuesProperty( key );
487
  }
488
489
  public ObjectProperty<String> skinProperty( final Key key ) {
490
    assert key != null;
491
    return valuesProperty( key );
492
  }
493
494
  public String getString( final Key key ) {
495
    assert key != null;
496
    return stringProperty( key ).get();
497
  }
498
499
  /**
500
   * Returns the {@link Boolean} preference value associated with the given
501
   * {@link Key}. The caller must be sure that the given {@link Key} is
502
   * associated with a value that matches the return type.
503
   *
504
   * @param key The {@link Key} associated with a preference value.
505
   * @return The value associated with the given {@link Key}.
506
   */
507
  public boolean getBoolean( final Key key ) {
508
    assert key != null;
509
    return booleanProperty( key ).get();
510
  }
511
512
  /**
513
   * Returns the {@link Integer} preference value associated with the given
514
   * {@link Key}. The caller must be sure that the given {@link Key} is
515
   * associated with a value that matches the return type.
516
   *
517
   * @param key The {@link Key} associated with a preference value.
518
   * @return The value associated with the given {@link Key}.
519
   */
520
  @SuppressWarnings( "unused" )
521
  public int getInteger( final Key key ) {
522
    assert key != null;
523
    return integerProperty( key ).get();
524
  }
525
526
  /**
527
   * Returns the {@link Double} preference value associated with the given
528
   * {@link Key}. The caller must be sure that the given {@link Key} is
529
   * associated with a value that matches the return type.
530
   *
531
   * @param key The {@link Key} associated with a preference value.
532
   * @return The value associated with the given {@link Key}.
533
   */
534
  public double getDouble( final Key key ) {
535
    assert key != null;
536
    return doubleProperty( key ).get();
537
  }
538
539
  /**
540
   * Returns the {@link File} preference value associated with the given
541
   * {@link Key}. The caller must be sure that the given {@link Key} is
542
   * associated with a value that matches the return type.
543
   *
544
   * @param key The {@link Key} associated with a preference value.
545
   * @return The value associated with the given {@link Key}.
546
   */
547
  public File getFile( final Key key ) {
548
    assert key != null;
549
    return fileProperty( key ).get();
550
  }
551
552
  /**
553
   * Returns the language locale setting for the
554
   * {@link AppKeys#KEY_LANGUAGE_LOCALE} key.
555
   *
556
   * @return The user's current locale setting.
557
   */
558
  public Locale getLocale() {
559
    return localeProperty( KEY_LANGUAGE_LOCALE ).toLocale();
560
  }
561
562
  @SuppressWarnings( "unchecked" )
563
  public <K, V> Map<K, V> getMetadata() {
564
    final var metadata = listsProperty( KEY_DOC_META );
565
    final HashMap<K, V> map;
566
567
    if( metadata != null ) {
568
      map = new HashMap<>( metadata.size() );
569
570
      metadata.forEach(
571
        entry -> map.put( (K) entry.getKey(), (V) entry.getValue() )
572
      );
573
    }
574
    else {
575
      map = new HashMap<>();
576
    }
577
578
    return map;
579
  }
580
581
  public Path getThemesPath() {
582
    final var dir = getFile( KEY_TYPESET_CONTEXT_THEMES_PATH );
583
    final var name = getString( KEY_TYPESET_CONTEXT_THEME_SELECTION );
584
585
    return Path.of( dir.toString(), name );
586
  }
587
588
  /**
589
   * Delegates to {@link #listen(Key, ReadOnlyProperty, BooleanSupplier)},
590
   * providing a value of {@code true} for the {@link BooleanSupplier} to
591
   * indicate the property changes always take effect.
592
   *
593
   * @param key      The value to bind to the internal key property.
594
   * @param property The external property value that sets the internal value.
595
   */
596
  public <T> void listen( final Key key, final ReadOnlyProperty<T> property ) {
597
    assert key != null;
598
    assert property != null;
599
600
    listen( key, property, () -> true );
601
  }
602
603
  /**
604
   * Binds a read-only property to a value in the preferences. This allows
605
   * user interface properties to change and the preferences will be
606
   * synchronized automatically.
607
   * <p>
608
   * This calls {@link Platform#runLater(Runnable)} to ensure that all pending
609
   * application window states are finished before assessing whether property
610
   * changes should be applied. Without this, exiting the application while the
611
   * window is maximized would persist the window's maximum dimensions,
612
   * preventing restoration to its prior, non-maximum size.
613
   *
614
   * @param key      The value to bind to the internal key property.
615
   * @param property The external property value that sets the internal value.
616
   * @param enabled  Indicates whether property changes should be applied.
617
   */
618
  public <T> void listen(
619
    final Key key,
620
    final ReadOnlyProperty<T> property,
621
    final BooleanSupplier enabled ) {
622
    assert key != null;
623
    assert property != null;
624
    assert enabled != null;
625
626
    property.addListener(
627
      ( _, _, n ) -> runLater( () -> {
628
        if( enabled.getAsBoolean() ) {
629
          valuesProperty( key ).setValue( n );
630
        }
631
      } )
632
    );
633
  }
634
635
  /**
636
   * Creates a lightweight persistence mechanism for user preferences.
637
   *
638
   * @return The {@link XmlStore} that helps with persisting application state.
639
   */
640
  private XmlStore createXmlStore() {
641
    // Root-level configuration item is the application name.
642
    return new XmlStore( APP_TITLE_LOWERCASE );
643
  }
644
}
1645
A src/main/java/com/keenwrite/preferences/XmlStore.java
1
package com.keenwrite.preferences;
2
3
import com.keenwrite.dom.DocumentParser;
4
import javafx.beans.property.ListProperty;
5
import javafx.beans.property.SetProperty;
6
import org.w3c.dom.Document;
7
import org.w3c.dom.Element;
8
import org.w3c.dom.Node;
9
10
import javax.xml.xpath.XPath;
11
import javax.xml.xpath.XPathExpression;
12
import javax.xml.xpath.XPathExpressionException;
13
import java.io.File;
14
import java.io.FileWriter;
15
import java.io.IOException;
16
import java.util.*;
17
import java.util.Map.Entry;
18
import java.util.function.Consumer;
19
20
import static com.keenwrite.events.StatusEvent.clue;
21
import static java.nio.charset.StandardCharsets.UTF_8;
22
import static javax.xml.xpath.XPathConstants.NODE;
23
24
/**
25
 * Responsible for managing XML documents, which includes reading, writing,
26
 * retrieving, and setting elements. This is an alternative to Apache
27
 * Commons Configuration, JAXB, and Jackson. All of them are heavyweight and
28
 * the latter are difficult to use with dynamic data (because they require
29
 * annotations).
30
 * <p>
31
 * <strong>Note:</strong> It is preferable to use a different instance when
32
 * loading and saving the documents. Otherwise, old and duplicate data will
33
 * persist. Using a new instance ensures that elements removed from the
34
 * user preferences will not persist across XML configuration file versions.
35
 */
36
public class XmlStore {
37
  private static final String SEPARATOR = "/";
38
39
  private final String mRoot;
40
  private Document mDocument = DocumentParser.newDocument();
41
42
  /**
43
   * Constructs a new instance with a blank {@link Document}. Call the
44
   * {@link #load(File)} method to populate the document from a {@link File},
45
   * or {@link #save(File)} to persist the current document state.
46
   *
47
   * @param root The root-level document element.
48
   */
49
  public XmlStore( final String root ) {
50
    assert root != null;
51
52
    mRoot = root;
53
  }
54
55
  /**
56
   * Loads the given configuration file into a document object model.
57
   * Clients of this class can set and retrieve elements via the requisite
58
   * access methods.
59
   *
60
   * @param config File containing persistent user preferences.
61
   */
62
  public void load( final File config ) {
63
    assert config != null;
64
    assert config.isFile();
65
66
    try {
67
      mDocument = DocumentParser.parse( config );
68
    } catch( final Exception ignored ) {
69
      mDocument = DocumentParser.newDocument();
70
    }
71
  }
72
73
  /**
74
   * Returns the document value associated with the given key name.
75
   *
76
   * @param key {@link Key} name to retrieve.
77
   * @return The value associated with the key.
78
   * @throws NoSuchElementException No value could be found for the key.
79
   */
80
  public String getValue( final Key key ) throws NoSuchElementException {
81
    assert key != null;
82
83
    try {
84
      final var node = toNode( key, mDocument );
85
86
      if( node != null ) {
87
        return node.getTextContent();
88
      }
89
    } catch( final XPathExpressionException ignored ) { }
90
91
    throw new NoSuchElementException( key.toString() );
92
  }
93
94
  /**
95
   * Returns a set of document values associated with the given key name. This
96
   * is suitable for basic sets, such as:
97
   * <pre>
98
   *   {@code
99
   *   <recent>
100
   *     <file>/tmp/filename.txt</file>
101
   *     <file>/home/username/document.md</file>
102
   *     <file>/usr/local/share/app/conf/help.Rmd</file>
103
   *   </recent>}
104
   * </pre>
105
   * <p>
106
   * The {@code file} element name can be ignored.
107
   *
108
   * @param key {@link Key} name to retrieve.
109
   * @return The values associated with the key, or an empty set if none found.
110
   */
111
  public Set<String> getSet( final Key key ) {
112
    assert key != null;
113
114
    final var set = new LinkedHashSet<String>();
115
116
    visit( key, node -> set.add( node.getTextContent() ) );
117
118
    return set;
119
  }
120
121
  /**
122
   * Returns a map of name/value pairs associated with the given key name.
123
   * This is suitable for mapped values, such as:
124
   * <pre>
125
   *   {@code
126
   *   <meta>
127
   *     <title>{{book.title}}</title>
128
   *     <author>{{book.author}}</author>
129
   *     <date>{{book.publish.date}}</date>
130
   *   </meta>}
131
   * </pre>
132
   * <p>
133
   * The element names under the {@code meta} node must be preserved along
134
   * with their values. Resolving the values based on the variable definitions
135
   * (in moustache syntax) is not a responsibility of this class.
136
   *
137
   * @param key {@link Key} name to retrieve (e.g., {@code meta}).
138
   * @return A map of element names to element values, or an empty map if
139
   * none found.
140
   */
141
  public Map<String, String> getMap( final Key key ) {
142
    assert key != null;
143
144
    // Create a new key that will match all child nodes under the given key,
145
    // extracting each element as a name/value pair for the resulting map.
146
    final var all = Key.key( key, "*" );
147
    final var map = new LinkedHashMap<String, String>();
148
149
    visit( all, node -> map.put( node.getNodeName(), node.getTextContent() ) );
150
151
    return map;
152
  }
153
154
  /**
155
   * Call to write the user preferences to a file.
156
   *
157
   * @param config The file wherein the preferences are saved.
158
   * @throws IOException Could not write to the file.
159
   */
160
  public void save( final File config ) throws IOException {
161
    assert config != null;
162
163
    try( final var writer = new FileWriter( config, UTF_8 ) ) {
164
      writer.write( DocumentParser.toString( mDocument ) );
165
    }
166
  }
167
168
  public void setValue( final Key key, final String value ) {
169
    assert key != null;
170
    assert value != null;
171
172
    try {
173
      final var node = upsert( key, mDocument );
174
175
      node.setTextContent( value );
176
    } catch( final XPathExpressionException ex ) {
177
      clue( ex );
178
    }
179
  }
180
181
  public void setSet( final Key key, final SetProperty<?> set ) {
182
    assert key != null;
183
    assert set != null;
184
185
    Node node = null;
186
187
    try {
188
      for( final var item : set ) {
189
        if( node == null ) {
190
          node = upsert( key, mDocument );
191
        }
192
        else {
193
          final var doc = node.getOwnerDocument();
194
          final var sibling = doc.createElement( key.name() );
195
          var parent = node.getParentNode();
196
197
          if( parent == null ) {
198
            parent = doc.getDocumentElement();
199
          }
200
201
          parent.appendChild( sibling );
202
          node = sibling;
203
        }
204
205
        node.setTextContent( item.toString() );
206
      }
207
    } catch( final XPathExpressionException ignored ) { }
208
  }
209
210
  /**
211
   * @param key  The application key representing a user preference.
212
   * @param list List of {@link Entry} items.
213
   */
214
  public void setMap( final Key key, final ListProperty<?> list ) {
215
    assert key != null;
216
    assert list != null;
217
218
    for( final var item : list ) {
219
      if( item instanceof Entry<?, ?> entry ) {
220
        try {
221
          final var child = Key.key( key, entry.getKey().toString() );
222
          final var node = upsert( child, mDocument );
223
224
          node.setTextContent( entry.getValue().toString() );
225
        } catch( final XPathExpressionException ignored ) { }
226
      }
227
    }
228
  }
229
230
  private Node toNode( final Key key, final Document doc )
231
    throws XPathExpressionException {
232
    final var xpath = toXPath( key );
233
    final var expr = DocumentParser.compile( xpath );
234
    final var element = expr.evaluate( doc, NODE );
235
236
    return element instanceof Node node ? node : null;
237
  }
238
239
  /**
240
   * Provides the equivalent of update-or-insert behaviour provided by some
241
   * SQL databases. Finds the element in the document represented by the
242
   * given {@link Key}. If no element is found then the full path to the
243
   * element is created. In essence, this method converts a hierarchy of
244
   * {@link Key} names into a hierarchy of {@link Document} {@link Element}s
245
   * (i.e., {@link Node}s).
246
   * <p>
247
   * For example, given a key named {@code workspace.meta.version}, this will
248
   * produce a document structure that, when exported as XML, resembles:
249
   * <pre>{@code
250
   *   <root>
251
   *     <workspace>
252
   *       <meta>
253
   *         <version/>
254
   *       </meta>
255
   *     </workspace>
256
   *   </root>
257
   * }</pre>
258
   * <p>
259
   * The calling code is responsible for populating the {@link Node} returned
260
   * with its particular value. In the example above, the text content of the
261
   * {@link Node} would be filled with the application version number.
262
   *
263
   * @param key The application key representing a user preference.
264
   * @param doc The document that may contain an xpath for the {@link Key}.
265
   * @return The existing or new element.
266
   */
267
  private Node upsert( final Key key, final Document doc )
268
    throws XPathExpressionException {
269
    assert key != null;
270
    assert doc != null;
271
272
    final var missing = new Stack<Key>();
273
    Key visitor = key;
274
    Node parent = null;
275
276
    do {
277
      final var node = toNode( visitor, doc );
278
279
      // If an element exists on the first iteration, return it because there
280
      // is no missing hierarchy to create.
281
      if( node != null ) {
282
        if( missing.isEmpty() ) {
283
          return node;
284
        }
285
286
        parent = node;
287
      }
288
      else {
289
        // Track the number of elements in the hierarchy that don't exist.
290
        missing.push( visitor );
291
292
        // Attempt to find the parent xpath in the document.
293
        visitor = visitor.parent();
294
      }
295
    }
296
    while( visitor != null && parent == null );
297
298
    // If the document is empty, update the top-level document element.
299
    if( parent == null ) {
300
      parent = doc.getDocumentElement();
301
302
      // If there is still no top-level element, then create it.
303
      if( parent == null ) {
304
        parent = doc.createElement( mRoot );
305
        doc.appendChild( parent );
306
      }
307
    }
308
309
    assert parent != null;
310
311
    // Create the hierarchy.
312
    while( !missing.isEmpty() ) {
313
      visitor = missing.pop();
314
315
      final var child = doc.createElement( visitor.name() );
316
      parent.appendChild( child );
317
      parent = child;
318
    }
319
320
    return parent;
321
  }
322
323
  /**
324
   * Abstraction for functionality that requires iterating over multiple
325
   * nodes under a particular xpath.
326
   *
327
   * @param key      {@link #toXPath(Key) Compiled} into an {@link XPath}.
328
   * @param consumer Accepts each node that matches the {@link XPath}.
329
   */
330
  private void visit( final Key key, final Consumer<Node> consumer ) {
331
    assert key != null;
332
    assert consumer != null;
333
334
    try {
335
      final var xpath = toXPath( key );
336
337
      DocumentParser.visit( mDocument, xpath, consumer );
338
    } catch( final XPathExpressionException ignored ) {
339
      // Programming error. Triggered by loading a previous config version?
340
    }
341
  }
342
343
  /**
344
   * Creates an {@link XPathExpression} value based on the given {@link Key}.
345
   *
346
   * @param key The {@link Key} to convert to an xpath string.
347
   * @return The given {@link Key} compiled into an {@link XPathExpression}.
348
   * @throws XPathExpressionException Could not compile the {@link Key}.
349
   */
350
  private StringBuilder toXPath( final Key key )
351
    throws XPathExpressionException {
352
    assert key != null;
353
354
    final var sb = new StringBuilder( 128 );
355
356
    key.walk( sb::append, SEPARATOR );
357
    sb.insert( 0, SEPARATOR );
358
359
    if( !mRoot.isBlank() ) {
360
      sb.insert( 0, SEPARATOR + mRoot );
361
    }
362
363
    return sb;
364
  }
365
366
  /**
367
   * Pretty-prints the XML document into a string. Meant to be used for
368
   * debugging. To save the configuration, see {@link #save(File)}.
369
   *
370
   * @return The document in a well-formed, indented, string format.
371
   */
372
  @Override
373
  public String toString() {
374
    return DocumentParser.toString( mDocument );
375
  }
376
}
1377
A src/main/java/com/keenwrite/preview/ChainedReplacedElementFactory.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preview;
13
4
import com.keenwrite.collections.BoundedCache;
5
import com.keenwrite.ui.adapters.ReplacedElementAdapter;
6
import org.w3c.dom.Element;
7
import org.xht
A src/main/java/com/keenwrite/preview/DiagramUrlGenerator.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preview;
3
4
import java.nio.charset.StandardCharsets;
5
import java.util.zip.Deflater;
6
7
import static java.lang.String.format;
8
import static java.util.Base64.getUrlEncoder;
9
10
/**
11
 * Responsible for transforming text-based diagram descriptions into URLs
12
 * that the HTML renderer can embed as SVG images.
13
 */
14
public class DiagramUrlGenerator {
15
  private DiagramUrlGenerator() {
16
  }
17
18
  /**
19
   * Returns a URL that can be embedded as the {@code src} attribute to an HTML
20
   * {@code img} tag.
21
   *
22
   * @param server  Name of server to use for diagram conversion.
23
   * @param diagram Diagram type (e.g., Graphviz, Block, PlantUML).
24
   * @param text    Diagram text that conforms to the diagram type.
25
   * @return A secure URL string to use as an image {@code src} attribute.
26
   */
27
  public static String toUrl(
28
    final String server, final String diagram, final String text ) {
29
    return format(
30
      "https://%s/%s/svg/%s", server, diagram, encode( text )
31
    );
32
  }
33
34
  /**
35
   * Convert the plain-text version of the diagram into a URL-encoded value
36
   * suitable for passing to a web server using an HTTP GET request.
37
   *
38
   * @param text The diagram text to encode.
39
   * @return The URL-encoded (and compressed) version of the text.
40
   */
41
  private static String encode( final String text ) {
42
    return getUrlEncoder().encodeToString(
43
      compress( text.getBytes( StandardCharsets.UTF_8 ) )
44
    );
45
  }
46
47
  /**
48
   * Compresses a sequence of bytes using ZLIB format.
49
   *
50
   * @param source The data to compress.
51
   * @return A lossless, compressed sequence of bytes.
52
   */
53
  private static byte[] compress( final byte[] source ) {
54
    final var deflater = new Deflater();
55
    deflater.setInput( source );
56
    deflater.finish();
57
58
    final var compressed = new byte[ Short.MAX_VALUE ];
59
    final var size = deflater.deflate( compressed );
60
    final var result = new byte[ size ];
61
62
    System.arraycopy( compressed, 0, result, 0, size );
63
64
    return result;
65
  }
66
}
167
A src/main/java/com/keenwrite/preview/FlyingSaucerPanel.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preview;
3
4
import com.keenwrite.events.FileOpenEvent;
5
import com.keenwrite.events.HyperlinkOpenEvent;
6
import com.keenwrite.ui.adapters.DocumentAdapter;
7
import javafx.beans.property.BooleanProperty;
8
import javafx.beans.property.SimpleBooleanProperty;
9
import org.w3c.dom.Document;
10
import org.xhtmlrenderer.layout.SharedContext;
11
import org.xhtmlrenderer.render.Box;
12
import org.xhtmlrenderer.simple.XHTMLPanel;
13
import org.xhtmlrenderer.simple.extend.XhtmlNamespaceHandler;
14
import org.xhtmlrenderer.swing.*;
15
16
import javax.swing.*;
17
import java.awt.*;
18
import java.awt.event.ComponentAdapter;
19
import java.awt.event.ComponentEvent;
20
import java.net.URI;
21
22
import static com.keenwrite.events.StatusEvent.clue;
23
import static com.keenwrite.util.ProtocolScheme.getProtocol;
24
import static java.lang.Boolean.FALSE;
25
import static java.lang.Boolean.TRUE;
26
import static java.lang.Math.max;
27
import static java.lang.Thread.sleep;
28
import static javax.swing.SwingUtilities.invokeLater;
29
30
/**
31
 * Responsible for configuring FlyingSaucer's {@link XHTMLPanel}.
32
 */
33
public final class FlyingSaucerPanel extends XHTMLPanel
34
  implements HtmlRenderer {
35
36
  /**
37
   * Suppresses scroll attempts until after the document has loaded.
38
   */
39
  private static final class DocumentEventHandler extends DocumentAdapter {
40
    private final BooleanProperty mReadyProperty = new SimpleBooleanProperty();
41
42
    @Override
43
    public void documentStarted() {
44
      mReadyProperty.setValue( FALSE );
45
    }
46
47
    @Override
48
    public void documentLoaded() {
49
      mReadyProperty.setValue( TRUE );
50
    }
51
  }
52
53
  /**
54
   * Ensures that the preview panel fills its container's area completely.
55
   */
56
  private final class ComponentEventHandler extends ComponentAdapter {
57
    /**
58
     * Invoked when the component's size changes.
59
     */
60
    public void componentResized( final ComponentEvent e ) {
61
      setPreferredSize( e.getComponent().getPreferredSize() );
62
    }
63
  }
64
65
  /**
66
   * Responsible for opening hyperlinks. External hyperlinks are opened in
67
   * the system's default browser; local file system links are opened in the
68
   * editor.
69
   */
70
  private static final class HyperlinkListener extends LinkListener {
71
    @Override
72
    public void linkClicked( final BasicPanel panel, final String link ) {
73
      try {
74
        final var uri = new URI( link );
75
76
        switch( getProtocol( uri ) ) {
77
          case HTTP -> HyperlinkOpenEvent.fire( uri );
78
          case FILE -> FileOpenEvent.fire( uri );
79
          default -> { }
80
        }
81
      } catch( final Exception ex ) {
82
        clue( ex );
83
      }
84
    }
85
  }
86
87
  private static final XhtmlNamespaceHandler XNH = new XhtmlNamespaceHandler();
88
  private final ChainedReplacedElementFactory mFactory;
89
90
  FlyingSaucerPanel() {
91
    // The order is important: SwingReplacedElementFactory replaces SVG images
92
    // with a blank image, which will cause the chained factory to cache the
93
    // image and exit. Instead, the SVG must execute first to rasterize the
94
    // content. Consequently, the chained factory must maintain insertion order.
95
    mFactory = new ChainedReplacedElementFactory(
96
      new ImageReplacedElementFactory(),
97
      new SwingReplacedElementFactory()
98
    );
99
100
    final var context = getSharedContext();
101
    final var textRenderer = context.getTextRenderer();
102
    context.setReplacedElementFactory( mFactory );
103
    textRenderer.setSmoothingThreshold( 0 );
104
105
    addDocumentListener( new DocumentEventHandler() );
106
    removeMouseTrackingListeners();
107
    addMouseTrackingListener( new HyperlinkListener() );
108
    addComponentListener( new ComponentEventHandler() );
109
  }
110
111
  /**
112
   * Updates the document model displayed by the renderer. Effectively, this
113
   * updates the HTML document to provide new content.
114
   *
115
   * @param doc     A complete HTML5 document, including doctype.
116
   * @param baseUri URI to use for finding relative files, such as images.
117
   */
118
  @Override
119
  public void render( final Document doc, final String baseUri ) {
120
    setDocument( doc, baseUri, XNH );
121
  }
122
123
  @Override
124
  public void clearCache() {
125
    mFactory.clearCache();
126
  }
127
128
  @Override
129
  public void scrollTo( final String id, final JScrollPane scrollPane ) {
130
    int iter = 0;
131
    Box box = null;
132
133
    while( iter++ < 3 && ((box = getBoxById( id )) == null) ) {
134
      try {
135
        sleep( 10 );
136
      } catch( final Exception ex ) {
137
        clue( ex );
138
      }
139
    }
140
141
    scrollTo( box, scrollPane );
142
  }
143
144
  /**
145
   * Scrolls to the location specified by the {@link Box} that corresponds
146
   * to a point somewhere in the preview pane. If there is no caret, then
147
   * this will not change the scroll position. Changing the scroll position
148
   * to the top if the {@link Box} instance is {@code null} will result in
149
   * jumping around a lot and inconsistent synchronization issues.
150
   *
151
   * @param box The rectangular region containing the caret, or {@code null}
152
   *            if the HTML does not have a caret.
153
   */
154
  private void scrollTo( final Box box, final JScrollPane scrollPane ) {
155
    if( box != null ) {
156
      invokeLater( () -> {
157
        scrollTo( createPoint( box, scrollPane ) );
158
        scrollPane.repaint();
159
      } );
160
    }
161
  }
162
163
  /**
164
   * Creates a {@link Point} to use as a reference for scrolling to the area
165
   * described by the given {@link Box}. The {@link Box} coordinates are used
166
   * to populate the {@link Point}'s location, with minor adjustments for
167
   * vertical centering.
168
   *
169
   * @param box The {@link Box} that represents a scrolling anchor reference.
170
   * @return A coordinate suitable for scrolling to.
171
   */
172
  private Point createPoint( final Box box, final JScrollPane scrollPane ) {
173
    assert box != null;
174
175
    // Scroll back up by half the height of the scroll bar to keep the typing
176
    // area within the view port; otherwise, the view port will have jumped too
177
    // high up and the most recently typed letters won't be visible.
178
    int y = max( box.getAbsY() - scrollPane.getVerticalScrollBar()
179
                                           .getHeight() / 2, 0 );
180
    int x = box.getAbsX();
181
182
    if( !box.getStyle().isInline() ) {
183
      final var margin = box.getMargin( getLayoutContext() );
184
      y += (int) margin.top();
185
      x += (int) margin.left();
186
    }
187
188
    return new Point( x, y );
189
  }
190
191
  /**
192
   * Delegates to the {@link SharedContext}.
193
   *
194
   * @param id The HTML element identifier to retrieve in {@link Box} form.
195
   * @return The {@link Box} that corresponds to the given element ID, or
196
   * {@code null} if none found.
197
   */
198
  Box getBoxById( final String id ) {
199
    return getSharedContext().getBoxById( id );
200
  }
201
202
  /**
203
   * Suppress scrolling to the top on updates.
204
   */
205
  @Override
206
  public void resetScrollPosition() {
207
  }
208
209
  /**
210
   * The default mouse click listener attempts navigation within the preview
211
   * panel. We want to usurp that behaviour to open the link in a
212
   * platform-specific browser.
213
   */
214
  private void removeMouseTrackingListeners() {
215
    for( final var listener : getMouseTrackingListeners() ) {
216
      if( !(listener instanceof HoverListener) ) {
217
        removeMouseTrackingListener( (FSMouseListener) listener );
218
      }
219
    }
220
  }
221
}
1222
A src/main/java/com/keenwrite/preview/HighQualityRenderingHints.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preview;
3
4
import java.util.HashMap;
5
import java.util.Map;
6
7
import static java.awt.RenderingHints.*;
8
import static java.awt.Toolkit.getDefaultToolkit;
9
10
/**
11
 * Responsible for initializing settings to produce high-quality image
12
 * transformations.
13
 */
14
@SuppressWarnings( "rawtypes" )
15
public class HighQualityRenderingHints {
16
  /**
17
   * Default hints for high-quality rendering that may be changed by
18
   * the system's rendering hints.
19
   */
20
  private static final Map<Object, Object> DEFAULT_HINTS = Map.of(
21
    KEY_ANTIALIASING, VALUE_ANTIALIAS_ON,
22
    KEY_ALPHA_INTERPOLATION, VALUE_ALPHA_INTERPOLATION_QUALITY,
23
    KEY_COLOR_RENDERING, VALUE_COLOR_RENDER_QUALITY,
24
    KEY_DITHERING, VALUE_DITHER_DISABLE,
25
    KEY_FRACTIONALMETRICS, VALUE_FRACTIONALMETRICS_ON,
26
    KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC,
27
    KEY_RENDERING, VALUE_RENDER_QUALITY,
28
    KEY_STROKE_CONTROL, VALUE_STROKE_PURE,
29
    KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON
30
  );
31
32
  /**
33
   * Shared hints for high-quality rendering.
34
   */
35
  static final Map<Object, Object> RENDERING_HINTS = new HashMap<>(
36
    DEFAULT_HINTS
37
  );
38
39
  static {
40
    initializeRenderingHints();
41
  }
42
43
  private static void initializeRenderingHints() {
44
    final var toolkit = getDefaultToolkit();
45
    final var hints = toolkit.getDesktopProperty( "awt.font.desktophints" );
46
47
    if( hints instanceof final Map map ) {
48
      for( final var key : map.keySet() ) {
49
        final var hint = map.get( key );
50
        RENDERING_HINTS.put( key, hint );
51
      }
52
    }
53
  }
54
55
  /**
56
   * Defines a reusable constant, nothing more.
57
   */
58
  private HighQualityRenderingHints() {
59
  }
60
}
161
A src/main/java/com/keenwrite/preview/HtmlPreview.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preview;
3
4
import com.keenwrite.dom.DocumentConverter;
5
import com.keenwrite.events.DocumentChangedEvent;
6
import com.keenwrite.events.ScrollLockEvent;
7
import com.keenwrite.preferences.LocaleProperty;
8
import com.keenwrite.preferences.Workspace;
9
import jAppConverts a text string to a structured HTMLDocumentConverter CONVERTER = new DocumentConverter();
10
 """
11
    <!doctype html>
12
""Renderer mPrevScrollPreview = new FlyingSaucer(Component) mPrevScroll
13
        _ScrollLocked )
14
         } );
15
16
Scroll
17
      () ->ScrollLocked )final var jsoupDoc = DocumentConverter.parse( decorate( html ) );
18
    final var doc = CONVERTER.fromJsoup( jsoupDoc );
19
    final var uri = getBaseUri();
120
21
    doc.setDocumentURI( uri );mPreview.render( doc, uri ) );
22
    DocumentChangedEvent.fire( html Preview!mScrollLocked ) {
23
      mPreview.scrollTo( id, mScrollPane );
24
      mScrollPane.repaint();
25
    }{@code en-Latn-CA}String.formagetgetBoolean( KEY_IMAGE_RESIZE ) ) {
26
      mPreview
A src/main/java/com/keenwrite/preview/HtmlRenderer.java
1
package com.keenwrite.preview;
2
3
import org.w3c.dom.Document;
4
5
import javax.swing.*;
6
7
/**
8
 * Denotes the ability to render an HTML document onto a Swing component.
9
 */
10
public interface HtmlRenderer {
11
12
  /**
13
   * Renders an HTML document with respect to a base location.
14
   *
15
   * @param doc     The document to render.
16
   * @param baseUri The document's relative URI.
17
   */
18
  void render( final Document doc, final String baseUri );
19
20
  /**
21
   * Scrolls the given {@link JScrollPane} to the first HTML element that
22
   * has an {@code id} attribute that matches the given identifier.
23
   *
24
   * @param id         The HTML element identifier.
25
   * @param scrollPane The GUI widget that controls scrolling.
26
   */
27
  void scrollTo( final String id, final JScrollPane scrollPane );
28
29
  /**
30
   * Clears the cache (e.g., so that images are re-rendered using updated
31
   * dimensions).
32
   */
33
  void clearCache();
34
}
135
A src/main/java/com/keenwrite/preview/ImageReplacedElementFactory.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.preview;
6
7
import com.keenwrite.io.MediaType;
8
import com.keenwrite.ui.adapters.ReplacedElementAdapter;
9
import io.sf.carte.echosvg.transcoder.TranscoderException;
10
import org.w3c.dom.Element;
11
import org.xhtmlrenderer.extend.ReplacedElement;
12
import org.xhtmlrenderer.extend.UserAgentCallback;
13
import org.xhtmlrenderer.layout.LayoutContext;
14
import org.xhtmlrenderer.render.BlockBox;
15
import org.xhtmlrenderer.swing.ImageReplacedElement;
16
17
import javax.imageio.ImageIO;
18
import java.awt.image.BufferedImage;
19
import java.io.IOException;
20
import java.net.URI;
21
import java.net.URISyntaxException;
22
import java.nio.file.Files;
23
import java.nio.file.Path;
24
import java.nio.file.Paths;
25
import java.text.ParseException;
26
27
import static com.keenwrite.events.StatusEvent.clue;
28
import static com.keenwrite.io.downloads.DownloadManager.open;
29
import static com.keenwrite.preview.SvgRasterizer.BROKEN_IMAGE_PLACEHOLDER;
30
import static com.keenwrite.preview.SvgRasterizer.rasterize;
31
import static com.keenwrite.processors.markdown.extensions.tex.TexNode.HTML_TEX;
32
import static com.keenwrite.util.ProtocolScheme.getProtocol;
33
34
/**
35
 * Responsible for running {@link SvgRasterizer} on SVG images detected within
36
 * a document to transform them into rasterized versions. This will fall back
37
 * to loading rasterized images from a file if not detected as SVG.
38
 */
39
public final class ImageReplacedElementFactory extends ReplacedElementAdapter {
40
41
  public static final String HTML_IMAGE = "img";
42
  public static final String HTML_IMAGE_SRC = "src";
43
44
  private static final ImageReplacedElement BROKEN_IMAGE =
45
    createElement( BROKEN_IMAGE_PLACEHOLDER );
46
47
  @Override
48
  public ReplacedElement createReplacedElement(
49
    final LayoutContext c,
50
    final BlockBox box,
51
    final UserAgentCallback uac,
52
    final int cssWidth,
53
    final int cssHeight ) {
54
    final var e = box.getElement();
55
56
    try {
57
      final BufferedImage raster =
58
        switch( e.getNodeName() ) {
59
          case HTML_IMAGE -> createHtmlImage( box, e, uac );
60
          case HTML_TEX -> createTexImage( e );
61
          default -> null;
62
        };
63
64
      return createElement( raster );
65
    } catch( final Exception ex ) {
66
      clue( "Main.status.image.request.error.create", ex );
67
    }
68
69
    return BROKEN_IMAGE;
70
  }
71
72
  /**
73
   * Convert an HTML element to a raster graphic.
74
   */
75
  private static BufferedImage createHtmlImage(
76
    final BlockBox box,
77
    final Element e,
78
    final UserAgentCallback uac )
79
    throws TranscoderException, URISyntaxException, IOException {
80
    final var source = e.getAttribute( HTML_IMAGE_SRC );
81
    final var mediaType = MediaType.fromFilename( source );
82
83
    URI uri = null;
84
    BufferedImage raster = null;
85
86
    final var w = box.getContentWidth();
87
88
    if( getProtocol( source ).isRemote() ) {
89
      try( final var response = open( source );
90
           final var stream = response.getInputStream() ) {
91
92
        // Rasterize SVG from URL resource.
93
        raster = response.isSvg()
94
          ? rasterize( stream, w )
95
          : ImageIO.read( stream );
96
97
        clue( "Main.status.image.request.fetch", source );
98
      }
99
    }
100
    else if( mediaType.isSvg() ) {
101
      uri = resolve( source, uac, e );
102
    }
103
104
    if( uri != null && w > 0 ) {
105
      raster = rasterize( uri, w );
106
    }
107
108
    // Not an SVG, attempt to read a local rasterized image.
109
    if( raster == null && mediaType.isImage() ) {
110
      uri = resolve( source, uac, e );
111
112
      final var path = Paths.get( uri );
113
114
      try( final var stream = Files.newInputStream( path ) ) {
115
        raster = ImageIO.read( stream );
116
      }
117
    }
118
119
    // Image path resolved; image rendered successfully.
120
    clue();
121
122
    return raster;
123
  }
124
125
  /**
126
   * Attempt to rasterize based on file name.
127
   *
128
   * @param source The source URI from the document.
129
   * @param uac    A callback for retrieving the image resource.
130
   * @param e      The HTML element containing a reference to a file.
131
   * @return The resolved URI to the file.
132
   * @throws URISyntaxException Could not resolve URI.
133
   */
134
  private static URI resolve(
135
    final String source,
136
    final UserAgentCallback uac,
137
    final Element e )
138
    throws URISyntaxException {
139
    final var nSource = source.replaceAll( "\\\\", "/" );
140
141
    try {
142
      final var baseUri = new URI( uac.getBaseURL() );
143
      final var resolved = baseUri.resolve( nSource );
144
      final var path = resolved.normalize();
145
      clue( "Main.status.image.request.resolve", path );
146
147
      return path.isAbsolute() ? path : resolve( nSource, e );
148
    } catch( final Exception ex ) {
149
      clue( "Main.status.image.request.error.resolve", nSource );
150
      throw new URISyntaxException( nSource, ex.getMessage() );
151
    }
152
  }
153
154
  private static URI resolve( final String source, final Element e )
155
    throws URISyntaxException {
156
    final var base = new URI( e.getBaseURI() ).getPath();
157
    return Path.of( base, source ).toUri();
158
  }
159
160
  /**
161
   * Convert the TeX element to a raster graphic.
162
   */
163
  private BufferedImage createTexImage( final Element e )
164
    throws TranscoderException, ParseException {
165
    return rasterize( MathRenderer.toString( e.getTextContent() ) );
166
  }
167
168
  private static ImageReplacedElement createElement( final BufferedImage bi ) {
169
    return bi == null
170
      ? BROKEN_IMAGE
171
      : new ImageReplacedElement( bi, bi.getWidth(), bi.getHeight() );
172
  }
173
}
1174
A src/main/java/com/keenwrite/preview/MathRenderer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preview;
3
4
import com.keenwrite.dom.DocumentParser;
5
import com.whitemagicsoftware.keentype.lib.KeenType;
6
import javafx.beans.property.DoubleProperty;
7
import javafx.beans.property.SimpleDoubleProperty;
8
import org.w3c.dom.Document;
9
10
import static com.keenfinal DoubleProperty sSize = new SimpleDoubleProperty( 2 );public static void bindSize( final DoubleProperty size ) {
11
    sSize.bind( sizesSize.doubleValue() );
12
  }
13
}
114
A src/main/java/com/keenwrite/preview/SmoothImageReplacedElement.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preview;
3
4
import com.keenwrite.preview.images.Lanczos3Filter;
5
import com.keenwrite.preview.images.ResampleOp;
6
import org.xhtmlrenderer.swing.ImageReplacedElement;
7
8
import java.awt.*;
9
import java.awt.image.BufferedImage;
10
11
/**
12
 * Responsible for scaling an image using a Lanczos-3 filter, typically for
13
 * down-sampling.
14
 */
15
public final class SmoothImageReplacedElement extends ImageReplacedElement {
16
  private static final Lanczos3Filter FILTER = new Lanczos3Filter();
17
18
  /**
19
   * Creates a high-quality rescaled version of the given image. The
20
   * aspect ratio is maintained if either width or height is less than 1.
21
   *
22
   * @param source An instance of {@link BufferedImage} to rescale.
23
   * @param width  Rescale the given image to this width (px).
24
   * @param height Rescale the given image to this height (px).
25
   */
26
  public SmoothImageReplacedElement(
27
    final Image source, final int width, final int height ) {
28
    super._image = rescale( source, width, height );
29
  }
30
31
  private BufferedImage rescale(
32
    final Image source, final int w, final int h ) {
33
    final var bi = (BufferedImage) source;
34
    final var dim = rescaleDimensions( bi, w, h );
35
36
    final var resampleOp = new ResampleOp( FILTER, dim.width, dim.height );
37
    return resampleOp.filter( bi, null );
38
  }
39
40
  /**
41
   * Calculates scaled dimensions while maintaining the image aspect ratio.
42
   */
43
  private Dimension rescaleDimensions(
44
    final BufferedImage bi, final int width, final int height ) {
45
    final var oldW = bi.getWidth();
46
    final var oldH = bi.getHeight();
47
48
    int newW = width;
49
    int newH = height;
50
51
    if( newW <= 0 ) {
52
      newW = (int) (oldW * ((double) newH / oldH));
53
    }
54
55
    if( newH <= 0 ) {
56
      newH = (int) (oldH * ((double) newW / oldW));
57
    }
58
59
    if( newW <= 0 && newH <= 0 ) {
60
      newW = oldW;
61
      newH = oldH;
62
    }
63
64
    return new Dimension( newW, newH );
65
  }
66
}
167
A src/main/java/com/keenwrite/preview/SvgRasterizer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.preview;
13
4
import io.sf.carte.echosvg.anim.dom.SAXSVGDocumentFactory;
5
import io.sf.carte.echosvg.bridge.BridgeContext;
6
import io.sf.carte.echosvg.bridge.Docuunused" )
A src/main/java/com/keenwrite/preview/images/AdvancedResizeOp.java
1
/*
2
 * Copyright 2013, Morten Nobel-Joergensen
3
 *
4
 * License: The BSD 3-Clause License
5
 * http://opensource.org/licenses/BSD-3-Clause
6
 */
7
package com.keenwrite.preview.images;
8
9
import java.awt.*;
10
import java.awt.geom.Point2D;
11
import java.awt.geom.Rectangle2D;
12
import java.awt.image.BufferedImage;
13
import java.awt.image.BufferedImageOp;
14
import java.awt.image.ColorModel;
15
16
/**
17
 * @author Morten Nobel-Joergensen
18
 */
19
public abstract class AdvancedResizeOp implements BufferedImageOp {
20
  private final ConstrainedDimension dimensionConstrain;
21
22
  public AdvancedResizeOp( ConstrainedDimension dimensionConstrain ) {
23
    this.dimensionConstrain = dimensionConstrain;
24
  }
25
26
  public final BufferedImage filter( BufferedImage src, BufferedImage dest ) {
27
    Dimension dstDimension = dimensionConstrain.getDimension(
28
      new Dimension( src.getWidth(), src.getHeight() ) );
29
    int dstWidth = dstDimension.width;
30
    int dstHeight = dstDimension.height;
31
32
    return doFilter( src, dest, dstWidth, dstHeight );
33
  }
34
35
  protected abstract BufferedImage doFilter(
36
    BufferedImage src, BufferedImage dest, int dstWidth, int dstHeight );
37
38
  @Override
39
  public final Rectangle2D getBounds2D( BufferedImage src ) {
40
    return new Rectangle( 0, 0, src.getWidth(), src.getHeight() );
41
  }
42
43
  @Override
44
  public final BufferedImage createCompatibleDestImage(
45
    BufferedImage src, ColorModel destCM ) {
46
    if( destCM == null ) {
47
      destCM = src.getColorModel();
48
    }
49
50
    return new BufferedImage(
51
      destCM,
52
      destCM.createCompatibleWritableRaster( src.getWidth(), src.getHeight() ),
53
      destCM.isAlphaPremultiplied(),
54
      null );
55
  }
56
57
  @Override
58
  public final Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
59
    return (Point2D) srcPt.clone();
60
  }
61
62
  @Override
63
  public final RenderingHints getRenderingHints() {
64
    return null;
65
  }
66
}
167
A src/main/java/com/keenwrite/preview/images/ConstrainedDimension.java
1
/*
2
 * Copyright 2013, Morten Nobel-Joergensen
3
 *
4
 * SPDX-License-Identifier: BSD-3-Clause
5
 */
6
package com.keenwrite.preview.images;
7
8
import java.awt.*;
9
10
/**
11
 * This class let you create dimension constrains based on an actual image.
12
 */
13
public class ConstrainedDimension {
14
  private ConstrainedDimension() {
15
  }
16
17
  /**
18
   * Will always return a dimension with positive width and height;
19
   *
20
   * @param dimension of the unscaled image
21
   * @return the dimension of the scaled image
22
   */
23
  public Dimension getDimension( Dimension dimension ) {
24
    return dimension;
25
  }
26
27
  /**
28
   * Used when the destination size is fixed. This may not keep the image
29
   * aspect radio.
30
   *
31
   * @param width  destination dimension width
32
   * @param height destination dimension height
33
   * @return destination dimension (width x height)
34
   */
35
  public static ConstrainedDimension createAbsolutionDimension(
36
    final int width, final int height ) {
37
    assert width > 0 : "Width must be positive integer";
38
    assert height > 0 : "Height must be positive integer";
39
40
    return new ConstrainedDimension() {
41
      public Dimension getDimension( Dimension dimension ) {
42
        return new Dimension( width, height );
43
      }
44
    };
45
  }
46
}
147
A src/main/java/com/keenwrite/preview/images/ImageUtils.java
1
/*
2
 * Copyright 2013, Morten Nobel-Joergensen
3
 *
4
 * License: The BSD 3-Clause License
5
 * http://opensource.org/licenses/BSD-3-Clause
6
 */
7
package com.keenwrite.preview.images;
8
9
import java.awt.*;
10
import java.awt.image.BufferedImage;
11
import java.awt.image.Raster;
12
import java.awt.image.WritableRaster;
13
14
import static java.awt.image.BufferedImage.*;
15
16
/**
17
 * @author Heinz Doerr
18
 * @author Morten Nobel-Joergensen
19
 */
20
public final class ImageUtils {
21
  @SuppressWarnings( "DuplicateBranchesInSwitch" )
22
  static int nrChannels( final BufferedImage img ) {
23
    return switch( img.getType() ) {
24
      case TYPE_3BYTE_BGR -> 3;
25
      case TYPE_4BYTE_ABGR -> 4;
26
      case TYPE_BYTE_GRAY -> 1;
27
      case TYPE_INT_BGR -> 3;
28
      case TYPE_INT_ARGB -> 4;
29
      case TYPE_INT_RGB -> 3;
30
      case TYPE_CUSTOM -> 4;
31
      case TYPE_4BYTE_ABGR_PRE -> 4;
32
      case TYPE_INT_ARGB_PRE -> 4;
33
      case TYPE_USHORT_555_RGB -> 3;
34
      case TYPE_USHORT_565_RGB -> 3;
35
      case TYPE_USHORT_GRAY -> 1;
36
      default -> 0;
37
    };
38
  }
39
40
  /**
41
   * returns one row (height == 1) of byte packed image data in BGR or AGBR form
42
   *
43
   * @param temp must be either null or an array with length of w*h
44
   */
45
  static void getPixelsBGR(
46
    BufferedImage img, int y, int w, byte[] array, int[] temp ) {
47
    final int x = 0;
48
    final int h = 1;
49
50
    assert array.length == temp.length * nrChannels( img );
51
    assert temp.length == w;
52
53
    final Raster raster;
54
    switch( img.getType() ) {
55
      case TYPE_3BYTE_BGR, TYPE_4BYTE_ABGR,
56
        TYPE_4BYTE_ABGR_PRE, TYPE_BYTE_GRAY -> {
57
        raster = img.getRaster();
58
        //int ttype= raster.getTransferType();
59
        raster.getDataElements( x, y, w, h, array );
60
      }
61
      case TYPE_INT_BGR -> {
62
        raster = img.getRaster();
63
        raster.getDataElements( x, y, w, h, temp );
64
        ints2bytes( temp, array, 0, 1, 2 );  // bgr -->  bgr
65
      }
66
      case TYPE_INT_RGB -> {
67
        raster = img.getRaster();
68
        raster.getDataElements( x, y, w, h, temp );
69
        ints2bytes( temp, array, 2, 1, 0 );  // rgb -->  bgr
70
      }
71
      case TYPE_INT_ARGB, TYPE_INT_ARGB_PRE -> {
72
        raster = img.getRaster();
73
        raster.getDataElements( x, y, w, h, temp );
74
        ints2bytes( temp, array, 2, 1, 0, 3 );  // argb -->  abgr
75
      }
76
      case TYPE_CUSTOM -> {
77
        // loader, but else ???
78
        img.getRGB( x, y, w, h, temp, 0, w );
79
        ints2bytes( temp, array, 2, 1, 0, 3 );  // argb -->  abgr
80
      }
81
      default -> {
82
        img.getRGB( x, y, w, h, temp, 0, w );
83
        ints2bytes( temp, array, 2, 1, 0 );  // rgb -->  bgr
84
      }
85
    }
86
  }
87
88
  /**
89
   * converts and copies byte packed  BGR or ABGR into the img buffer,
90
   * the img type may vary (e.g. RGB or BGR, int or byte packed)
91
   * but the number of components (w/o alpha, w alpha, gray) must match
92
   * <p>
93
   * does not unmange the image for all (A)RGN and (A)BGR and gray imaged
94
   */
95
  public static void setBGRPixels( byte[] bgrPixels, BufferedImage img, int x,
96
                                   int y, int w, int h ) {
97
    int imageType = img.getType();
98
    WritableRaster raster = img.getRaster();
99
100
    if( imageType == TYPE_3BYTE_BGR ||
101
      imageType == TYPE_4BYTE_ABGR ||
102
      imageType == TYPE_4BYTE_ABGR_PRE ||
103
      imageType == TYPE_BYTE_GRAY ) {
104
      raster.setDataElements( x, y, w, h, bgrPixels );
105
    }
106
    else {
107
      int[] pixels;
108
      if( imageType == TYPE_INT_BGR ) {
109
        pixels = bytes2int( bgrPixels, 2, 1, 0 );  // bgr -->  bgr
110
      }
111
      else if( imageType == TYPE_INT_ARGB ||
112
        imageType == TYPE_INT_ARGB_PRE ) {
113
        pixels = bytes2int( bgrPixels, 3, 0, 1, 2 );  // abgr -->  argb
114
      }
115
      else {
116
        pixels = bytes2int( bgrPixels, 0, 1, 2 );  // bgr -->  rgb
117
      }
118
      if( w == 0 || h == 0 ) {
119
        return;
120
      }
121
      else if( pixels.length < w * h ) {
122
        throw new IllegalArgumentException( "pixels array must have a length" + " >= w*h" );
123
      }
124
      if( imageType == TYPE_INT_ARGB ||
125
        imageType == TYPE_INT_RGB ||
126
        imageType == TYPE_INT_ARGB_PRE ||
127
        imageType == TYPE_INT_BGR ) {
128
        raster.setDataElements( x, y, w, h, pixels );
129
      }
130
      else {
131
        // Unmanages the image
132
        img.setRGB( x, y, w, h, pixels, 0, w );
133
      }
134
    }
135
  }
136
137
  public static void ints2bytes( int[] in, byte[] out, int index1, int index2,
138
                                 int index3 ) {
139
    for( int i = 0; i < in.length; i++ ) {
140
      int index = i * 3;
141
      int value = in[ i ];
142
      out[ index + index1 ] = (byte) value;
143
      value = value >> 8;
144
      out[ index + index2 ] = (byte) value;
145
      value = value >> 8;
146
      out[ index + index3 ] = (byte) value;
147
    }
148
  }
149
150
  public static void ints2bytes( int[] in, byte[] out, int index1, int index2,
151
                                 int index3, int index4 ) {
152
    for( int i = 0; i < in.length; i++ ) {
153
      int index = i * 4;
154
      int value = in[ i ];
155
      out[ index + index1 ] = (byte) value;
156
      value = value >> 8;
157
      out[ index + index2 ] = (byte) value;
158
      value = value >> 8;
159
      out[ index + index3 ] = (byte) value;
160
      value = value >> 8;
161
      out[ index + index4 ] = (byte) value;
162
    }
163
  }
164
165
  public static int[] bytes2int( byte[] in, int index1, int index2,
166
                                 int index3 ) {
167
    int[] out = new int[ in.length / 3 ];
168
    for( int i = 0; i < out.length; i++ ) {
169
      int index = i * 3;
170
      int b1 = (in[ index + index1 ] & 0xff) << 16;
171
      int b2 = (in[ index + index2 ] & 0xff) << 8;
172
      int b3 = in[ index + index3 ] & 0xff;
173
      out[ i ] = b1 | b2 | b3;
174
    }
175
    return out;
176
  }
177
178
  public static int[] bytes2int( byte[] in, int index1, int index2, int index3,
179
                                 int index4 ) {
180
    int[] out = new int[ in.length / 4 ];
181
    for( int i = 0; i < out.length; i++ ) {
182
      int index = i * 4;
183
      int b1 = (in[ index + index1 ] & 0xff) << 24;
184
      int b2 = (in[ index + index2 ] & 0xff) << 16;
185
      int b3 = (in[ index + index3 ] & 0xff) << 8;
186
      int b4 = in[ index + index4 ] & 0xff;
187
      out[ i ] = b1 | b2 | b3 | b4;
188
    }
189
    return out;
190
  }
191
192
  public static BufferedImage convert( BufferedImage src, int bufImgType ) {
193
    BufferedImage img = new BufferedImage( src.getWidth(),
194
                                           src.getHeight(),
195
                                           bufImgType );
196
    Graphics2D g2d = img.createGraphics();
197
    g2d.drawImage( src, 0, 0, null );
198
    g2d.dispose();
199
    return img;
200
  }
201
}
1202
A src/main/java/com/keenwrite/preview/images/Lanczos3.java
1
package com.keenwrite.preview.images;
2
3
import java.awt.image.BufferedImage;
4
5
/**
6
 * Unused. Needs to extract image data from {@link BufferedImage} and create
7
 * down-sampled version.
8
 */
9
public class Lanczos3 {
10
  static double sinc( double x ) {
11
    x *= Math.PI;
12
13
    if( x < 0.01f && x > -0.01f ) {
14
      return 1.0f + x * x * (-1.0f / 6.0f + x * x * 1.0f / 120.0f);
15
    }
16
17
    return Math.sin( x ) / x;
18
  }
19
20
  static float clip( double t ) {
21
    final float eps = .0000125f;
22
23
    if( Math.abs( t ) < eps ) { return 0.0f; }
24
25
    return (float) t;
26
  }
27
28
  static float lancos( float t ) {
29
    if( t < 0.0f ) { t = -t; }
30
31
    if( t < 3.0f ) { return clip( sinc( t ) * sinc( t / 3.0f ) ); }
32
    else { return 0.0f; }
33
  }
34
35
  static float lancos3_resample_x(
36
    int[][] arr, int src_w, int src_h, int y, int x, float xscale ) {
37
    float s = 0;
38
    float coef_sum = 0.0f;
39
    float coef;
40
    float pix;
41
    int i;
42
43
    int l, r;
44
    float c;
45
    float hw;
46
47
    // For the reduction of the situation hw is equivalent to expanding the
48
    // number of pixels in the field, if you do not do this, the final
49
    // reduction of the image effect is not much different from the recent
50
    // field interpolation method, the effect is equivalent to the first
51
    // low-pass filtering, and then interpolate
52
    if( xscale > 1.0f ) { hw = 3.0f; }
53
    else { hw = 3.0f / xscale; }
54
55
    c = (float) x / xscale;
56
    l = (int) Math.floor( c - hw );
57
    r = (int) Math.ceil( c + hw );
58
59
    if( y < 0 ) { y = 0; }
60
    if( y >= src_h ) { y = src_h - 1; }
61
    if( xscale > 1.0f ) { xscale = 1.0f; }
62
    for( i = l; i <= r; i++ ) {
63
      x = Math.max( i, 0 );
64
      if( i >= src_w ) { x = src_w - 1; }
65
      pix = arr[ y ][ x ];
66
      coef = lancos( (c - i) * xscale );
67
      s += pix * coef;
68
      coef_sum += coef;
69
    }
70
    s /= coef_sum;
71
    return s;
72
  }
73
74
  static class uint8_2d {
75
    int[][] arr;
76
    int rows;
77
    int cols;
78
79
    public uint8_2d( final int h1, final int w1 ) {
80
      arr = new int[ h1 ][ w1 ];
81
      rows = h1;
82
      cols = w1;
83
    }
84
  }
85
86
  void img_resize_using_lancos3( uint8_2d src, uint8_2d dst ) {
87
    if( src == null || dst == null ) { return; }
88
89
    int src_rows, src_cols;
90
    int dst_rows, dst_cols;
91
    int i, j;
92
    int[][] src_arr;
93
    int[][] dst_arr;
94
    float xratio;
95
    float yratio;
96
    int val;
97
    int k;
98
    float hw;
99
100
    src_arr = src.arr;
101
    dst_arr = dst.arr;
102
    src_rows = src.rows;
103
    src_cols = src.cols;
104
    dst_rows = dst.rows;
105
    dst_cols = dst.cols;
106
107
    xratio = (float) dst_cols / (float) src_cols;
108
    yratio = (float) dst_rows / (float) src_rows;
109
110
    float scale;
111
112
    if( yratio > 1.0f ) {
113
      hw = 3.0f;
114
      scale = 1.0f;
115
    }
116
    else {
117
      hw = 3.0f / yratio;
118
      scale = yratio;
119
    }
120
121
    for( i = 0; i < dst_rows; i++ ) {
122
      for( j = 0; j < dst_cols; j++ ) {
123
        int t, b;
124
        float c;
125
126
        float s = 0;
127
        float coef_sum = 0.0f;
128
        float coef;
129
        float pix;
130
131
        c = (float) i / yratio;
132
        t = (int) Math.floor( c - hw );
133
        b = (int) Math.ceil( c + hw );
134
        // Interpolate in the x direction first, then interpolate in the y
135
        // direction.
136
        for( k = t; k <= b; k++ ) {
137
          pix = lancos3_resample_x( src_arr, src_cols, src_rows, k, j, xratio );
138
          coef = lancos( (c - k) * scale );
139
          coef_sum += coef;
140
          pix *= coef;
141
          s += pix;
142
        }
143
        val = (int) (s / coef_sum);
144
        if( val < 0 ) { val = 0; }
145
        if( val > 255 ) { val = 255; }
146
        dst_arr[ i ][ j ] = val;
147
      }
148
    }
149
  }
150
151
  BufferedImage test_lancos3_resize( BufferedImage img, float factor ) {
152
    assert img != null;
153
154
    uint8_2d r = null;
155
    uint8_2d g = null;
156
    uint8_2d b = null;
157
158
    BufferedImage out = null;
159
    // TODO: Split buffered image into RGB components.
160
    //split_img_data( img, r, g, b );
161
162
    int w, h;
163
    int w1, h1;
164
    w = img.getWidth();
165
    h = img.getHeight();
166
167
    // TODO: Maintain aspect ratio.
168
    w1 = (int) (factor * w);
169
    h1 = (int) (factor * h);
170
171
    uint8_2d r1 = new uint8_2d( h1, w1 );
172
    uint8_2d g1 = new uint8_2d( h1, w1 );
173
    uint8_2d b1 = new uint8_2d( h1, w1 );
174
175
    img_resize_using_lancos3( r, r1 );
176
    img_resize_using_lancos3( g, g1 );
177
    img_resize_using_lancos3( b, b1 );
178
179
    // TODO: Combine rescaled image into RGB components.
180
    //merge_img_data( r1, g1, b1, out);
181
182
    return out;
183
  }
184
}
1185
A src/main/java/com/keenwrite/preview/images/Lanczos3Filter.java
1
/*
2
 * Copyright 2013, Morten Nobel-Joergensen
3
 *
4
 * License: The BSD 3-Clause License
5
 * http://opensource.org/licenses/BSD-3-Clause
6
 */
7
package com.keenwrite.preview.images;
18
9
public final class Lanczos3Filter implements ResampleFilter {
10
  private static final
A src/main/java/com/keenwrite/preview/images/ResampleFilter.java
1
/*
2
 * Copyright 2013, Morten Nobel-Joergensen
3
 *
4
 * License: The BSD 3-Clause License
5
 * http://opensource.org/licenses/BSD-3-Clause
6
 */
7
package com.keenwrite.preview.images;
8
9
public interface ResampleFilter {
10
  float getSamplingRadius();
11
12
  float apply(float v);
13
}
114
A src/main/java/com/keenwrite/preview/images/ResampleOp.java
1
/*
2
 * Copyright 2013, Morten Nobel-Joergensen
3
 *
4
 * License: The BSD 3-Clause License
5
 * http://opensource.org/licenses/BSD-3-Clause
6
 */
7
package com.keenwrite.preview.images;
8
9
import java.awt.image.BufferedImage;
10
import java.util.concurrent.atomic.AtomicI*
11
    final int dstWidth,
12
    final int dstHeight ) {
13
    this( filter,stWidth, d free memory
14
    // 
115
16
    if( dst != null &&
17
      dstWidth == dst.getWidth() &&
18
      dstHeight == d ][ xLoc ][ xLoc ][ xLoc ][ xLoc
A src/main/java/com/keenwrite/processors/ExecutorProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors;
3
4
import java.util.Optional;
5
import java.util.concurrent.atomic.AtomicReference;
6
7
/**
8
 * Responsible for transforming data through a variety of chained handlers.
9
 *
10
 * @param <T> The data type to process.
11
 */
12
public class ExecutorProcessor<T> implements Processor<T> {
13
14
  /**
15
   * The next link in the processing chain.
16
   */
17
  private final Processor<T> mNext;
18
19
  protected ExecutorProcessor() {
20
    this( null );
21
  }
22
23
  /**
24
   * Constructs a new processor having a given successor.
25
   *
26
   * @param successor The next processor in the chain.
27
   */
28
  public ExecutorProcessor( final Processor<T> successor ) {
29
    mNext = successor;
30
  }
31
32
  /**
33
   * Calls every link in the chain to process the given data.
34
   *
35
   * @param data The data to transform.
36
   * @return The data after processing by every link in the chain.
37
   */
38
  @Override
39
  public T apply( final T data ) {
40
    // Start processing using the first processor after the executor.
41
    Optional<Processor<T>> handler = next();
42
    final var result = new MutableReference( data );
43
44
    while( handler.isPresent() ) {
45
      handler = handler.flatMap( p -> {
46
        result.set( p.apply( result.get() ) );
47
        return p.next();
48
      } );
49
    }
50
51
    return result.get();
52
  }
53
54
  @Override
55
  public Optional<Processor<T>> next() {
56
    return Optional.ofNullable( mNext );
57
  }
58
59
  /**
60
   * A minor micro-optimization, since the processors cannot be run in parallel,
61
   * avoid using an {@link AtomicReference} during processor execution. This
62
   * is about twice as fast for the first four processor links in the chain.
63
   */
64
  private final class MutableReference {
65
    private T mObject;
66
67
    MutableReference( final T object ) {
68
      set( object );
69
    }
70
71
    void set( final T object ) {
72
      mObject = object;
73
    }
74
75
    T get() {
76
      return mObject;
77
    }
78
  }
79
}
180
A src/main/java/com/keenwrite/processors/Processor.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
A src/main/java/com/keenwrite/processors/ProcessorContext.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors;
6
7
import com.keenwrite.ExportFormat;
8
import com.keenwrite.collections.InterpolatingMap;
9
import com.keenwrite.constants.Constants;
10
import com.keenwrite.editors.common.Caret;
11
import com.keenwrite.io.FileType;
12
import com.keenwrite.io.MediaType;
13
import com.keenwrite.io.MediaTypeExtension;
14
import com.keenwrite.processors.variable.VariableProcessor;
15
import com.keenwrite.sigils.PropertyKeyOperator;
16
import com.keenwrite.sigils.SigilKeyOperator;
17
import com.keenwrite.util.GenericBuilder;
18
import org.renjin.repackaged.guava.base.Splitter;
19
20
import java.io.File;
21
import java.nio.file.Path;
22
import java.util.HashMap;
23
import java.util.Locale;
24
import java.util.Map;
25
import java.util.concurrent.Callable;
26
import java.util.function.Supplier;
27
28
import static com.keenwrite.Bootstrap.USER_CACHE_DIR;
29
import static com.keenwrite.Bootstrap.USER_DATA_DIR;
30
import static com.keenwrite.constants.Constants.*;
31
import static com.keenwrite.io.FileType.UNKNOWN;
32
import static com.keenwrite.io.MediaType.TEXT_PROPERTIES;
33
import static com.keenwrite.io.SysFile.toFile;
34
import static com.keenwrite.predicates.PredicateFactory.createFileTypePredicate;
35
import static com.keenwrite.processors.html.IdentityProcessor.IDENTITY;
36
import static com.keenwrite.processors.text.TextReplacementFactory.replace;
37
import static com.keenwrite.util.Strings.sanitize;
38
39
/**
40
 * Provides a context for configuring a chain of {@link Processor} instances.
41
 */
42
public final class ProcessorContext {
43
44
  private final Mutator mMutator;
45
46
  /**
47
   * Determines the file type from the path extension. This should only be
48
   * called when it is known that the file type won't be a definition file
49
   * (e.g., YAML or other definition source), but rather an editable file
50
   * (e.g., Markdown, R Markdown, etc.).
51
   *
52
   * @param path The path with a file name extension.
53
   * @return The FileType for the given path.
54
   */
55
  private static FileType lookup( final Path path ) {
56
    assert path != null;
57
58
    final var prefix = GLOB_PREFIX_FILE;
59
    final var keys = sSettings.getKeys( prefix );
60
61
    var found = false;
62
    var fileType = UNKNOWN;
63
64
    while( keys.hasNext() && !found ) {
65
      final var key = keys.next();
66
      final var patterns = sSettings.getStringSettingList( key );
67
      final var predicate = createFileTypePredicate( patterns );
68
69
      if( predicate.test( toFile( path ) ) ) {
70
        // Remove the EXTENSIONS_PREFIX to get the file name extension mapped
71
        // to a standard name (as defined in the settings.properties file).
72
        final String suffix = key.replace( prefix + '.', "" );
73
        fileType = FileType.from( suffix );
74
        found = true;
75
      }
76
    }
77
78
    return fileType;
79
  }
80
81
  public boolean isExportFormat( final ExportFormat exportFormat ) {
82
    return mMutator.mExportFormat == exportFormat;
83
  }
84
85
  /**
86
   * Responsible for populating the instance variables required by the
87
   * context.
88
   */
89
  public static class Mutator {
90
    private Path mSourcePath;
91
    private Path mTargetPath;
92
    private ExportFormat mExportFormat;
93
    private Supplier<Boolean> mConcatenate = () -> true;
94
    private Supplier<String> mChapters = () -> "";
95
96
    private Supplier<Path> mThemeDir = USER_DIRECTORY::toPath;
97
    private Supplier<Locale> mLocale = () -> Locale.ENGLISH;
98
99
    private Supplier<Map<String, String>> mDefinitions = HashMap::new;
100
    private Supplier<Map<String, String>> mMetadata = HashMap::new;
101
    private Supplier<Caret> mCaret = () -> Caret.builder().build();
102
103
    private Supplier<Path> mImageDir = USER_DIRECTORY::toPath;
104
    private Supplier<String> mImageServer = () -> DIAGRAM_SERVER_NAME;
105
    private Supplier<String> mImageOrder = () -> PERSIST_IMAGES_DEFAULT;
106
    private Supplier<Path> mCacheDir = USER_CACHE_DIR::toPath;
107
    private Supplier<Path> mFontDir = () -> getFontDirectory().toPath();
108
109
    private Supplier<String> mModesEnabled = () -> "";
110
111
    private Supplier<String> mSigilBegan = () -> DEF_DELIM_BEGAN_DEFAULT;
112
    private Supplier<String> mSigilEnded = () -> DEF_DELIM_ENDED_DEFAULT;
113
114
    private Supplier<Path> mRWorkingDir = USER_DIRECTORY::toPath;
115
    private Supplier<String> mRScript = () -> "";
116
117
    private Supplier<Boolean> mCurlQuotes = () -> true;
118
    private Supplier<Boolean> mAutoRemove = () -> true;
119
120
    public void setSourcePath( final Path sourcePath ) {
121
      assert sourcePath != null;
122
      mSourcePath = sourcePath;
123
    }
124
125
    public void setTargetPath( final Path outputPath ) {
126
      assert outputPath != null;
127
      mTargetPath = outputPath;
128
    }
129
130
    public void setThemeDir( final Supplier<Path> themeDir ) {
131
      assert themeDir != null;
132
      mThemeDir = themeDir;
133
    }
134
135
    public void setCacheDir( final Supplier<File> cacheDir ) {
136
      assert cacheDir != null;
137
138
      mCacheDir = () -> {
139
        final var dir = cacheDir.get();
140
141
        return (dir == null ? toFile( USER_DATA_DIR ) : dir).toPath();
142
      };
143
    }
144
145
    public void setImageDir( final Supplier<File> imageDir ) {
146
      assert imageDir != null;
147
148
      mImageDir = () -> {
149
        final var dir = imageDir.get();
150
151
        return (dir == null ? USER_DIRECTORY : dir).toPath();
152
      };
153
    }
154
155
    public void setImageOrder( final Supplier<String> imageOrder ) {
156
      assert imageOrder != null;
157
      mImageOrder = imageOrder;
158
    }
159
160
    public void setImageServer( final Supplier<String> imageServer ) {
161
      assert imageServer != null;
162
      mImageServer = imageServer;
163
    }
164
165
    public void setFontDir( final Supplier<File> fontDir ) {
166
      assert fontDir != null;
167
168
      mFontDir = () -> {
169
        final var dir = fontDir.get();
170
171
        return (dir == null ? USER_DIRECTORY : dir).toPath();
172
      };
173
    }
174
175
    public void setModesEnabled( final Supplier<String> modesEnabled ) {
176
      assert modesEnabled != null;
177
      mModesEnabled = modesEnabled;
178
    }
179
180
    public void setExportFormat( final ExportFormat exportFormat ) {
181
      assert exportFormat != null;
182
      mExportFormat = exportFormat;
183
    }
184
185
    public void setConcatenate( final Supplier<Boolean> concatenate ) {
186
      mConcatenate = concatenate;
187
    }
188
189
    public void setChapters( final Supplier<String> chapters ) {
190
      mChapters = chapters;
191
    }
192
193
    public void setLocale( final Supplier<Locale> locale ) {
194
      assert locale != null;
195
      mLocale = locale;
196
    }
197
198
    /**
199
     * Sets the list of fully interpolated key-value pairs to use when
200
     * substituting variable names back into the document as variable values.
201
     * This uses a {@link Callable} reference so that GUI and command-line
202
     * usage can insert their respective behaviours. That is, this method
203
     * prevents coupling the GUI to the CLI.
204
     *
205
     * @param supplier Defines how to retrieve the definitions.
206
     */
207
    public void setDefinitions( final Supplier<Map<String, String>> supplier ) {
208
      assert supplier != null;
209
      mDefinitions = supplier;
210
    }
211
212
    /**
213
     * Sets metadata to use in the document header. These are made available
214
     * to the typesetting engine as {@code \documentvariable} values.
215
     *
216
     * @param metadata The key/value pairs to publish as document metadata.
217
     */
218
    public void setMetadata( final Supplier<Map<String, String>> metadata ) {
219
      assert metadata != null;
220
      mMetadata = metadata.get() == null ? HashMap::new : metadata;
221
    }
222
223
    /**
224
     * Sets document variables to use when building the document. These
225
     * variables will override existing key/value pairs, or be added as
226
     * new key/value pairs if not already defined. This allows users to
227
     * inject variables into the document from the command-line, allowing
228
     * for dynamic assignment of in-text values when building documents.
229
     *
230
     * @param overrides The key/value pairs to add (or override) as variables.
231
     */
232
    public void setOverrides( final Supplier<Map<String, String>> overrides ) {
233
      assert overrides != null;
234
      assert mDefinitions != null;
235
      assert mDefinitions.get() != null;
236
237
      final var map = overrides.get();
238
239
      if( map != null ) {
240
        mDefinitions.get().putAll( map );
241
      }
242
    }
243
244
    /**
245
     * Sets the source for deriving the {@link Caret}. Typically, this is
246
     * the text editor that has focus.
247
     *
248
     * @param caret The source for the currently active caret.
249
     */
250
    public void setCaret( final Supplier<Caret> caret ) {
251
      assert caret != null;
252
      mCaret = caret;
253
    }
254
255
    public void setSigilBegan( final Supplier<String> sigilBegan ) {
256
      assert sigilBegan != null;
257
      mSigilBegan = sigilBegan;
258
    }
259
260
    public void setSigilEnded( final Supplier<String> sigilEnded ) {
261
      assert sigilEnded != null;
262
      mSigilEnded = sigilEnded;
263
    }
264
265
    public void setRWorkingDir( final Supplier<Path> rWorkingDir ) {
266
      assert rWorkingDir != null;
267
      mRWorkingDir = rWorkingDir;
268
    }
269
270
    public void setRScript( final Supplier<String> rScript ) {
271
      assert rScript != null;
272
      mRScript = rScript;
273
    }
274
275
    public void setCurlQuotes( final Supplier<Boolean> curlQuotes ) {
276
      assert curlQuotes != null;
277
      mCurlQuotes = curlQuotes;
278
    }
279
280
    public void setAutoRemove( final Supplier<Boolean> autoRemove ) {
281
      assert autoRemove != null;
282
      mAutoRemove = autoRemove;
283
    }
284
285
    private boolean isExportFormat( final ExportFormat format ) {
286
      return mExportFormat == format;
287
    }
288
  }
289
290
  public static GenericBuilder<Mutator, ProcessorContext> builder() {
291
    return GenericBuilder.of( Mutator::new, ProcessorContext::new );
292
  }
293
294
  /**
295
   * Creates a new context for use by the {@link ProcessorFactory} when
296
   * instantiating new {@link Processor} instances. Although all the
297
   * parameters are required, not all {@link Processor} instances will use
298
   * all parameters.
299
   */
300
  private ProcessorContext( final Mutator mutator ) {
301
    assert mutator != null;
302
303
    mMutator = mutator;
304
  }
305
306
  public Path getSourcePath() {
307
    return mMutator.mSourcePath;
308
  }
309
310
  /**
311
   * Answers what type of input document is to be processed.
312
   *
313
   * @return The input document's {@link MediaType}.
314
   */
315
  public MediaType getSourceType() {
316
    return MediaTypeExtension.fromPath( mMutator.mSourcePath );
317
  }
318
319
  /**
320
   * Fully qualified file name to use when exporting (e.g., document.pdf).
321
   *
322
   * @return Full path to a file name.
323
   */
324
  public Path getTargetPath() {
325
    return mMutator.mTargetPath;
326
  }
327
328
  public ExportFormat getExportFormat() {
329
    return mMutator.mExportFormat;
330
  }
331
332
  public Locale getLocale() {
333
    return mMutator.mLocale.get();
334
  }
335
336
  /**
337
   * Returns the variable map of definitions, without interpolation.
338
   *
339
   * @return A map to help dereference variables.
340
   */
341
  public Map<String, String> getDefinitions() {
342
    return mMutator.mDefinitions.get();
343
  }
344
345
  /**
346
   * Returns the variable map of definitions, with interpolation.
347
   *
348
   * @return A map to help dereference variables.
349
   */
350
  public InterpolatingMap getInterpolatedDefinitions() {
351
    return new InterpolatingMap(
352
      createDefinitionKeyOperator(), getDefinitions()
353
    ).interpolate();
354
  }
355
356
  public Map<String, String> getMetadata() {
357
    return mMutator.mMetadata.get();
358
  }
359
360
  /**
361
   * Returns the current caret position in the document being edited and is
362
   * always up-to-date.
363
   *
364
   * @return Caret position in the document.
365
   */
366
  public Supplier<Caret> getCaret() {
367
    return mMutator.mCaret;
368
  }
369
370
  /**
371
   * Returns the directory that contains the file being edited. When
372
   * {@link Constants#DOCUMENT_DEFAULT} is created, the parent path is
373
   * {@code null}. This will get absolute path to the file before trying to
374
   * get te parent path, which should always be a valid path. In the unlikely
375
   * event that the base path cannot be determined by the path alone, the
376
   * default user directory is returned. This is necessary for the creation
377
   * of new files.
378
   *
379
   * @return Path to the directory containing a file being edited, or the
380
   * default user directory if the base path cannot be determined.
381
   */
382
  public Path getBaseDir() {
383
    final var path = getSourcePath().toAbsolutePath().getParent();
384
    return path == null ? DEFAULT_DIRECTORY : path;
385
  }
386
387
  FileType getSourceFileType() {
388
    return lookup( getSourcePath() );
389
  }
390
391
  public Path getThemeDir() {
392
    return mMutator.mThemeDir.get();
393
  }
394
395
  public Path getImageDir() {
396
    return mMutator.mImageDir.get();
397
  }
398
399
  public Path getCacheDir() {
400
    return mMutator.mCacheDir.get();
401
  }
402
403
  public Iterable<String> getImageOrder() {
404
    assert mMutator.mImageOrder != null;
405
406
    final var order = mMutator.mImageOrder.get();
407
    final var token = order.contains( "," ) ? ',' : ' ';
408
409
    return Splitter.on( token ).split( token + order );
410
  }
411
412
  public String getImageServer() {
413
    return mMutator.mImageServer.get();
414
  }
415
416
  public Path getFontDir() {
417
    return mMutator.mFontDir.get();
418
  }
419
420
  public String getModesEnabled() {
421
    // Force the processor to select particular sigils.
422
    final var processor = new VariableProcessor( IDENTITY, this );
423
    final var needles = processor.getDefinitions();
424
    final var haystack = sanitize( mMutator.mModesEnabled.get() );
425
426
    return needles.containsKey( haystack )
427
      ? replace( haystack, needles )
428
      : processor.hasSigils( haystack )
429
      ? ""
430
      : haystack;
431
  }
432
433
  public boolean getAutoRemove() {
434
    return mMutator.mAutoRemove.get();
435
  }
436
437
  public Path getRWorkingDir() {
438
    return mMutator.mRWorkingDir.get();
439
  }
440
441
  public String getRScript() {
442
    return mMutator.mRScript.get();
443
  }
444
445
  public boolean getCurlQuotes() {
446
    return mMutator.mCurlQuotes.get();
447
  }
448
449
  /**
450
   * Answers whether to process a single text file or all text files in
451
   * the same directory as a single text file. See {@link #getSourcePath()}
452
   * for the file to process (or all files in its directory).
453
   *
454
   * @return {@code true} means to process all text files, {@code false}
455
   * means to process a single file.
456
   */
457
  public boolean getConcatenate() {
458
    return mMutator.mConcatenate.get();
459
  }
460
461
  public String getChapters() {
462
    return mMutator.mChapters.get();
463
  }
464
465
  public SigilKeyOperator createKeyOperator() {
466
    return createKeyOperator( getSourcePath() );
467
  }
468
469
  /**
470
   * Returns the sigil operator for the given {@link Path}.
471
   *
472
   * @param path The type of file being edited, from its extension.
473
   */
474
  private SigilKeyOperator createKeyOperator( final Path path ) {
475
    assert path != null;
476
477
    return MediaType.fromFilename( path ) == TEXT_PROPERTIES
478
      ? createPropertyKeyOperator()
479
      : createDefinitionKeyOperator();
480
  }
481
482
  private SigilKeyOperator createPropertyKeyOperator() {
483
    return new PropertyKeyOperator();
484
  }
485
486
  private SigilKeyOperator createDefinitionKeyOperator() {
487
    final var began = mMutator.mSigilBegan.get();
488
    final var ended = mMutator.mSigilEnded.get();
489
490
    return new SigilKeyOperator( began, ended );
491
  }
492
}
1493
A src/main/java/com/keenwrite/processors/ProcessorFactory.java
1
/* Copyright 2020-2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors;
16
7
import com.keenwrite.processors.html.PreformattedProces
A src/main/java/com/keenwrite/processors/html/HtmlPreviewProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.html;
13
4
import com.keenwrite.preview.HtmlPreview;
5
import com.keenwrite.processors.ExecutorProcessor;
6
HtmlhtmlP final HtmlPreview htmlPreview ) {
7
    sHtmlPreview = htmlP
8
    sHtmlPreview.render( html
A src/main/java/com/keenwrite/processors/html/IdentityProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.html;
3
4
import com.keenwrite.processors.ExecutorProcessor;
5
6
/**
7
 * Responsible for transforming a string into itself. This is used at the
8
 * end of a processing chain when no more processing is required.
9
 */
10
public final class IdentityProcessor extends ExecutorProcessor<String> {
11
  public static final IdentityProcessor IDENTITY = new IdentityProcessor();
12
13
  /**
14
   * Constructs a new instance having no successor (the default successor is
15
   * {@code null}).
16
   */
17
  private IdentityProcessor() {
18
  }
19
20
  /**
21
   * Returns the given string without modification.
22
   *
23
   * @param s The string to return.
24
   * @return The value of s.
25
   */
26
  @Override
27
  public String apply( final String s ) {
28
    return s;
29
  }
30
}
131
A src/main/java/com/keenwrite/processors/html/PreformattedProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.html;
6
7
import com.keenwrite.processors.ExecutorProcessor;
8
import com.keenwrite.processors.Processor;
9
10
/**
11
 * TR."<pre>\{t}</pre>";
12
  }
13
}
114
A src/main/java/com/keenwrite/processors/html/XhtmlProcessor.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.html;
16
7
import com.keenwrite.dom.DocumentParser;
8
import com.keenwrite.io.MediaTypeExtension;
9
import com.keenwrCurlCurl
A src/main/java/com/keenwrite/processors/markdown/BaseMarkdownProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown;
16
7
import com.keenwrite.dom.DocumentConverter;
8
import com.keenwrite.processors.ExecutorProcessor;
9
imp
A src/main/java/com/keenwrite/processors/markdown/MarkdownProcessor.java
1
/* Copyright 2022 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown;
16
7
import com.keenwrite.ExportFormat;
8
import com.keenwrite.io.MediaType;
9
import com.keenwrite.processors.PImageBlockImage
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionBlock.java
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;
16
7
import com.vladsch.flexmark.html.HtmlWriter;
8
import com.vladsch.flexmark.parser.Inl
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionBlockParserFactory.java
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
}
134
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionCustomBlockParserFactory.java
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
}
120
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionExtension.java
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;
16
7
import com.keenwrite.processors.markdown.extensions.common.MarkdownRenderarsing and rendering {@link CaptionBlock} instaaptionaptionaptionaption syntax.
8
   */
9
  public static CaptionaptioncustomBlockParserFactory( new CaptionCustomBlockParsepostProcessorFactory( new CaptionPostProcessorFactoryaption
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionNodeRenderer.java
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
}
165
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionNodeRendererFactory.java
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.MarkdownaptionaptionNodeRenderer( options );
8
  }
9
}
110
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionParser.java
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
}
150
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionPostProcessor.java
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.blocks.ClosingDivBlock;
8
import com.keenwrite.processors.markdown.extensions.fences.blocks.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
}
161
A src/main/java/com/keenwrite/processors/markdown/extensions/captions/CaptionPostProcessorFactory.java
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.parser.block.NodePostProcessor;
8
import com.vladsch.flexma
9
class Captio extend {
10
  Captio() {
11
    // The argument isn't used by the Markdown parsing library.
12
    super( false );
13
14
    addNodes( CaptionBlock.class );
15
  }
16
17
  @Override
18
  protectedfinanew CaptionPostProcessor();
19
  }
20
}
121
A src/main/java/com/keenwrite/processors/markdown/extensions/caret/CaretExtension.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved..extensions.caret;
2
3
import com.keenwrite.editors.common.Caret;
4
import com.keenwrite.processors.ProcessorContext;
5
import com.kee.extensions.common.HtmlRendererAdapterjava.util.function.Supplier.extensions.caret.Builderextends HtmlRendererAdapter {
6
7
  private final Supplier<Caret>ProcessomCaret = context.getCaret(Processoontext )Builder builder,
8
 }
19
A src/main/java/com/keenwrite/processors/markdown/extensions/caret/IdAttributeProvider.java
1
package com.keenwrite.processors.markdown.extensions.caret;
12
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
importfinal
A src/main/java/com/keenwrite/processors/markdown/extensions/common/EmptyNode.java
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./**
7
   * Use {@link #EMPTY_NODE}.
8
   */}
9
10
  @NotNull
11
  @Override
12
  publicasedSequence.EMPTY_SEGMENTS;
13
  }
14
}
115
A src/main/java/com/keenwrite/processors/markdown/extensions/common/HtmlRendererAdapter.java
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
}
122
A src/main/java/com/keenwrite/processors/markdown/extensions/common/MarkdownCustomBlockParserFactory.java
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
}
147
A src/main/java/com/keenwrite/processors/markdown/extensions/common/MarkdownNodeRendererFactory.java
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.NodeRenderedata.DataHolderNodeRendererFactory
9
  implements NodeRendererFactory {Renderer apply( @NotNull final DataHolder options ) {
10
    return createNodeRenderer( optionsRenderer createNodeRenderer( DataHolder options );
11
}
112
A src/main/java/com/keenwrite/processors/markdown/extensions/common/MarkdownParserExtension.java
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
}
117
A src/main/java/com/keenwrite/processors/markdown/extensions/common/MarkdownPostProcessorFactory.java
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
}
126
A src/main/java/com/keenwrite/processors/markdown/extensions/common/MarkdownRendererExtension.java
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
}
139
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/FencedDivExtension.java
1
/* Copyright 2020-2024 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.fences;
3
4
import com.keenwrite.processors.markdown.extensions.common.MarkdownParserExtension;
5
import com.keenwrite.processors.markdown.extensions.common.MarkdownRendererExtension;
6
import com.keenwrite.processors.markdown.extensions.fences.factories.CustomDivBlockParserFactory;
7
import com.keenwrite.processors.markdown.extensions.fences.factories.FencedDivNodeRendererFactory;
8
import com.keenwrite.processors.markdown.extensions.fences.factories.FencedDivPreProcessorFactory;
9
import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
10
import com.vladsch.flexmark.parser.Parser.Builder;
11
12
import java.util.regex.Pattern;
13
14
import static java.util.regex.Pattern.UNICODE_CHARACTER_CLASS;
15
import static java.util.regex.Pattern.compile;
16
17
/**
18
 * Responsible for parsing div block syntax into HTML div tags. Fenced div
19
 * blocks start with three or more consecutive colons, followed by a space,
20
 * followed by attributes. The attributes can be either a single word, or
21
 * multiple words nested in braces. For example:
22
 *
23
 * <p>
24
 * ::: poem
25
 * Tyger Tyger, burning bright,
26
 * In the forests of the night;
27
 * What immortal hand or eye,
28
 * Could frame thy fearful symmetry?
29
 * :::
30
 * </p>
31
 * <p>
32
 * As well as:
33
 * </p>
34
 * <p>
35
 * ::: {#verse .p .d k=v author="Dickinson"}
36
 * Because I could not stop for Death --
37
 * He kindly stopped for me --
38
 * The Carriage held but just Ourselves --
39
 * And Immortality.
40
 * :::
41
 * </p>
42
 *
43
 * <p>
44
 * The second example produces the following starting {@code div} element:
45
 * </p>
46
 * {@literal <div id="verse" class="p d" data-k="v" data-author="Dickson">}
47
 *
48
 * <p>
49
 * This will parse fenced divs embedded inside of blockquote environments.
50
 * </p>
51
 */
52
public class FencedDivExtension extends MarkdownRendererExtension
53
  implements MarkdownParserExtension {
54
  /**
55
   * Matches any number of colons at start of line. This will match both the
56
   * opening and closing fences, with any number of colons.
57
   */
58
  public static final Pattern FENCE = compile( "^:::+.*" );
59
60
  /**
61
   * After a fenced div is detected, this will match the opening fence.
62
   */
63
  public static final Pattern FENCE_OPENING = compile(
64
    "^:::+\\s+([\\p{Alnum}\\-_]+|\\{.+})\\s*$",
65
    UNICODE_CHARACTER_CLASS );
66
67
  public static FencedDivExtension create() {
68
    return new FencedDivExtension();
69
  }
70
71
  @Override
72
  public void extend( final Builder builder ) {
73
    builder.customBlockParserFactory( new CustomDivBlockParserFactory() );
74
    builder.paragraphPreProcessorFactory( new FencedDivPreProcessorFactory() );
75
  }
76
77
  @Override
78
  protected NodeRendererFactory createNodeRendererFactory() {
79
    return new FencedDivNodeRendererFactory();
80
  }
81
}
182
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/ImageBlockExtension.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.fences;
13
4
import com.keenwrite.preview.DiagramUrlGenerator;
5
import com.keenwrite.processors.Processor;
6
import com.keenwrite.prImageImageImageImageImage
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/blocks/ClosingDivBlock.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.fences.blocks;
13
4
import com.vladsch.flexmark.html.HtmlWriter;
5
import com.vladsch.flexmark.util. closClosublic Closhtml.closeT
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/blocks/DivBlock.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.fences.blocks;
3
4
import com.vladsch.flexmark.html.HtmlWriter;
5
import com.vladsch.flexmark.util.ast.Block;
6
import com.vladsch.flexmark.util.sequence.BasedSequence;
7
import org.jetbrains.annotations.NotNull;
8
9
public abstract class DivBlock extends Block {
10
  static final CharSequence HTML_DIV = "div";
11
12
  public DivBlock( final BasedSequence chars ) {
13
    super( chars );
14
  }
15
16
  @Override
17
  @NotNull
18
  public BasedSequence[] getSegments() {
19
    return EMPTY_SEGMENTS;
20
  }
21
22
  /**
23
   * Append an opening or closing HTML div element to the given writer.
24
   *
25
   * @param html Builds the HTML document to be written.
26
   */
27
  public abstract void write( HtmlWriter html );
28
}
129
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/blocks/OpeningDivBlock.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.fences.blocks;
3
4
import com.vladsch.flexmark.html.HtmlWriter;
5
import com.vladsch.flexmark.util.html.Attribute;
6
import com.vladsch.flexmark.util.sequence.BasedSequence;
7
8
import java.util.ArrayList;
9
import java.util.List;
10
11
/**
12
 * Responsible for helping to generate an opening {@code div} element.
13
 */
14
public final class OpeningDivBlock extends DivBlock {
15
  private final List<Attribute> mAttributes = new ArrayList<>();
16
17
  public OpeningDivBlock( final BasedSequence chars,
18
                          final List<Attribute> attrs ) {
19
    super( chars );
20
    assert attrs != null;
21
    mAttributes.addAll( attrs );
22
  }
23
24
  @Override
25
  public void write( final HtmlWriter html ) {
26
    mAttributes.forEach( html::attr );
27
    html.withAttr().tag( HTML_DIV );
28
  }
29
}
130
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/factories/CustomDivBlockParserFactory.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown.extensions.fences.factories;
6
7
import com.keenwrite.processors.markdown.extensions.common.MarkdownCustomn instance of {@link}.
8
 */
9
public class Custom
10
  extends MarkdownCustomBlockParserFactory {
11
  @Override
12
  public BlockParserFactory createBlockParserFactory(
13
    final DataHreturn new( options );
14
  }
15
}
116
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/factories/DivBlockParserFactory.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown.extensions.fences.factories;
6
7
import com.keenwrite.processors.markdown.extensions.fences.parsers.ClosingParser;
8
import com.keenwrite.processors.markdown.extensions.fences.parsers.OpeningParser;
9
import com.vladsch.flexmark.parser.block.AbstractBlockParserFactory;
10
import com.vladsch.flexmark.parser.block.BlockStart;
11
import com.vladsch.flexmark.parser.block.MatchedBlockParser;
12
import com.vladsch.flexmark.parser.block.ParserState;
13
import com.vladsch.flexmark.util.data.DataHolder;
14
15
import static com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension.FENCE;
16
import static com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension.FENCE_OPENING;
17
18
/**
19
 * Responsible for creating a fenced div parser that is appropriate for the
20
 * type of fenced div encountered: opening or closing.
21
 */
22
public class DivBlockParserFactory extends AbstractBlockParserFactory {
23
  public DivBlockParserFactory( final DataHolder options ) {
24
    super( options );
25
  }
26
27
  /**
28
   * Try to match an opening or closing fenced div.
29
   *
30
   * @param state       Block parser state.
31
   * @param blockParser Last matched open block parser.
32
   * @return Wrapper for the opening or closing parser, upon finding :::.
33
   */
34
  @Override
35
  public BlockStart tryStart(
36
    final ParserState state, final MatchedBlockParser blockParser ) {
37
    return
38
      state.getIndent() == 0 && FENCE.matcher( state.getLine() ).matches()
39
        ? parseFence( state )
40
        : BlockStart.none();
41
  }
42
43
  /**
44
   * After finding a fenced div, this will further disambiguate an opening
45
   * from a closing fence.
46
   *
47
   * @param state Block parser state, contains line to parse.
48
   * @return Wrapper for the opening or closing parser, upon finding :::.
49
   */
50
  private BlockStart parseFence( final ParserState state ) {
51
    final var line = state.getLine();
52
    final var fence = FENCE_OPENING.matcher( line.trim() );
53
54
    return BlockStart.of(
55
      fence.matches()
56
        ? new OpeningParser( fence.group( 1 ) )
57
        : new ClosingParser( line )
58
    ).atIndex( state.getIndex() );
59
  }
60
}
161
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/factories/FencedDivNodeRendererFactory.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.fences.factories;
3
4
import com.keenwrite.processors.markdown.extensions.common.MarkdownNodeRendererFactory;
5
import com.keenwrite.processors.markdown.extensions.fences.renderers.FencedDivRendererhtml.renderer.NodeRendererpublic class FencedDivNodeRendererFactory extends MarkdownNodeRenderrotected NodeRenderer createNodeRenderer(FencedDivRenderer();
6
  }
7
}
18
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/factories/FencedDivPreProcessorFactory.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown.extensions.fences.factories;
6
7
import com.keenwrite.processors.markdown.extensions.fences.processors.FencedDivParagraphPreProcessor;
8
import com.vladsch.flexmark.parser.block.ParagraphPreProcessor;
9
import com.vladsch.flexmark.parser.block.ParagraphPreProcessorFactory;
10
import com.vladsch.flexmark.parser.block.ParserState;
11
import com.vladsch.flexmark.parser.core.ReferencePreProcessorFactory;
12
import org.jetbrains.annotations.Nullable;
13
14
import java.util.Set;
15
16
public class FencedDivPreProcessorFactory
17
  implements ParagraphPreProcessorFactory {
18
19
  @Override
20
  public ParagraphPreProcessor apply( final ParserState state ) {
21
    return new FencedDivParagraphPreProcessor( state.getProperties() );
22
  }
23
24
  @Override
25
  public @Nullable Set<Class<?>> getBeforeDependents() {
26
    return Set.of();
27
  }
28
29
  @Override
30
  public @Nullable Set<Class<?>> getAfterDependents() {
31
    return Set.of( ReferencePreProcessorFactory.class );
32
  }
33
34
  @Override
35
  public boolean affectsGlobalScope() {
36
    return false;
37
  }
38
39
}
140
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/parsers/ClosingParser.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown.extensions.fences.parsers;
6
7
import com.keenwrite.processors.markdown.extensions.fences.blocks.ClosingDivBClosClosprivate final ClosingDivBlock mBlock;
18
9
  public ClosingParser( final BasedSequence line ) {
10
    mBlock = new ClosingDivBlock( line
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/parsers/DivBlockParser.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown.extensions.fences.parsers;
6
7
import com.vladsch.flexmark.parser.block.AbstractBlockParser;
8
import com.vladsch.flexmark.parser.block.BlockContinue;
9
import com.vladsch.flexmark.parser.block.ParserState;
10
11
/**
12
 * Abstracts common {@link OpeningParser} and
13
 * {@link ClosingParser} methods.
14
 */
15
public abstract class DivBlockParser extends AbstractBlockParser {
16
  @Override
17
  public BlockContinue tryContinue( final ParserState state ) {
18
    return BlockContinue.none();
19
  }
20
21
  @Override
22
  public void closeBlock( final ParserState state ) {}
23
}
124
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/parsers/OpeningParser.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown.extensions.fences.parsers;
16
7
import com.keenwrite.processors.markdown.extensions.fences.blocks.OpeningDivBSTR."data-\{}";
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/processors/FencedDivParagraphPreProcessor.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown.extensions.fences.processors;
6
7
import com.keenwrite.processors.markdown.extensions.fences.parsers.ClosingParser;
8
import com.keenwrite.processors.markdown.extensions.fences.parsers.OpeningParser;
9
import com.vladsch.flexmark.ast.Paragraph;
10
import com.vladsch.flexmark.parser.block.ParagraphPreProcessor;
11
import com.vladsch.flexmark.parser.block.ParserState;
12
import com.vladsch.flexmark.util.data.MutableDataHolder;
13
import com.vladsch.flexmark.util.sequence.BasedSequence;
14
15
import java.util.ArrayList;
16
17
import static com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension.FENCE;
18
import static com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension.FENCE_OPENING;
19
20
public class FencedDivParagraphPreProcessor
21
  implements ParagraphPreProcessor {
22
  public FencedDivParagraphPreProcessor( final MutableDataHolder unused ) {}
23
24
  @Override
25
  public int preProcessBlock( final Paragraph block,
26
                              final ParserState state ) {
27
    final var lines = block.getContentLines();
28
29
    // Stores lines matching opening or closing fenced div sigil.
30
    final var sigilLines = new ArrayList<BasedSequence>();
31
32
    for( final var line : lines ) {
33
      // Seeks a :::+ sigil.
34
      final var fence = FENCE.matcher( line );
35
36
      if( fence.find() ) {
37
        // Attributes after the fence are required to detect an open fence.
38
        final var attrs = FENCE_OPENING.matcher( line.trim() );
39
        final var match = attrs.matches();
40
41
        final var parser = match
42
          ? new OpeningParser( attrs.group( 1 ) )
43
          : new ClosingParser( line );
44
        final var divBlock = parser.getBlock();
45
46
        if( match ) {
47
          block.insertBefore( divBlock );
48
        }
49
        else {
50
          block.insertAfter( divBlock );
51
        }
52
53
        state.blockAdded( divBlock );
54
55
        // Schedule the line for removal (because it has been handled).
56
        sigilLines.add( line );
57
      }
58
    }
59
60
    sigilLines.forEach( lines::remove );
61
62
    return 0;
63
  }
64
}
165
A src/main/java/com/keenwrite/processors/markdown/extensions/fences/renderers/FencedDivRenderer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.fences.renderers;
3
4
import com.keenwrite.processors.markdown.extensions.fences.blocks.ClosingDivBlock;
5
import com.keenwrite.processors.markdown.extensions.fences.blocks.DivBlock;
6
import com.keenwrite.processors.markdown.extensions.fences.blocks.OpeningDivBlock;
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
import org.jetbrains.annotations.Nullable;
12
13
import java.util.Set;
14
15
/**
16
 * Responsible for rendering opening and closing fenced div blocks as HTML
17
 * {@code div} elements.
18
 */
19
public class FencedDivRenderer implements NodeRenderer {
20
  @Nullable
21
  @Override
22
  public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
23
    return Set.of(
24
      new NodeRenderingHandler<>( OpeningDivBlock.class, this::render ),
25
      new NodeRenderingHandler<>( ClosingDivBlock.class, this::render )
26
    );
27
  }
28
29
  /**
30
   * Renders the fenced div block as an HTML {@code <div></div>} element.
31
   */
32
  void render(
33
    final DivBlock node,
34
    final NodeRendererContext context,
35
    final HtmlWriter html ) {
36
    node.write( html );
37
  }
38
}
139
A src/main/java/com/keenwrite/processors/markdown/extensions/images/ImageLinkExtension.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.images;
13
4
import com.keenwrite.ExportFormat;
5
import com.keenwrite.processors.ProcessorContext;
6
import com.keenwrite.processorscommonrivate ImageLinkResolver() {
A src/main/java/com/keenwrite/processors/markdown/extensions/outline/DocumentOutlineExtension.java
1
package com.keenwrite.processors.markdown.extensions.outline;
12
3
import;
4
import com.keenwrite.processors.Processor;
5
import com.keenwrite.processors.markdown.extensions.common.MarkdownParserExtension;
6
importast.Heading;
7
import com.vladsch.flexmark.parser.Parser.Builder;
8
import com.vladsch.flexmark.parser.MarkdownREGEXREGEXParseHeadingEvent.fire
A src/main/java/com/keenwrite/processors/markdown/extensions/r/RInlineExtension.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.r;
3
4
import com.keenwrite.processors.Processor;
5
import com.keenwrite.processors.ProcessorContext;
6
import com.keenwrite.processors.markdown.BaseMarkdownProcessor;
7
import com.keenwrite.processors.markdown.extensions.common.MarkdownParserExtension;
8
import com.keenwrite.processors.r.RInlineEvaluator;
9
import com.vladsch.flexmark.ast.Paragraph;
10
import com.vladsch.flexmark.parser.InlineParserExtensionFactory;
11
import com.vladsch.flexmark.parser.delimiter.DelimiterProcessor;
12
import com.vladsch.flexmark.parser.internal.InlineParserImpl;
13
import com.vladsch.flexmark.parser.internal.LinkRefProcessorData;
14
import com.vladsch.flexmark.util.data.DataHolder;
15
16
import java.util.BitSet;
17
import java.util.List;
18
import java.util.Map;
19
20
import static com.keenwrite.processors.html.IdentityProcessor.IDENTITY;
21
import static com.vladsch.flexmark.parser.Parser.Builder;
22
23
/**
24
 * Responsible for processing inline R statements (denoted using the
25
 * {@link RInlineEvaluator#PREFIX}) to prevent them from being converted to
26
 * HTML {@code <code>} elements and stop them from interfering with TeX
27
 * statements. Note that TeX statements are processed using a Markdown
28
 * extension, rather than an implementation of {@link Processor}. For this
29
 * reason, some pre-conversion is necessary.
30
 */
31
public final class RInlineExtension implements MarkdownParserExtension {
32
  private final RInlineEvaluator mEvaluator;
33
  private final BaseMarkdownProcessor mMarkdownProcessor;
34
35
  private RInlineExtension(
36
    final RInlineEvaluator evaluator,
37
    final ProcessorContext context ) {
38
    mEvaluator = evaluator;
39
    mMarkdownProcessor = new BaseMarkdownProcessor( IDENTITY, context );
40
  }
41
42
  /**
43
   * Creates an extension capable of intercepting R code blocks and preventing
44
   * them from being converted into HTML {@code <code>} elements.
45
   */
46
  public static RInlineExtension create(
47
    final RInlineEvaluator evaluator,
48
    final ProcessorContext context ) {
49
    return new RInlineExtension( evaluator, context );
50
  }
51
52
  @Override
53
  public void extend( final Builder builder ) {
54
    builder.customInlineParserFactory( InlineParser::new );
55
  }
56
57
  /**
58
   * Prevents rendering {@code `r} statements as inline HTML {@code <code>}
59
   * blocks, which allows the {@link RInlineEvaluator} to post-process the
60
   * text prior to display in the preview pane. This intervention assists
61
   * with decoupling the caret from the Markdown content so that the two
62
   * can vary independently in the architecture while permitting synchronization
63
   * of the editor and preview pane.
64
   * <p>
65
   * The text is therefore processed twice: once by flexmark-java and once by
66
   * {@link RInlineEvaluator}.
67
   * </p>
68
   */
69
  private final class InlineParser extends InlineParserImpl {
70
    private InlineParser(
71
      final DataHolder options,
72
      final BitSet specialCharacters,
73
      final BitSet delimiterCharacters,
74
      final Map<Character, DelimiterProcessor> delimiterProcessors,
75
      final LinkRefProcessorData referenceLinkProcessors,
76
      final List<InlineParserExtensionFactory> inlineParserExtensions ) {
77
      super(
78
        options,
79
        specialCharacters,
80
        delimiterCharacters,
81
        delimiterProcessors,
82
        referenceLinkProcessors,
83
        inlineParserExtensions
84
      );
85
    }
86
87
    /**
88
     * The superclass handles a number backtick parsing edge cases; this method
89
     * changes the behaviour to retain R code snippets, identified by
90
     * {@link RInlineEvaluator#PREFIX}, so that subsequent processing can
91
     * invoke R. If other languages are added, the {@link InlineParser} will
92
     * have to be rewritten to identify more than merely R.
93
     *
94
     * @return The return value from {@link super#parseBackticks()}.
95
     * @inheritDoc
96
     */
97
    @Override
98
    protected boolean parseBackticks() {
99
      final var foundTicks = super.parseBackticks();
100
101
      if( foundTicks ) {
102
        final var blockNode = getBlock();
103
        final var codeNode = blockNode.getLastChild();
104
105
        if( codeNode != null ) {
106
          final var code = codeNode.getChars().toString();
107
108
          if( mEvaluator.test( code ) ) {
109
            codeNode.unlink();
110
111
            final var rText = mEvaluator.apply( code );
112
            var node = mMarkdownProcessor.toNode( rText );
113
114
            if( node.getFirstChild() instanceof Paragraph paragraph ) {
115
              node = paragraph.getFirstChild();
116
            }
117
118
            if( node != null ) {
119
              blockNode.appendChild( node );
120
            }
121
          }
122
        }
123
      }
124
125
      return foundTicks;
126
    }
127
  }
128
}
1129
A src/main/java/com/keenwrite/processors/markdown/extensions/references/AnchorNameDelimiterProcessor.java
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
parser.InlineParser;
7
import com.vladsch.flexmark.parser.core.delimiter.Delimiter;
8
import coimport static com.keenwrite.constants.Constants.DEF_DELIM_BEGAN_DEFAULT;
9
10
/**
11
 * Responsible for processing {@code {@type:id}} anchors.
12
 */
13
class AnchorNam
14
   AnchorNameNode({'}'
15
   text = opener.getNode();
116
17
    // Ensure that the default delimiters are respected (not clobbered by
18
    // transforming them into anchor links).
19
    return text.getChars().toString().equals( DEF_DELIM_BEGAN_DEFAULT ) ? 0 : 1
20
   canBeOpener(
21
   
22
   
A src/main/java/com/keenwrite/processors/markdown/extensions/references/AnchorNameNode.java
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
}
178
A src/main/java/com/keenwrite/processors/markdown/extensions/references/AnchorXrefNode.java
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
}
148
A src/main/java/com/keenwrite/processors/markdown/extensions/references/AnchorXrefProcessorFactory.java
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
}
182
A src/main/java/com/keenwrite/processors/markdown/extensions/references/BasedSequenceNameParser.java
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.seque
8
  @Override
9
  Pattern getPattern() {
10
    return PATTERN;
11
  }
12
}
113
A src/main/java/com/keenwrite/processors/markdown/extensions/references/BasedSequenceParser.java
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
}
161
A src/main/java/com/keenwrite/processors/markdown/extensions/references/BasedSequenceXrefParser.java
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;
16
7
import com.vladsch.flexmark.util.ast.Node;
8
import com.vladsch.flexmark.util.sequecommonPattern getPattern() {
9
    return PATTERN
A src/main/java/com/keenwrite/processors/markdown/extensions/references/CrossReferenceExtension.java
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.MarkdownRenderhtml.renderer.NodeRendererFactoryBuilderfinalMarkdownRendererExtension {
8
  /**
9
   * Use {@link #create()}.
10
   */
11
  private() {}
12
13
  /**
14
   * Returns a new {@link}.
15
   *
16
   * @return An extension capable of parsing cross-reference syntax.
17
   */
18
  public staticcreate() {
19
    return();
20
  }rotected NodeRendererFactory createNodeRendererFactory() {
21
    returnFactory();
22
  }
23
}
124
A src/main/java/com/keenwrite/processors/markdown/extensions/references/CrossReferenceNode.java
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
}
136
A src/main/java/com/keenwrite/processors/markdown/extensions/references/CrossReferencesNodeRenderer.java
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.vladHtmlWriter;
8
import com.vladsch.flexmark.html.renderer.NodeRenderer;
9
import com.vlad
10
import java.util.Arrays;
11
import java.util.HashSet;HTML elements that correspond to cross-references.
12
 */
13
class CrossReferencesNodenew HashSet<>( Arrays.asListAnchorNameNodeAnchorXrefNode );
14
  }
115
16
  private void render(
17
    final CrossReferenceNode
A src/main/java/com/keenwrite/processors/markdown/extensions/references/CrossReferencesNodeRendererFactory.java
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;
16
7
import com.keenwrite.processors.markdown.extensions.common.Markdownh;
8
import com.vladsch.flexmark.hextends MarkdownNodeRendererFactory {
9
  @Override
10
  protected NodeRenderer createNodeRenderer(
A src/main/java/com/keenwrite/processors/markdown/extensions/tex/TexExtension.java
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
}
163
A src/main/java/com/keenwrite/processors/markdown/extensions/tex/TexInlineDelimiterProcessor.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.tex;
3
parser.core.delimiter.Delimiter;
4
sch.flexmark.parser.core.delimiter.Delimiter;
5
class TexInlinTexNode( opener, closer $$/**
6
   * Allow for $ or $$.
7
   *
8
   * @param opener One or more opening delimiter characters.
9
   * @param closer One or more closing delimiter characters.
10
   * @return The number of delimiters to use to determine whether a valid
11
   * opening delimiter expression is found.
12
   */
A src/main/java/com/keenwrite/processors/markdown/extensions/tex/TexNode.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.tex;
3
4
import com.vladsch.flexmark.ast.DelimitedNodeImpl;
5
import com.vladsch.flexmark.parser.core.delimiter.Delimiter;
6
7
public class TexNode extends DelimitedNodeImpl {
8
  /**
9
   * A TeX expression wrapped in a {@code <tex>} element.
10
   */
11
  public static final String HTML_TEX = "tex";
12
13
  static final String TOKEN_OPEN = "$";
14
  static final String TOKEN_CLOSE = "$";
15
16
  private final String mOpener;
17
  private final String mCloser;
18
19
  /**
20
   * Creates a new TeX node representation that can distinguish between '$'
21
   * and '$$' as opening/closing delimiters. The '$' is used for inline
22
   * TeX statements and '$$' is used for multi-line statements.
23
   *
24
   * @param opener The opening delimiter.
25
   * @param closer The closing delimiter.
26
   */
27
  public TexNode( final Delimiter opener, final Delimiter closer ) {
28
    mOpener = getDelimiter( opener );
29
    mCloser = getDelimiter( closer );
30
  }
31
32
  /**
33
   * @return Either '$' or '$$'.
34
   */
35
  public String getOpeningDelimiter() {
36
    return mOpener;
37
  }
38
39
  /**
40
   * @return Either '$' or '$$'.
41
   */
42
  public String getClosingDelimiter() {
43
    return mCloser;
44
  }
45
46
  private String getDelimiter( final Delimiter delimiter ) {
47
    return delimiter.getInput().subSequence(
48
      delimiter.getStartIndex(), delimiter.getEndIndex()
49
    ).toString();
50
  }
51
}
152
A src/main/java/com/keenwrite/processors/markdown/extensions/tex/TexNodeRenderer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.markdown.extensions.tex;
3
4
import com.keenwrite.ExportFormat;
5
import com.keenwrite.preview.MathRenderer;
6
import com.keenwrite.preview.SvgRasterizer;
7
import com.vladsch.flexmark.html.HtmlWriter;
8
import com.vladsch.flexmark.html.renderer.NodeRenderer;
9
import com.vladsch.flexmark.html.renderer.NodeRendererContext;
10
import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
11
import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
12
import com.vladsch.flexmark.util.ast.Node;
13
import com.vladsch.flexmark.util.data.DataHolder;
14
import org.jetbrains.annotations.NotNull;
15
import org.jetbrains.annotations.Nullable;
16
17
import java.util.Map;
18
import java.util.Set;
19
import java.util.function.Function;
20
21
import static com.keenwrite.ExportFormat.*;
22
import static com.keenwrite.processors.markdown.extensions.tex.TexNode.*;
23
24
public class TexNodeRenderer {
25
  private static final RendererFacade RENDERER =
26
    new TexElementNodeRenderer( false );
27
28
  private static final Map<ExportFormat, RendererFacade> EXPORT_RENDERERS =
29
    Map.of(
30
      APPLICATION_PDF, new TexElementNodeRenderer( true ),
31
      HTML_TEX_SVG, new TexSvgNodeRenderer(),
32
      HTML_TEX_DELIMITED, new TexDelimitedNodeRenderer(),
33
      XHTML_TEX, new TexElementNodeRenderer( true ),
34
      TEXT_TEX, new TexElementNodeRenderer( true ),
35
      NONE, RENDERER
36
    );
37
38
  public static class TexNodeRendererFactory implements NodeRendererFactory {
39
    private final RendererFacade mNodeRenderer;
40
41
    public TexNodeRendererFactory(
42
      final ExportFormat exportFormat,
43
      final Function<String, String> evaluator ) {
44
      final var format = exportFormat == null ? NONE : exportFormat;
45
46
      mNodeRenderer = EXPORT_RENDERERS.getOrDefault( format, RENDERER );
47
      mNodeRenderer.setEvaluator( evaluator );
48
    }
49
50
    @NotNull
51
    @Override
52
    public NodeRenderer apply( @NotNull final DataHolder options ) {
53
      return mNodeRenderer;
54
    }
55
  }
56
57
  private static abstract class RendererFacade
58
    implements NodeRenderer {
59
    private Function<String, String> mEvaluator;
60
61
    @Override
62
    public @Nullable Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
63
      return Set.of(
64
        new NodeRenderingHandler<>( TexNode.class, this::render )
65
      );
66
    }
67
68
    /**
69
     * Subclasses implement this method to render the content of {@link TexNode}
70
     * instances as per their associated {@link ExportFormat}.
71
     *
72
     * @param node    {@link Node} containing text content of a math formula.
73
     * @param context Configuration information (unused).
74
     * @param html    Where to write the rendered output.
75
     */
76
    abstract void render( final TexNode node,
77
                          final NodeRendererContext context,
78
                          final HtmlWriter html );
79
80
    private void setEvaluator( final Function<String, String> evaluator ) {
81
      mEvaluator = evaluator;
82
    }
83
84
    Function<String, String> getEvaluator() {
85
      return mEvaluator;
86
    }
87
  }
88
89
  /**
90
   * Responsible for rendering a TeX node as an HTML {@code <tex>}
91
   * element. This is the default behaviour.
92
   */
93
  private static class TexElementNodeRenderer extends RendererFacade {
94
    private final boolean mIncludeDelimiter;
95
96
    private TexElementNodeRenderer( final boolean includeDelimiter ) {
97
      mIncludeDelimiter = includeDelimiter;
98
    }
99
100
    void render( final TexNode node,
101
                 final NodeRendererContext context,
102
                 final HtmlWriter html ) {
103
      final var text = getEvaluator().apply( node.getText().toString() );
104
      final var content =
105
        mIncludeDelimiter
106
          ? node.getOpeningDelimiter() + text + node.getClosingDelimiter()
107
          : text;
108
      html.tag( HTML_TEX );
109
      html.raw( content );
110
      html.closeTag( HTML_TEX );
111
    }
112
  }
113
114
  /**
115
   * Responsible for rendering a TeX node as an HTML {@code <svg>}
116
   * element.
117
   */
118
  private static class TexSvgNodeRenderer extends RendererFacade {
119
    void render( final TexNode node,
120
                 final NodeRendererContext context,
121
                 final HtmlWriter html ) {
122
      final var tex = node.getText().toStringOrNull();
123
      final var doc = MathRenderer.toDocument(
124
        tex == null ? "" : getEvaluator().apply( tex )
125
      );
126
      final var svg = SvgRasterizer.toSvg( doc.getDocumentElement() );
127
      html.raw( svg );
128
    }
129
  }
130
131
  /**
132
   * Responsible for rendering a TeX node as text bracketed by $ tokens.
133
   */
134
  private static class TexDelimitedNodeRenderer extends RendererFacade {
135
    void render( final TexNode node,
136
                 final NodeRendererContext context,
137
                 final HtmlWriter html ) {
138
      html.raw( TOKEN_OPEN );
139
      html.raw( getEvaluator().apply( node.getText().toString() ) );
140
      html.raw( TOKEN_CLOSE );
141
    }
142
  }
143
}
1144
A src/main/java/com/keenwrite/processors/pdf/PdfProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.pdf;
3
4
import com.keenwrite.processors.ExecutorProcessor;
5
import com.keenwrite.processors.ProcessorContext;
6
import com.keenwrite.typesetting.Typesetter;
7
8
import static com.keenwrite.Bootstrap.APP_TITLE_ABBR;
9
import static com.keenwrite.events.StatusEvent.clue;
10
import static com.keenwrite.io.MediaType.TEXT_XML;
11
import static com.keenwrite.io.SysFile.normalize;
12
import static com.keenwrite.typesetting.Typesetter.Mutator;
13
import static com.keenwrite.util.Strings.sanitize;
14
import static java.nio.charset.StandardCharsets.UTF_8;
15
import static java.nio.file.Files.deleteIfExists;
16
import static java.nio.file.Files.writeString;
17
18
/**
19
 * Responsible for using a typesetting engine to convert an XHTML document
20
 * into a PDF file. This must not be run from the JavaFX thread.
21
 */
22
public final class PdfProcessor extends ExecutorProcessor<String> {
23
  private final ProcessorContext mProcessorContext;
24
25
  public PdfProcessor( final ProcessorContext context ) {
26
    assert context != null;
27
    mProcessorContext = context;
28
  }
29
30
  /**
31
   * Converts a document by calling a third-party application to typeset the
32
   * given XHTML document.
33
   *
34
   * @param xhtml The document to convert to a PDF file.
35
   * @return {@code null} because there is no valid return value from generating
36
   * a PDF file.
37
   */
38
  public String apply( final String xhtml ) {
39
    try {
40
      clue( "Main.status.typeset.create" );
41
42
      final var context = mProcessorContext;
43
      final var targetPath = context.getTargetPath();
44
      clue( "Main.status.typeset.setting", "target", targetPath );
45
46
      final var parent = normalize( targetPath.toAbsolutePath().getParent() );
47
48
      final var document = TEXT_XML.createTempFile( APP_TITLE_ABBR, parent );
49
      final var sourcePath = writeString( document, xhtml, UTF_8 );
50
      clue( "Main.status.typeset.setting", "source", sourcePath );
51
52
      final var themeDir = normalize( context.getThemeDir() );
53
      clue( "Main.status.typeset.setting", "themes", themeDir );
54
55
      final var imageDir = normalize( context.getImageDir() );
56
      clue( "Main.status.typeset.setting", "images", imageDir );
57
58
      final var imageOrder = context.getImageOrder();
59
      clue( "Main.status.typeset.setting", "order", imageOrder );
60
61
      final var cacheDir = normalize( context.getCacheDir() );
62
      clue( "Main.status.typeset.setting", "caches", cacheDir );
63
64
      final var fontDir = normalize( context.getFontDir() );
65
      clue( "Main.status.typeset.setting", "fonts", fontDir );
66
67
      final var rWorkDir = normalize( context.getRWorkingDir() );
68
      clue( "Main.status.typeset.setting", "r-work", rWorkDir );
69
70
      final var modesEnabled = sanitize( context.getModesEnabled() );
71
      clue( "Main.status.typeset.setting", "mode", modesEnabled );
72
73
      final var autoRemove = context.getAutoRemove();
74
      clue( "Main.status.typeset.setting", "purge", autoRemove );
75
76
      final var typesetter = Typesetter
77
        .builder()
78
        .with( Mutator::setTargetPath, targetPath )
79
        .with( Mutator::setSourcePath, sourcePath )
80
        .with( Mutator::setThemeDir, themeDir )
81
        .with( Mutator::setImageDir, imageDir )
82
        .with( Mutator::setCacheDir, cacheDir )
83
        .with( Mutator::setFontDir, fontDir )
84
        .with( Mutator::setModesEnabled, modesEnabled )
85
        .with( Mutator::setAutoRemove, autoRemove )
86
        .build();
87
88
      try {
89
        typesetter.typeset();
90
      }
91
      finally {
92
        // Smote the temporary file after typesetting the document.
93
        if( typesetter.autoRemove() ) {
94
          deleteIfExists( document );
95
        }
96
      }
97
    } catch( final Exception ex ) {
98
      // Typesetter runtime exceptions will pass up the call stack.
99
      clue( "Main.status.typeset.failed", ex );
100
    }
101
102
    // Do not continue processing (the document was typeset into a binary).
103
    return null;
104
  }
105
}
1106
A src/main/java/com/keenwrite/processors/r/Engine.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.r;
3
4
import com.keenwrite.collections.BoundedCache;
5
6
import javax.script.ScriptEngine;
7
import javax.script.ScriptEngineManager;
8
import java.util.Map;
9
10
import static com.keenwrite.Messages.get;
11
import static com.keenwrite.events.StatusEvent.clue;
12
import static java.lang.Math.min;
13
14
/**
15
 * Responsible for executing R statements, which can also update the engine's
16
 * state.
17
 */
18
public final class Engine {
19
  /**
20
   * Inline R expressions that have already been evaluated.
21
   */
22
  private static final Map<String, String> sCache =
23
    new BoundedCache<>( 768 );
24
25
  /**
26
   * Engine for evaluating R expressions.
27
   */
28
  private static final ScriptEngine sEngine =
29
    new ScriptEngineManager().getEngineByName( "Renjin" );
30
31
  /**
32
   * Empties the cache.
33
   */
34
  public static void clear() {
35
    sCache.clear();
36
  }
37
38
  /**
39
   * Look up an R expression from the cache then return the resulting object.
40
   * If the R expression hasn't been cached, it'll first be evaluated.
41
   *
42
   * @param r R expression to evaluate.
43
   * @return The object resulting from the evaluation.
44
   */
45
  public static String eval( final String r ) {
46
    return sCache.computeIfAbsent( r, _ -> evaluate( r ) );
47
  }
48
49
  /**
50
   * Returns the result of an R expression as an object converted to string.
51
   *
52
   * @param r R expression to evaluate.
53
   * @return The object resulting from the evaluation.
54
   */
55
  private static String evaluate( final String r ) {
56
    try {
57
      return sEngine.eval( r ).toString();
58
    } catch( final Exception ex ) {
59
      final var expr = r.substring( 0, min( r.length(), 50 ) );
60
      clue( get( "Main.status.error.r", expr, ex.getMessage() ), ex );
61
      throw new IllegalArgumentException( r );
62
    }
63
  }
64
}
165
A src/main/java/com/keenwrite/processors/r/RBootstrapController.java
1
/* Copyright 2020 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.r;
6
7
import com.keenwrite.preferences.Workspace;
8
import com.keenwrite.sigils.RKeyOperator;
9
10
import java.util.HashMap;
11
import java.util.Map;
12
import java.util.function.Supplier;
13
14
import static com.keenwrite.events.StatusEvent.clue;
15
import static com.keenwrite.preferences.AppKeys.KEY_R_DIR;
16
import static com.keenwrite.preferences.AppKeys.KEY_R_SCRIPT;
17
import static com.keenwrite.processors.variable.RVariableProcessor.escape;
18
import static com.keenwrite.processors.text.TextReplacementFactory.replace;
19
20
/**
21
 * Transforms a document containing R statements into Markdown.
22
 */
23
public final class RBootstrapController {
24
25
  private static final RKeyOperator KEY_OPERATOR = new RKeyOperator();
26
27
  private final Workspace mWorkspace;
28
  private final Supplier<Map<String, String>> mSupplier;
29
30
  public RBootstrapController(
31
    final Workspace workspace,
32
    final Supplier<Map<String, String>> supplier ) {
33
    assert workspace != null;
34
    assert supplier != null;
35
36
    mWorkspace = workspace;
37
    mSupplier = supplier;
38
39
    mWorkspace.stringProperty( KEY_R_SCRIPT )
40
              .addListener( ( c, o, n ) -> update() );
41
    mWorkspace.fileProperty( KEY_R_DIR )
42
              .addListener( ( c, o, n ) -> update() );
43
44
    // Add the definitions immediately upon loading them.
45
    update();
46
  }
47
48
  /**
49
   * Updates the R code so that R can find imported libraries. Note that
50
   * any existing R functionality will not be overwritten if this method is
51
   * called multiple times.
52
   */
53
  public void update() {
54
    final var bootstrap = getRScript();
55
56
    if( !bootstrap.isBlank() ) {
57
      final var dir = getRWorkingDirectory();
58
      final var definitions = mSupplier.get();
59
60
      update( bootstrap, dir, definitions );
61
    }
62
  }
63
64
  public static void update(
65
    final String bootstrap,
66
    final String workingDir,
67
    final Map<String, String> definitions ) {
68
69
    if( !bootstrap.isBlank() ) {
70
      final Map<String, String> map;
71
72
      if( definitions == null ) {
73
        map = new HashMap<>();
74
      }
75
      else {
76
        map = new HashMap<>( definitions.size() + 1 );
77
        definitions.forEach(
78
          ( k, v ) -> map.put( KEY_OPERATOR.apply( k ), escape( v ) )
79
        );
80
      }
81
82
      map.put(
83
        KEY_OPERATOR.apply( "application.r.working.directory" ),
84
        escape( workingDir )
85
      );
86
87
      try {
88
        Engine.eval( replace( bootstrap, map ) );
89
      } catch( final Exception ex ) {
90
        clue( ex );
91
      }
92
    }
93
  }
94
95
  private String getRScript() {
96
    return mWorkspace.getString( KEY_R_SCRIPT );
97
  }
98
99
  private String getRWorkingDirectory() {
100
    final var wd = mWorkspace.getFile( KEY_R_DIR );
101
    return wd.toString().replace( '\\', '/' );
102
  }
103
}
1104
A src/main/java/com/keenwrite/processors/r/RBootstrapProcessor.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.r;
6
7
import com.keenwrite.processors.ExecutorProcessor;
8
import com.keenwrite.processors.Processor;
9
import com.keenwrite.processors.ProcessorContext;
10
11
public class RBootstrapProcessor extends ExecutorProcessor<String> {
12
  private final Processor<String> mSuccessor;
13
  private final ProcessorContext mContext;
14
15
  public RBootstrapProcessor(
16
    final Processor<String> successor,
17
    final ProcessorContext context ) {
18
    assert successor != null;
19
    assert context != null;
20
21
    mSuccessor = successor;
22
    mContext = context;
23
  }
24
25
  /**
26
   * Processes the given text document by replacing variables with their values.
27
   *
28
   * @param text The document text that includes variables that should be
29
   *             replaced with values when rendered as HTML.
30
   * @return The text with all variables replaced.
31
   */
32
  @Override
33
  public String apply( final String text ) {
34
    assert text != null;
35
36
    final var bootstrap = mContext.getRScript();
37
    final var workingDir = mContext.getRWorkingDir().toString();
38
    final var definitions = mContext.getDefinitions();
39
40
    RBootstrapController.update( bootstrap, workingDir, definitions );
41
42
    return mSuccessor.apply( text );
43
  }
44
}
145
A src/main/java/com/keenwrite/processors/r/RChunkEvaluator.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.r;
3
4
import java.util.function.Function;
5
6
import static com.keenwrite.constants.Constants.STATUS_PARSE_ERROR;
7
import static com.keenwrite.events.StatusEvent.clue;
8
9
/**
10
 * Transforms a document containing R statements into Markdown. The statements
11
 * are part of an R chunk, <code>```{r}</code>.
12
 */
13
public final class RChunkEvaluator implements Function<String, String> {
14
15
  /**
16
   * Constructs an evaluator capable of executing R statements.
17
   */
18
  public RChunkEvaluator() {}
19
20
  /**
21
   * Evaluates the given R statements and returns the result as a string.
22
   * If an image was produced, the calling code is responsible for persisting
23
   * and making the file embeddable into the document.
24
   *
25
   * @param r The R statements to evaluate.
26
   * @return The output from the final R statement.
27
   */
28
  @Override
29
  public String apply( final String r ) {
30
    try {
31
      return Engine.eval( r );
32
    } catch( final Exception ex ) {
33
      clue( STATUS_PARSE_ERROR, ex.getMessage() );
34
35
      return r;
36
    }
37
  }
38
}
139
A src/main/java/com/keenwrite/processors/r/RInlineEvaluator.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.r;
3
4
import com.keenwrite.processors.Processor;
5
import com.keenwrite.processors.variable.RVariableProcessor;
6
7
import java.util.function.Function;
8
import java.util.function.Predicate;
9
10
import static com.keenwrite.constants.Constants.STATUS_PARSE_ERROR;
11
import static com.keenwrite.events.StatusEvent.clue;
12
13
/**
14
 * Evaluates inline R statements.
15
 */
16
public final class RInlineEvaluator
17
  implements Function<String, String>, Predicate<String> {
18
  public static final String PREFIX = "`r#";
19
  public static final String SUFFIX = "`";
20
21
  private static final int PREFIX_LENGTH = PREFIX.length();
22
23
  private final Processor<String> mProcessor;
24
25
  /**
26
   * Constructs an evaluator capable of executing R statements.
27
   */
28
  public RInlineEvaluator( final RVariableProcessor processor ) {
29
    mProcessor = processor;
30
  }
31
32
  /**
33
   * Evaluates all R statements in the source document and inserts the
34
   * calculated value into the generated document.
35
   *
36
   * @param text The document text that includes variables that should be
37
   *             replaced with values when rendered as HTML.
38
   * @return The generated document with output from all R statements
39
   * substituted with value returned from their execution.
40
   */
41
  @Override
42
  public String apply( final String text ) {
43
    try {
44
      final var buffer = new StringBuilder( text.length() );
45
46
      int index = 0;
47
      int began;
48
      int ended = 0;
49
50
      while( (began = text.indexOf( PREFIX, index )) >= 0 && ended > -1 ) {
51
        buffer.append( text, index, began );
52
53
        // If the R expression has no definite end, this returns -1.
54
        ended = text.indexOf( SUFFIX, began + 1 );
55
56
        if( ended > began ) {
57
          final var r = mProcessor.apply(
58
            text.substring( began + PREFIX_LENGTH, ended )
59
          );
60
61
          // Return the evaluated R expression for insertion back into the text.
62
          buffer.append( Engine.eval( r ) );
63
64
          index = ended + 1;
65
        }
66
      }
67
68
      buffer.append( text.substring( index ) );
69
70
      return buffer.toString();
71
    } catch( final Exception ex ) {
72
      clue( STATUS_PARSE_ERROR, ex.getMessage() );
73
74
      // If the string couldn't be parsed using R, append the statement
75
      // that failed to parse, instead of its evaluated value.
76
      return text;
77
    }
78
  }
79
80
  /**
81
   * Answers whether the engine associated with this evaluator may attempt to
82
   * evaluate the given source code statement.
83
   *
84
   * @param code The source code to verify.
85
   * @return {@code true} if the code may be evaluated.
86
   */
87
  @Override
88
  public boolean test( final String code ) {
89
    return code.startsWith( PREFIX );
90
  }
91
}
192
A src/main/java/com/keenwrite/processors/text/AbstractTextReplacer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.text;
3
4
import java.util.Map;
5
6
/**
7
 * Responsible for common behaviour across all text replacer implementations.
8
 */
9
public abstract class Abstrac = new String[ 0 ] = new String[ 0 ]private final Object mMutex = new Object()synchronized( mMutex ) {
10
        return mKeys;
11
    }synchronized( mMutex ) {
12
        return mValues;
13
    }synchronized( mMutex ) {
14
    }
15
    }
16
  }
17
}
118
A src/main/java/com/keenwrite/processors/text/AhoCorasickReplacer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.text;
3
4
import java.util.Map;
5
6
import static org.ahocorasick.trie.Trie.builder;
7
8
/**
9
 * Replaces text using an Aho-Corasick algorithm.
10
 */
11
public final var keySet = keys( map );
112
13
    if( keySet != null ) {
14
      }
A src/main/java/com/keenwrite/processors/text/StringUtilsReplacer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.text;
3
4
import java.util.Map;
5
6
import static com.keenwrite.util.Strings.replaceEach;
7
8
/**
9
 * Replaces text using a brute-force replacement method.
10
 */
11
public class StringUtilsReplacer extends AbstractTextReplacer {
12
13
  /**
14
   * Default (empty) constructor.
15
   */
16
  protected StringUtilsReplacer() {}
17
18
  @Override
19
  public String replace( final String text, final Map<String, String> map ) {
20
    return replaceEach( text, keys( map ), values( map ) );
21
  }
22
}
123
A src/main/java/com/keenwrite/processors/text/TextProcessor.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.text;
6
io.MediaType;
7
import com.keenwrite.processors.ExecutorPconverting documents to plain text files. This will
8
 * perform interpolated variable substitutions and execute R commands
9
 * as necessary.
10
 */
11
public class TextProcessor extends ExecutorProcessor<String> {
12
  privatemEvaluator;
13
14
  public Text
15
    );
16
m}
17
    else {
18
      mEvaluat}
19
  }
20
21
  @Override
22
  public String apply( final String document ) {
23
    return mEvaluator.apply( document );
24
  }
25
}
126
A src/main/java/com/keenwrite/processors/text/TextReplacementFactory.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.text;
3
4
import com.keenwrite.util.Strings;
5
6
import java.util.Map;
7
8
/**
9
 * Used to generate a class capable of efficiently replacing variable
10
 * definitions with their values.
11
 */
12
public final class TextReplacementFactory {
13
14
  private static final TextReplacer APACHE = new StringUtilsReplacer();
15
  private static final TextReplacer AHO_CORASICK = new AhoCorasickReplacer();
16
17
  /**
18
   * Returns a text search/replacement instance that is reasonably optimal for
19
   * the given length of text.
20
   *
21
   * @param length The length of text that requires some search and replacing.
22
   * @return A class that can search and replace text with utmost expediency.
23
   */
24
  public static TextReplacer getTextReplacer( final int length ) {
25
    // After about 1,500 characters, the Aho-Corsick algorithm is faster.
26
    return length < 1500 ? APACHE : AHO_CORASICK;
27
  }
28
29
  /**
30
   * Convenience method to instantiate a suitable text replacer algorithm and
31
   * perform a replacement using the given map. At this point, the values should
32
   * be already dereferenced and ready to be substituted verbatim; any
33
   * recursively defined values must have been interpolated previously.
34
   *
35
   * @param text    The text containing zero or more variables to replace.
36
   * @param needles The map of variables to their dereferenced values.
37
   * @return The text with all variables replaced.
38
   */
39
  public static String replace(
40
    final String text, final Map<String, String> needles ) {
41
    final String haystack = Strings.sanitize( text );
42
    assert needles != null;
43
44
    return getTextReplacer( haystack.length() ).replace( haystack, needles );
45
  }
46
}
147
A src/main/java/com/keenwrite/processors/text/TextReplacer.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.text;
3
4
import java.util.Map;
5
6
/**
7
 * Defines the ability to replace text given a set of keys and values.
8
 */
9
public interface TextReplacer {
10
11
  /**
12
   * Searches through the given text for any of the keys given in the map and
13
   * replaces the keys that appear in the text with the key's corresponding
14
   * value.
15
   *
16
   * @param text The text that contains zero or more keys.
17
   * @param map  The set of keys mapped to replacement values.
18
   * @return The given text with all keys replaced with corresponding values.
19
   */
20
  String replace( String text, Map<String, String> map );
21
}
122
A src/main/java/com/keenwrite/processors/variable/RVariableProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.variable;
16
7
import com.keenwrite.processors.Processor;
8
import com.keenwrite.processors.ProcessorContext;
9
import com.keenwrite.
A src/main/java/com/keenwrite/processors/variable/VariableProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.processors.variable;
3
4
import com.keenwrite.processors.ExecutorProcessor;
5
import com.keenwrite.processors.Processor;
6
import com.keenwrite.processors.ProcessorContext;
7
import com.keenwrite.sigils.SigilKeyOperator;
8
9
import java.util.HashMap;
10
import java.util.Map;
11
import java.util.function.Function;
12
13
import static com.keenwrite.processors.text.TextReplacementFactory.replace;
14
15
/**
16
 * Processes interpolated string definitions in the document and inserts
17
 * their values into the post-processed text. The default variable syntax is
18
 * <pre>{{variable}}</pre> (a.k.a., moustache syntax).
19
 */
20
public class VariableProcessor
21
  extends ExecutorProcessor<String> implements Function<String, String> {
22
23
  private final ProcessorContext mContext;
24
  private final SigilKeyOperator mSigilOperator;
25
26
  /**
27
   * Constructs a processor capable of interpolating string definitions.
28
   *
29
   * @param successor Subsequent link in the processing chain.
30
   * @param context   Contains resolved definitions map.
31
   */
32
  public VariableProcessor(
33
    final Processor<String> successor,
34
    final ProcessorContext context ) {
35
    super( successor );
36
37
    mContext = context;
38
    mSigilOperator = createKeyOperator( context );
39
  }
40
41
  /**
42
   * Subclasses may change the type of operation performed on keys, such as
43
   * wrapping key names in sigils.
44
   *
45
   * @param context Provides the name of the file being edited.
46
   * @return An operator for transforming key names.
47
   */
48
  protected SigilKeyOperator createKeyOperator(
49
    final ProcessorContext context ) {
50
    return context.createKeyOperator();
51
  }
52
53
  /**
54
   * Returns the map to use for variable substitution.
55
   *
56
   * @return A map of variable names to values, with keys wrapped in sigils.
57
   */
58
  public Map<String, String> getDefinitions() {
59
    return entoken( mContext.getInterpolatedDefinitions() );
60
  }
61
62
  /**
63
   * Subclasses may override this method to change how keys are wrapped
64
   * in sigils.
65
   *
66
   * @param key The key to enwrap.
67
   * @return The wrapped key.
68
   */
69
  protected String processKey( final String key ) {
70
    return mSigilOperator.apply( key );
71
  }
72
73
  /**
74
   * Subclasses may override this method to modify values prior to use. This
75
   * can be used, for example, to escape values prior to evaluating by a
76
   * scripting engine.
77
   *
78
   * @param value The value to process.
79
   * @return The processed value.
80
   */
81
  protected String processValue( final String value ) {
82
    return value;
83
  }
84
85
  /**
86
   * Answers whether the given key is wrapped in sigil tokens.
87
   *
88
   * @param key The key to analyze.
89
   * @return {@code true} if the key is wrapped in sigils.
90
   */
91
  public boolean hasSigils( final String key ) {
92
    return mSigilOperator.match( key ).find();
93
  }
94
95
  /**
96
   * Processes the given text document by replacing variables with their values.
97
   *
98
   * @param text The document text that includes variables that should be
99
   *             replaced with values when rendered as HTML.
100
   * @return The text with all variables replaced.
101
   */
102
  @Override
103
  public String apply( final String text ) {
104
    assert text != null;
105
106
    return replace( text, getDefinitions() );
107
  }
108
109
  /**
110
   * Converts the given map from regular variables to processor-specific
111
   * variables.
112
   *
113
   * @param map Map of variable names to values.
114
   * @return Map of variables with the keys and values subjected to
115
   * post-processing.
116
   */
117
  protected Map<String, String> entoken( final Map<String, String> map ) {
118
    assert map != null;
119
120
    final var result = new HashMap<String, String>( map.size() );
121
122
    map.forEach( ( k, v ) -> result.put( processKey( k ), processValue( v ) ) );
123
124
    return result;
125
  }
126
}
1127
A src/main/java/com/keenwrite/search/SearchModel.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.search;
13
4
import com.keenwrite.util.CyclicIterator;
5
import javafx.beans.property.ObjectProperty;
6
import javafx.beans.property.SimpleObjectProperty;
7
import ja
A src/main/java/com/keenwrite/security/PermissiveCertificate.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.security;
3
4
import javax.net.ssl.*;
5
import java.security.KeyManagementException;
6
import java.security.NoSuchAlgorithmException;
7
import java.security.SecureRandom;
8
import java.security.cert.X509Certificate;
9
10
import static javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier;
11
import static javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory;
12
13
/**
14
 * Responsible for trusting all certificate chains. The purpose of this class
15
 * is to work-around certificate issues caused by software that blocks
16
 * HTTP requests. For example, Zscaler may block HTTP requests to kroki.io
17
 * when generating diagrams.
18
 */
19
public final class PermissiveCertificate {
20
  /**
21
   * Create a trust manager that does not validate certificate chains.
22
   */
23
  private static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{
24
    new X509TrustManager() {
25
      @Override
26
      public X509Certificate[] getAcceptedIssuers() {
27
        return new X509Certificate[ 0 ];
28
      }
29
30
      @Override
31
      public void checkClientTrusted(
32
        X509Certificate[] certs, String authType ) {
33
      }
34
35
      @Override
36
      public void checkServerTrusted(
37
        X509Certificate[] certs, String authType ) {
38
      }
39
    }
40
  };
41
42
  /**
43
   * Responsible for permitting all hostnames for making HTTP requests.
44
   */
45
  private static class PermissiveHostNameVerifier implements HostnameVerifier {
46
    @Override
47
    public boolean verify( final String hostname, final SSLSession session ) {
48
      return true;
49
    }
50
  }
51
52
  /**
53
   * Install the all-trusting trust manager. If this fails it means that in
54
   * certain situations the HTML preview may fail to render diagrams. A way
55
   * to work around the issue is to install a local server for generating
56
   * diagrams.
57
   */
58
  public static boolean installTrustManager() {
59
    try {
60
      final var context = SSLContext.getInstance( "SSL" );
61
      context.init( null, TRUST_ALL_CERTS, new SecureRandom() );
62
      setDefaultSSLSocketFactory( context.getSocketFactory() );
63
      setDefaultHostnameVerifier( new PermissiveHostNameVerifier() );
64
      return true;
65
    } catch( NoSuchAlgorithmException | KeyManagementException e ) {
66
      return false;
67
    }
68
  }
69
70
  /**
71
   * Use {@link #installTrustManager()}.
72
   */
73
  private PermissiveCertificate() {
74
  }
75
}
176
A src/main/java/com/keenwrite/service/Service.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
A src/main/java/com/keenwrite/service/Settings.java
1
1
/* Copyright 2020-2021
2
 * All rights reserved.
3
 * -- All rights reserved.
A src/main/java/com/keenwrite/service/events/Notification.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
A src/main/java/com/keenwrite/service/events/Notifier.java
1
/* Copyright 2020-2021 -- All rights reserved. */
2
package com.keenwritenio.file.PathPath path,
3
    String titleKey,
4
Path path,
5
String title,
16
A src/main/java/com/keenwrite/service/events/impl/ButtonOrderPane.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.service.events.impl;
3
4
import javafx.scene.Node;
5
import javafx.scene.control.ButtonBar;
6
import javafx.scene.control.DialogPane;
7
8
import static com.keenwrite.constants.Constants.sSettings;
9
import static javafx.scene.control.ButtonBar.BUTTON_ORDER_WINDOWS;
10
11
/**
12
 * Ensures a consistent button order for alert dialogs across platforms (because
13
 * the default button order on Linux defies all logic).
14
 */
15
public class ButtonOrderPane extends DialogPane {
16
  public ButtonOrderPane() {
17
  }
18
19
  @Override
20
  protected Node createButtonBar() {
21
    final var node = (ButtonBar) super.createButtonBar();
22
    node.setButtonOrder( getButtonOrder() );
23
    return node;
24
  }
25
26
  private String getButtonOrder() {
27
    return sSettings.getSetting(
28
      "dialog.alert.button.order.windows", BUTTON_ORDER_WINDOWS );
29
  }
30
}
131
A src/main/java/com/keenwrite/service/events/impl/DefaultNotification.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.service.events.impl;
3
4
import com.keenwrite.service.events.Notification;
5
6
import java.text.MessageFormat;
7
8
/**
9
 * Responsible for alerting the user to prominmTitle;mCmTitle = title;
10
    mCmTmContent;
11
  }
12
13
}
114
A src/main/java/com/keenwrite/service/events/impl/DefaultNotifier.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.service.events.impl;
13
4
import com.keenwrite.service.events.Notification;
5
import com.keenwrite.service.events.Notifier;
6
import javafx.scene.control.Alert;
7
impconstants
A src/main/java/com/keenwrite/service/impl/DefaultSettings.java
1
/* Copyright 2020-2021 -- All rights reserved. */
2
package com.keenwritecom.keenwrite.config.;
3
import com.keenwrite.service.Settings;
4
5
import static com.keenwrite.constants.Constants.PATH_PROPERTIES_SETTINGS/
6
public final
7
  privatemProperties = loadProperties() {      loadProperties() {
8
    final varvar    final var encoding = getDefaultEncodingvar reade
9
        url.openStream(), encodingread( reader );
10
      } catch( final exPATH_PROPERTIES_SETTINGSmProperties;
11
  }
12
}
113
A src/main/java/com/keenwrite/sigils/PropertyKeyOperator.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.sigils;
3
4
/**
5
 * Responsible for defining sigils used within property files.
6
 */
7
public class PropertyKeyOperator extends SigilKeyOperator {
8
  public static final String BEGAN = "${";
9
  public static final String ENDED = "}";
10
11
  /**
12
   * Constructs a new {@link SigilKeyOperator} subclass with <code>${</code>
13
   * and <code>}</code> used for the beginning and ending sigils.
14
   */
15
  public PropertyKeyOperator() {
16
    super( BEGAN, ENDED );
17
  }
18
}
119
A src/main/java/com/keenwrite/sigils/RKeyOperator.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.sigils;
3
4
import com.keenwrite.collections.BoundedCache;
5
6
/**
7
 * Converts dot-separated variable names into names compatible with R. That is,
8
 * {@code variable.name.qualified} becomes {@code v$variable$name$qualified}.
9
 */
10
public final class RKeyOperator extends SigilKeyOperator {
11
  private static final char KEY_SEPARATOR_DEF = '.';
12
  private static final char KEY_SEPARATOR_R = '$';
13
14
  /** Minor optimization to avoid recreating an object. */
15
  private final StringBuilder mVarName = new StringBuilder( 128 );
16
17
  /** Optimization to avoid re-converting variable names into R format. */
18
  private final BoundedCache<String, String> mVariables = new BoundedCache<>(
19
    2048
20
  );
21
22
  /**
23
   * Constructs a new instance capable of converting dot-separated variable
24
   * names into R's dollar-symbol-separated names.
25
   */
26
  public RKeyOperator() {
27
    // The keys are not delimited.
28
    super( "", "" );
29
  }
30
31
  /**
32
   * Transforms a definition key name into the expected format for an R
33
   * variable key name.
34
   * <p>
35
   * This algorithm is faster than {@link String#replace(char, char)}. Faster
36
   * still would be to cache the values, but that would mean managing the
37
   * cache when the user changes the beginning and ending of the R delimiters.
38
   * This code gives about a 2% performance boost when scrolling using
39
   * cursor keys. After the JIT warms up, this super-minor bottleneck vanishes.
40
   *
41
   * @param key The variable name to transform, neither blank nor {@code null}.
42
   * @return The transformed variable name.
43
   */
44
  @Override
45
  public String apply( final String key ) {
46
    assert key != null;
47
    assert !key.isBlank();
48
49
    return mVariables.computeIfAbsent( key, this::convert );
50
  }
51
52
  private String convert( final String key ) {
53
    mVarName.setLength( 0 );
54
    mVarName.append( "v" );
55
    mVarName.append( KEY_SEPARATOR_R );
56
    mVarName.append( key );
57
58
    // The 3 is for v$ + first char, which cannot be a separator.
59
    for( int i = mVarName.length() - 1; i >= 3; i-- ) {
60
      if( mVarName.charAt( i ) == KEY_SEPARATOR_DEF ) {
61
        mVarName.setCharAt( i, KEY_SEPARATOR_R );
62
      }
63
    }
64
65
    return mVarName.toString();
66
  }
67
}
168
A src/main/java/com/keenwrite/sigils/SigilKeyOperator.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.sigils;
3
4
import java.util.function.UnaryOperator;
5
import java.util.regex.Matcher;
6
import java.util.regex.Pattern;
7
8
import static java.lang.String.format;
9
import static java.util.regex.Pattern.compile;
10
import static java.util.regex.Pattern.quote;
11
12
/**
13
 * Responsible for bracketing definition keys with token delimiters.
14
 */
15
public class SigilKeyOperator implements UnaryOperator<String> {
16
  private final String mBegan;
17
  private final String mEnded;
18
  private final Pattern mPattern;
19
20
  public SigilKeyOperator( final String began, final String ended ) {
21
    assert began != null;
22
    assert ended != null;
23
24
    mBegan = began;
25
    mEnded = ended;
26
    mPattern = compile( format( "%s(.*?)%s", quote( began ), quote( ended ) ) );
27
  }
28
29
  @Override
30
  public String apply( final String key ) {
31
    assert key != null;
32
    assert !key.startsWith( mBegan );
33
    assert !key.endsWith( mEnded );
34
35
    return mBegan + key + mEnded;
36
  }
37
38
  public Matcher match( final String text ) {
39
    return mPattern.matcher( text );
40
  }
41
}
142
A src/main/java/com/keenwrite/spelling/api/SpellCheckListener.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
A src/main/java/com/keenwrite/spelling/api/SpellChecker.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
A src/main/java/com/keenwrite/spelling/api/package-info.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
3
/**
4
 * This package contains interfaces for spell checking implementations.
5
 */
6
package com.keenwrite.spelling.api;
17
A src/main/java/com/keenwrite/spelling/impl/Lexicon.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.spelling.impl;
6
7
import com.keenwrite.events.spelling.LexiconLoadedEvent;
8
import com.keenwrite.exceptions.MissingFileExceptionSTR.\{s}", count );
9
  }
10
}
111
A src/main/java/com/keenwrite/spelling/impl/PermissiveSpeller.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.final
A src/main/java/com/keenwrite/spelling/impl/SymSpellSpeller.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.spelling.impl;
3
4
import com.keenwrite.spelling.api.SpellCheckListener;
5
import com.keenwrite.spelling.api.SpellChecker;
6
import io.gitlab.rxp90.jsymspell.SymSpexceptions.NotInitializedException
7
    throws NotInitializedExceptionfinal var symSpell =.build();
18
9
 symSpell );
A src/main/java/com/keenwrite/spelling/impl/package-info.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
3
/**
4
 * This package contains classes for spell checking implementations.
5
 */
6
package com.keenwrite.spelling.impl;
17
A src/main/java/com/keenwrite/typesetting/GuestTypesetter.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.typesetting;
13
4
import com.keenwrite.io.CommandNotFoundException;
5
import com.keenwrite.io.StreamGobbler;
6
import com.keenwrite.io.SysFile;
7
import com.keenwriteTR."\{TYPESETTER_EXE} --version > /dev/null"TR."\{TYPESETTER_VERSION}; echo $?"
A src/main/java/com/keenwrite/typesetting/HostTypesetter.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting;
6
7
import com.keenwrite.collections.CircularQueue;
8
import com.keenwrite.io.StreamGobbler;
9
import com.keenwrite.io.SysFile;
10
11
import java.io.io.IOTEMPORARYio.SysFile.toFilecharset.StandardCharsets.UTF_8
12
 TargeAll( optionsCircularQueue<toFile( mDirectory );
13
14
      // Slurp page numbers in a separate thread while typesetting.
15
      StreamGobbler.gobble(, line -> {
16
        listener.accept( line );
17
        stdout.add( line );
18
      } SysFile.getFileName( getSourcePath()srcName = SysFile.getFileName( getTargetPath()RemoveTarge, UTF_8@SuppressWarnings( "SpellCheckingInspection" )TEMPORARY_DIRECTORYtoFile( cache )Booleantask = new HostTypesetter.TypesetTask();
19
    return task.typeset();
20
  }
21
}
122
A src/main/java/com/keenwrite/typesetting/PaginationListener.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.typesetting;
3
4
import java.function.Consumimplements Consumer<String>int mPageCount = 1;
5
  private int mPassCount = 1;
6
  private int mPageTotal = 0) {accept( final String line ) {
7
if( page < mPmPassCount++;
8
        mPageTotal = mPageCount;
9
      }
10
11
      mPmPageCount, mPmPageTotal, mPassCount
12
      );
13
    }
14
  }
15
}
116
A src/main/java/com/keenwrite/typesetting/Typesetter.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting;
6
7
import com.keenwrite.util.GenericBuilder;
8
import com.keenwrite.util.Time;
19
10
import java.nio.file.Path;
11
import jaSTR."\{prefix}began"STR."\{prefix}ended.\{suffix}"
A src/main/java/com/keenwrite/typesetting/TypesetterNotFoundException.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.typesetting;
3
4
/**
5
 * Responsible for creating an alternate execution path when a typesetter
6
 * cannot be found.
7
 */
8
public class TypesetterNotFoundException extends RuntimeException {
9
  /**
10
   * Constructs a new exception that indicates the typesetting engine cannot
11
   * be found anywhere along the PATH.
12
   *
13
   * @param name Typesetter executable file name.
14
   */
15
  public TypesetterNotFoundException( final String name ) {
16
    super( name );
17
  }
18
}
119
A src/main/java/com/keenwrite/typesetting/containerization/ContainerManager.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.containerization;
6
7
import com.keenwrite.io.CommandNotFoundException;
8
9
import java.io.File;
10
import java.io.IOException;
11
import java.util.List;
112
13
public
A src/main/java/com/keenwrite/typesetting/containerization/Podman.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.containerization;
6
7
import com.keenwrite.Messages;
8
import com.keenwrite.io.CommandNotFoundException;
9
import com.keenwrite.io.SysFile;
10
11
import java.io.File;
12
import java.nio.file.Files;
13
import java.nio.file.Path;
14
import java.util.LinkedList;
15
import java.util.List;
16
17
import static com.keenwrite.events.StatusEvent.clue;
18
import static com.keenwrite.io.SysFile.toFile;
19
import static com.keenwrite.util.SystemUtils.IS_OS_WINDOWS;
20
import static java.lang.String.format;
21
import static java.lang.String.join;
22
import static java.lang.System.arraycopy;
23
import static java.util.Arrays.copyOf;
24
25
/**
26
 * Provides facilities for interacting with a container environment.
27
 */
28
public final class Podman implements ContainerManager {
29
  private static final String BINARY = "podman";
30
  private static final Path BINARY_PATH =
31
    Path.of(
32
      format( IS_OS_WINDOWS
33
                ? "C:\\Program Files\\RedHat\\Podman\\%s.exe"
34
                : "/usr/bin/%s",
35
              BINARY
36
      )
37
    );
38
  private static final SysFile MANAGER = new SysFile( BINARY );
39
40
  private final List<String> mMountPoints = new LinkedList<>();
41
42
  public Podman() { }
43
44
  /**
45
   * Answers whether the container is installed and runnable on the host.
46
   *
47
   * @return {@code true} if the container is available.
48
   */
49
  public static boolean canRun() {
50
    try {
51
      return toFile( getExecutable() ).isFile();
52
    } catch( final Exception ex ) {
53
      clue( "Wizard.container.executable.run.error", ex );
54
55
      // If the binary couldn't be found, then indicate that it cannot run.
56
      return false;
57
    }
58
  }
59
60
  private static Path getExecutable() {
61
    final var executable = Files.isExecutable( BINARY_PATH );
62
63
    clue( "Wizard.container.executable.run.scan", BINARY_PATH, executable );
64
65
    return executable
66
      ? BINARY_PATH
67
      : MANAGER.locate().orElseThrow();
68
  }
69
70
  @Override
71
  public int install( final File exe ) {
72
    // This monstrosity runs the installer in the background without displaying
73
    // a secondary command window, while blocking until the installer completes
74
    // and an exit code can be determined. I hate Windows.
75
    final var cmd = format(
76
      "start /b /high /wait cmd /c %s /quiet /install & exit ^!errorlevel^!",
77
      exe.getAbsolutePath()
78
    );
79
80
    clue( "Wizard.container.install.command", cmd );
81
82
    final var builder = processBuilder( "cmd", "/c", cmd );
83
84
    try {
85
      clue( "Wizard.container.install.await", cmd );
86
87
      // Wait for installation to finish (successfully or not).
88
      return wait( builder.start() );
89
    } catch( final Exception ignored ) {
90
      return -1;
91
    }
92
  }
93
94
  @Override
95
  public void start( final StreamProcessor processor )
96
    throws CommandNotFoundException {
97
    machine( processor, "stop" );
98
    podman( processor, "system", "prune", "--force" );
99
    machine( processor, "rm", "--force" );
100
    machine( processor, "init" );
101
    machine( processor, "start" );
102
  }
103
104
  @Override
105
  public void load( final StreamProcessor processor )
106
    throws CommandNotFoundException {
107
    final var url = Messages.get( "Wizard.typesetter.container.image.url" );
108
109
    podman( processor, "load", "-i", url );
110
  }
111
112
  /**
113
   * Runs:
114
   * <p>
115
   * <code>podman run --network=host --rm -t IMAGE /bin/sh -lc</code>
116
   * </p>
117
   * followed by the given arguments.
118
   *
119
   * @param args The command and arguments to run against the container.
120
   * @return The exit code from running the container manager (not the
121
   * exit code from running the command).
122
   * @throws CommandNotFoundException Container manager couldn't be found.
123
   */
124
  @Override
125
  public int run(
126
    final StreamProcessor processor,
127
    final String... args ) throws CommandNotFoundException {
128
    final var tag = Messages.get( "Wizard.typesetter.container.image.tag" );
129
130
    final var options = new LinkedList<String>();
131
    options.add( "run" );
132
    options.add( "--rm" );
133
    options.add( "--network=host" );
134
    options.addAll( mMountPoints );
135
    options.add( "-t" );
136
    options.add( tag );
137
    options.add( "/bin/sh" );
138
    options.add( "-lc" );
139
140
    final var command = toArray( toArray( options ), args );
141
    return podman( processor, command );
142
  }
143
144
  /**
145
   * Generates a command-line argument representing a mount point between
146
   * the host and guest systems.
147
   *
148
   * @param hostDir  The host directory to mount in the container.
149
   * @param guestDir The guest directory to map from the container to host.
150
   * @param readonly Set {@code true} to make the mount point read-only.
151
   */
152
  public void mount(
153
    final Path hostDir, final String guestDir, final boolean readonly ) {
154
    assert hostDir != null;
155
    assert guestDir != null;
156
    assert !guestDir.isBlank();
157
    assert toFile( hostDir ).isDirectory();
158
159
    mMountPoints.add(
160
      format( "-v%s:%s:%s", hostDir, guestDir, readonly ? "ro" : "Z" )
161
    );
162
  }
163
164
  private static void machine(
165
    final StreamProcessor processor,
166
    final String... args )
167
    throws CommandNotFoundException {
168
    podman( processor, toArray( "machine", args ) );
169
  }
170
171
  private static int podman(
172
    final StreamProcessor processor, final String... args )
173
    throws CommandNotFoundException {
174
    try {
175
      final var path = getExecutable();
176
      final var joined = join( ",", args );
177
178
      clue( "Wizard.container.process.enter", path, joined );
179
180
      final var builder = processBuilder( path, args );
181
      final var process = builder.start();
182
183
      processor.start( process.getInputStream() );
184
185
      return wait( process );
186
    } catch( final Exception ex ) {
187
      clue( ex );
188
      throw new CommandNotFoundException( MANAGER.toString() );
189
    }
190
  }
191
192
  /**
193
   * Performs a blocking wait until the {@link Process} completes.
194
   *
195
   * @param process The {@link Process} to await completion.
196
   * @return The exit code from running a command.
197
   * @throws InterruptedException The {@link Process} was interrupted.
198
   */
199
  private static int wait( final Process process ) throws InterruptedException {
200
    final var exitCode = process.waitFor();
201
202
    clue( "Wizard.container.process.exit", exitCode );
203
204
    process.destroy();
205
206
    return exitCode;
207
  }
208
209
  private static ProcessBuilder processBuilder( final String... args ) {
210
    final var builder = new ProcessBuilder( args );
211
    builder.redirectErrorStream( true );
212
213
    return builder;
214
  }
215
216
  private static ProcessBuilder processBuilder(
217
    final File file, final String... s ) {
218
    return processBuilder( toArray( file.getAbsolutePath(), s ) );
219
  }
220
221
  private static ProcessBuilder processBuilder(
222
    final Path path, final String... s ) {
223
    return processBuilder( toFile( path ), s );
224
  }
225
226
  /**
227
   * Merges two arrays into a single array.
228
   *
229
   * @param first  The first array to merge before the second array.
230
   * @param second The second array to merge after the first array.
231
   * @param <T>    The type of arrays to merge.
232
   * @return The merged arrays, with the first array elements preceding the
233
   * second array's elements.
234
   */
235
  private static <T> T[] toArray( final T[] first, final T[] second ) {
236
    assert first != null;
237
    assert second != null;
238
    assert first.length > 0;
239
    assert second.length > 0;
240
241
    final var merged = copyOf( first, first.length + second.length );
242
    arraycopy( second, 0, merged, first.length, second.length );
243
    return merged;
244
  }
245
246
  /**
247
   * Convenience method to merge a single string with an array of strings.
248
   *
249
   * @param first  The first item to prepend to the secondary items.
250
   * @param second The second item to combine with the first item.
251
   * @return A new array with the first element at index 0 and the second
252
   * elements starting at index 1.
253
   */
254
  private static String[] toArray( final String first, String... second ) {
255
    assert first != null;
256
    assert second != null;
257
    assert second.length > 0;
258
259
    return toArray( new String[]{first}, second );
260
  }
261
}
1262
A src/main/java/com/keenwrite/typesetting/containerization/StreamProcessor.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.typesetting.containerization;
3
4
import java.io.InputStream;
5
import java.io.PipedInputStream;
6
import java.io.PipedOutputStream;
7
8
/**
9
 * Implementations receive an {@link InputStream} for reading, which happens
10
 * on a separate thread. Implementations are responsible for starting the
11
 * thread. This class helps avoid relying on {@link PipedInputStream} and
12
 * {@link PipedOutputStream} to connect the {@link InputStream} from an
13
 * instance of {@link ProcessBuilder} to process standard output and standard
14
 * error for a running command.
15
 */
16
@FunctionalInterface
17
public interface StreamProcessor {
18
  /**
19
   * Processes the given {@link InputStream} on a separate thread.
20
   */
21
  void start( InputStream in );
22
}
123
A src/main/java/com/keenwrite/typesetting/installer/TypesetterInstaller.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer;
6
7
import com.keenwrite.events.ExportFailedEvent;
8
import com.keenwrite.preferences.AppKeys;
9
import com.keenwrite.preferences.Workspace;
10
import com.keenwrite.typesetting.installer.panes.*;
11
import org.controlsfx.dialog.Wizard;
12
import org.greenrobot.eventbus.Subscribe;
13
14
import java.util.LinkedList;
15
16
import static com.keenwrite.Messages.get;
17
import static com.keenwrite.events.Bus.register;
18
import static com.keenwrite.util.SystemUtils.*;
19
20
/**
21
 * Responsible for installing the typesetting system and all its requirements.
22
 */
23
public final class TypesetterInstaller {
24
  private final Workspace mWorkspace;
25
26
  /**
27
   * Registers for the {@link ExportFailedEvent}, which, when received,
28
   * indicates that the typesetting software must be installed.
29
   *
30
   * @param workspace To set {@link AppKeys#KEY_TYPESET_CONTEXT_THEMES_PATH} via
31
   *                  {@link TypesetterThemesDownloadPane}.
32
   */
33
  public TypesetterInstaller( final Workspace workspace ) {
34
    assert workspace != null;
35
36
    mWorkspace = workspace;
37
38
    register( this );
39
  }
40
41
  @Subscribe
42
  @SuppressWarnings( "unused" )
43
  public void handle( final ExportFailedEvent failedEvent ) {
44
    final var wizard = wizard();
45
46
    wizard.showAndWait();
47
  }
48
49
  private Wizard wizard() {
50
    final var title = get( "Wizard.typesetter.all.1.install.title" );
51
    final var wizard = new Wizard( this, title );
52
    final var wizardFlow = wizardFlow();
53
54
    wizard.setFlow( wizardFlow );
55
56
    return wizard;
57
  }
58
59
  private Wizard.Flow wizardFlow() {
60
    final var panels = wizardPanes();
61
    return new Wizard.LinearFlow( panels );
62
  }
63
64
  private InstallerPane[] wizardPanes() {
65
    final var panes = new LinkedList<InstallerPane>();
66
67
    // STEP 1: Introduction panel (all)
68
    panes.add( new IntroductionPane() );
69
70
    if( IS_OS_WINDOWS ) {
71
      // STEP 2 a: Download container (Windows)
72
      panes.add( new WindowsManagerDownloadPane() );
73
      // STEP 2 b: Install container (Windows)
74
      panes.add( new WindowsManagerInstallPane() );
75
    }
76
    else if( IS_OS_UNIX ) {
77
      // STEP 2: Install container (Unix)
78
      panes.add( new UnixManagerInstallPane() );
79
    }
80
    else {
81
      // STEP 2: Install container (other)
82
      panes.add( new UniversalManagerInstallPane() );
83
    }
84
85
    if( !IS_OS_LINUX ) {
86
      // STEP 3: Initialize container (all except Linux)
87
      panes.add( new ManagerInitializationPane() );
88
    }
89
90
    // STEP 4: Install typesetter container image (all)
91
    panes.add( new TypesetterImageDownloadPane() );
92
93
    // STEP 5: Download and install typesetter themes (all)
94
    panes.add( new TypesetterThemesDownloadPane( mWorkspace ) );
95
96
    return panes.toArray( InstallerPane[]::new );
97
  }
98
}
199
A src/main/java/com/keenwrite/typesetting/installer/panes/AbstractDownloadPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
16
7
import com.keenwrite.io.SysFile;
8
import javafx.collections.ObservableMap;
9
import javafx.concurrSTR."\{getPrefix()}.download.link.url" STR."\{getPrefix()}.paths", mFilename, directorySTR."\{getPrefix()}\{STATUS}.progress", 0, 0STR."\{STATUS}.checksum\{suffix}"STR."\{getPrefix()}.header"STR."\{STATUS}.success"STR."\{STATUS}.failure"
A src/main/java/com/keenwrite/typesetting/installer/panes/InstallerPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
16
7
import com.keenwrite.events.HyperlinkOpenEvent;
8
import com.keenwrite.typesetting.containerizatiTR."\{prefix}.lbl"TR."\{prefix}.url"
A src/main/java/com/keenwrite/typesetting/installer/panes/IntroductionPane.java
1
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panescti/**
6
 * Responsible for informing the user as to what will happen next.
7
 */ionPanePREFIX =
8
extends Installer
A src/main/java/com/keenwrite/typesetting/installer/panes/ManagerInitializationPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
6
7
import com.keenwrite.typesetting.containerization.ContainerManager;
8
9
/**
10
 * Responsible for initializing the container manager on all platforms except
11
 * for Linux.
12
 */
13
public final class ManagerInitializationPane extends ManagerOutputPane {
14
15
  private static final String PREFIX =
16
    "Wizard.typesetter.all.3.install.container";
17
18
  public ManagerInitializationPane() {
19
    super(
20
      PREFIX + ".correct",
21
      PREFIX + ".missing",
22
      ContainerManager::start,
23
      35
24
    );
25
  }
26
27
  @Override
28
  public String getHeaderKey() {
29
    return PREFIX + ".header";
30
  }
31
}
132
A src/main/java/com/keenwrite/typesetting/installer/panes/ManagerOutputPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
16
7
import com.keenwrite.io.CommandNotFoundException;
8
import com.keenwrite.io.downloads.DownloadManfinal static
A src/main/java/com/keenwrite/typesetting/installer/panes/TypesetterImageDownloadPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
6
7
import com.keenwrite.typesetting.containerization.ContainerManager;
8
typesetter's image via the container managerTypesetterImageDownloadPane extends ManagerOutputPane {all.4.download.image";
19
10
  public TypesetterImageDownloadPane() {
11
    super(
12
      PREFIX + ".correct",
13
      PREFIX + ".missing",
14
      ContainerManager::load,
15
      45
16
    
A src/main/java/com/keenwrite/typesetting/installer/panes/TypesetterThemesDownloadPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
6
7
import com.keenwrite.io.UserDataDir;
8
import com.keenwrite.io.Zip;
9
import com.keenwrite.preferences.Workspace;
10
import javafx.collections.ObservableMap;
11
import org.controlsfx.dialog.Wizard;
12
13
import java.io.File;
14
import java.io.IOException;
15
16
import static com.keenwrite.Messages.get;
17
import static com.keenwrite.events.StatusEvent.clue;
18
import static com.keenwrite.io.SysFile.toFile;
19
import static com.keenwrite.preferences.AppKeys.KEY_TYPESET_CONTEXT_THEMES_PATH;
20
21
/**
22
 * Responsible for downloading themes into the application's data directory.
23
 * The data directory differs between platforms, which is handled
24
 * transparently by the {@link UserDataDir} class.
25
 */
26
public class TypesetterThemesDownloadPane extends AbstractDownloadPane {
27
  private static final String PREFIX =
28
    "Wizard.typesetter.all.5.download.themes";
29
30
  private final Workspace mWorkspace;
31
32
  public TypesetterThemesDownloadPane( final Workspace workspace ) {
33
    assert workspace != null;
34
    mWorkspace = workspace;
35
  }
36
37
  @Override
38
  public void onEnteringPage( final Wizard wizard ) {
39
    // Delete the target themes file to force re-download so that unzipping
40
    // the file takes place. This side-steps checksum validation, which would
41
    // be best implemented after downloading.
42
    deleteTarget();
43
    super.onEnteringPage( wizard );
44
  }
45
46
  @Override
47
  protected void onDownloadSucceeded(
48
    final String threadName, final ObservableMap<Object, Object> properties ) {
49
    super.onDownloadSucceeded( threadName, properties );
50
51
    try {
52
      process( getTarget() );
53
    } catch( final Exception ex ) {
54
      clue( ex );
55
    }
56
  }
57
58
  private void process( final File target ) throws IOException {
59
    Zip.extract( target.toPath() );
60
61
    // Replace the default themes directory with the downloaded version.
62
    final var root = toFile( Zip.root( target.toPath() ) );
63
64
    // Make sure the typesetter will know where to find the themes.
65
    mWorkspace.fileProperty( KEY_TYPESET_CONTEXT_THEMES_PATH ).set( root );
66
    mWorkspace.save();
67
68
    // The themes pack is no longer needed.
69
    deleteTarget();
70
  }
71
72
  @Override
73
  protected String getPrefix() {
74
    return PREFIX;
75
  }
76
77
  @Override
78
  protected String getChecksum() {
79
    return get( "Wizard.typesetter.themes.checksum" );
80
  }
81
}
182
A src/main/java/com/keenwrite/typesetting/installer/panes/UniversalManagerInstallPane.java
1
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
6
u/**
7
 * Responsible for installing the container manager for any operating system
8
 * that was not explicitly detected.
9
 */
10
publil
A src/main/java/com/keenwrite/typesetting/installer/panes/UnixManagerInstallPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
16
7
import com.keenwrite.ui.clipboard.SystemClipboard;
8
import javafx.geometry.Insets;
9
import javafxSTR."\{PREFIX}.copy.began"STR."\{PREFIX}.copy.ended"STR."\{PREFIX}.header"STR."\{PREFIX}.command"STR."\{prefix}.distros"STR."\{prefix}.os.name\{suffix}"STR."\{prefix}.os.text\{suffix}"
A src/main/java/com/keenwrite/typesetting/installer/panes/WindowsManagerDownloadPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
6
7
import typesetting.installer.panes.WindowsManagerInstallPane.WIN_BIN container manager software on Windows.
8
 */
9
public final class WindowsManagerwin.2.download.container";void updateProperties(
10
   properties.put( WIN_BIN, getTarget() container.checksum" );
11
  }
12
}
113
A src/main/java/com/keenwrite/typesetting/installer/panes/WindowsManagerInstallPane.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.typesetting.installer.panes;
6
7
import com.keenwrite.typesetting.containerization.ContainerManager;
8
import javafx.scene.controlTR."\{PREFIX}.status.running"TR."\{PREFIX}.step.0"TR."\{PREFIX}.step.1"TR."\{PREFIX}.step.2"TR."\{PREFIX}.step.3"TR."\{PREFIX}.status"TR."\{key}.success" )
9
          : get( STR."\{key}.failure"TR."\{PREFIX}.unknown"TR."\{PREFIX}.header";
10
  }
11
}
112
A src/main/java/com/keenwrite/ui/actions/Action.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.actions;
3
4
import com.keenwrite.Messages;
5
import com.keenwrite.util.GenericBuilder;
6
import com.keenwrite.constants.Constants.ACTION_PREFIX;
7
import static com.keenwrite.ui.fonts.IconFactory.createGraphicfinalStringSTR."\{ACTION_PREFIX}\{id}.";
8
      final var text = STR."\{prefix}STR."\{prefix}STR."\{prefix}/// If there's no icon associated with the icon key name, don't attempt
9
      // to create a graphic for the icon, because it won't exist.
10
      final var iconNam      mIcon = iconKey.equals( iconName ) ? "" : iconName;
11
private static Button createIconButton( final String icon ) {
12
    return new Button( null, createGraphic( icon ) );
13
  }
14
15
  publfinal String text,
16
final String icon,
17
createGraphic mIcon // Do not display mnemonic accelerator character in tooltip text.
18
    // The accelerator key will still be available, this is display-only.
19
    tooltip = tooltip.replace( "_", "" );STR." (\{}\{')'}"}
120
A src/main/java/com/keenwrite/ui/actions/ApplicationBars.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.actions;
6
7
import com.keenwrite.ui.controls.EventedStatusBar;
8
import javafx.event.ActionEvent;
9
import javafx.event.EventHandler;
10
import javafx.scene.Node;
11
import javafx.scene.control.Menu;
12
import javafx.scene.control.MenuBar;
13
import javafx.scene.control.MenuItem;
14
import javafx.scene.control.ToolBar;
15
import org.controlsfx.control.StatusBar;
16
import org.jetbrains.annotations.NotNull;
17
18
import java.util.HashMap;
19
import java.util.Map;
20
21
import static com.keenwrite.Messages.get;
22
23
/**
24
 * Responsible for wiring all application actions to menus, toolbar buttons,
25
 * and keyboard shortcuts.
26
 */
27
public final class ApplicationBars {
28
29
  private static final Map<String, Action> sMap = new HashMap<>( 64 );
30
31
  /**
32
   * Empty constructor.
33
   */
34
  public ApplicationBars() {
35
  }
36
37
  /**
38
   * Creates the main application affordances.
39
   *
40
   * @param actions The {@link GuiCommands} that map user interface
41
   *                selections to executable code.
42
   * @return An instance of {@link MenuBar} that contains the menu.
43
   */
44
  public static MenuBar createMenuBar( final GuiCommands actions ) {
45
    final var SEPARATOR = new SeparatorAction();
46
47
    return new MenuBar(
48
      createMenuFile( actions, SEPARATOR ),
49
      createMenuEdit( actions, SEPARATOR ),
50
      createMenuFormat( actions ),
51
      createMenuInsert( actions, SEPARATOR ),
52
      createMenuVariable( actions, SEPARATOR ),
53
      createMenuView( actions, SEPARATOR ),
54
      createMenuHelp( actions )
55
    );
56
  }
57
58
  @NotNull
59
  private static Menu createMenuFile(
60
    final GuiCommands actions, final SeparatorAction SEPARATOR ) {
61
    // @formatter:off
62
    return createMenu(
63
      get( "Main.menu.file" ),
64
      addAction( "file.new", _ -> actions.file_new() ),
65
      addAction( "file.open", _ -> actions.file_open() ),
66
      addAction( "file.open_url", _ -> actions.file_open_url() ),
67
      SEPARATOR,
68
      addAction( "file.close", _ -> actions.file_close() ),
69
      addAction( "file.close_all", _ -> actions.file_close_all() ),
70
      SEPARATOR,
71
      addAction( "file.save", _ -> actions.file_save() ),
72
      addAction( "file.save_as", _ -> actions.file_save_as() ),
73
      addAction( "file.save_all", _ -> actions.file_save_all() ),
74
      SEPARATOR,
75
      addAction( "file.export", _ -> { } )
76
        .addSubActions(
77
          addAction( "file.export.pdf", _ -> actions.file_export_pdf() ),
78
          addAction( "file.export.pdf.dir", _ -> actions.file_export_pdf_dir() ),
79
          addAction( "file.export.pdf.repeat", _ -> actions.file_export_repeat() ),
80
          addAction( "file.export.html.dir", _ -> actions.file_export_html_dir() ),
81
          addAction( "file.export.text_tex.dir", _ -> actions.file_export_text_tex_dir() ),
82
          addAction( "file.export.html_svg", _ -> actions.file_export_html_svg() ),
83
          addAction( "file.export.html_tex", _ -> actions.file_export_html_tex() ),
84
          addAction( "file.export.text_tex", _ -> actions.file_export_text_tex() ),
85
          addAction( "file.export.xhtml_tex", _ -> actions.file_export_xhtml_tex() )
86
        ),
87
      SEPARATOR,
88
      addAction( "file.exit", _ -> actions.file_exit() )
89
    );
90
    // @formatter:on
91
  }
92
93
  @NotNull
94
  private static Menu createMenuEdit(
95
    final GuiCommands actions, final SeparatorAction SEPARATOR ) {
96
    return createMenu(
97
      get( "Main.menu.edit" ),
98
      SEPARATOR,
99
      addAction( "edit.undo", _ -> actions.edit_undo() ),
100
      addAction( "edit.redo", _ -> actions.edit_redo() ),
101
      SEPARATOR,
102
      addAction( "edit.cut", _ -> actions.edit_cut() ),
103
      addAction( "edit.copy", _ -> actions.edit_copy() ),
104
      addAction( "edit.paste", _ -> actions.edit_paste() ),
105
      addAction( "edit.select_all", _ -> actions.edit_select_all() ),
106
      SEPARATOR,
107
      addAction( "edit.find", _ -> actions.edit_find() ),
108
      addAction( "edit.find_next", _ -> actions.edit_find_next() ),
109
      addAction( "edit.find_prev", _ -> actions.edit_find_prev() ),
110
      SEPARATOR,
111
      addAction( "edit.preferences", _ -> actions.edit_preferences() )
112
    );
113
  }
114
115
  @NotNull
116
  private static Menu createMenuFormat( final GuiCommands actions ) {
117
    return createMenu(
118
      get( "Main.menu.format" ),
119
      addAction( "format.bold", _ -> actions.format_bold() ),
120
      addAction( "format.italic", _ -> actions.format_italic() ),
121
      addAction( "format.monospace", _ -> actions.format_monospace() ),
122
      addAction( "format.superscript", _ -> actions.format_superscript() ),
123
      addAction( "format.subscript", _ -> actions.format_subscript() ),
124
      addAction( "format.strikethrough", _ -> actions.format_strikethrough() )
125
    );
126
  }
127
128
  @NotNull
129
  private static Menu createMenuInsert(
130
    final GuiCommands actions,
131
    final SeparatorAction SEPARATOR ) {
132
    // @formatter:off
133
    return createMenu(
134
      get( "Main.menu.insert" ),
135
      addAction( "insert.blockquote", _ -> actions.insert_blockquote() ),
136
      addAction( "insert.code", _ -> actions.insert_code() ),
137
      addAction( "insert.fenced_code_block", _ -> actions.insert_fenced_code_block() ),
138
      SEPARATOR,
139
      addAction( "insert.link", _ -> actions.insert_link() ),
140
      addAction( "insert.image", _ -> actions.insert_image() ),
141
      SEPARATOR,
142
      addAction( "insert.heading_1", _ -> actions.insert_heading_1() ),
143
      addAction( "insert.heading_2", _ -> actions.insert_heading_2() ),
144
      addAction( "insert.heading_3", _ -> actions.insert_heading_3() ),
145
      SEPARATOR,
146
      addAction( "insert.unordered_list", _ -> actions.insert_unordered_list() ),
147
      addAction( "insert.ordered_list", _ -> actions.insert_ordered_list() ),
148
      addAction( "insert.horizontal_rule", _ -> actions.insert_horizontal_rule() )
149
    );
150
    // @formatter:on
151
  }
152
153
  @NotNull
154
  private static Menu createMenuVariable(
155
    final GuiCommands actions, final SeparatorAction SEPARATOR ) {
156
    return createMenu(
157
      get( "Main.menu.definition" ),
158
      addAction( "definition.insert", _ -> actions.definition_autoinsert() ),
159
      SEPARATOR,
160
      addAction( "definition.create", _ -> actions.definition_create() ),
161
      addAction( "definition.rename", _ -> actions.definition_rename() ),
162
      addAction( "definition.delete", _ -> actions.definition_delete() )
163
    );
164
  }
165
166
  @NotNull
167
  private static Menu createMenuView(
168
    final GuiCommands actions, final SeparatorAction SEPARATOR ) {
169
    return createMenu(
170
      get( "Main.menu.view" ),
171
      addAction( "view.refresh", _ -> actions.view_refresh() ),
172
      SEPARATOR,
173
      addAction( "view.preview", _ -> actions.view_preview() ),
174
      addAction( "view.outline", _ -> actions.view_outline() ),
175
      addAction( "view.statistics", _ -> actions.view_statistics() ),
176
      addAction( "view.files", _ -> actions.view_files() ),
177
      SEPARATOR,
178
      addAction( "view.menubar", _ -> actions.view_menubar() ),
179
      addAction( "view.toolbar", _ -> actions.view_toolbar() ),
180
      addAction( "view.statusbar", _ -> actions.view_statusbar() ),
181
      SEPARATOR,
182
      addAction( "view.log", _ -> actions.view_log() )
183
    );
184
  }
185
186
  @NotNull
187
  private static Menu createMenuHelp( final GuiCommands actions ) {
188
    return createMenu(
189
      get( "Main.menu.help" ),
190
      addAction( "help.about", _ -> actions.help_about() )
191
    );
192
  }
193
194
  public static Node createToolBar() {
195
    final var SEPARATOR = new SeparatorAction();
196
197
    return createToolBar(
198
      getAction( "file.new" ),
199
      getAction( "file.open" ),
200
      getAction( "file.save" ),
201
      SEPARATOR,
202
      getAction( "file.export.pdf" ),
203
      SEPARATOR,
204
      getAction( "edit.undo" ),
205
      getAction( "edit.redo" ),
206
      getAction( "edit.cut" ),
207
      getAction( "edit.copy" ),
208
      getAction( "edit.paste" ),
209
      SEPARATOR,
210
      getAction( "format.bold" ),
211
      getAction( "format.italic" ),
212
      getAction( "format.superscript" ),
213
      getAction( "format.subscript" ),
214
      getAction( "insert.blockquote" ),
215
      getAction( "insert.code" ),
216
      getAction( "insert.fenced_code_block" ),
217
      SEPARATOR,
218
      getAction( "insert.link" ),
219
      getAction( "insert.image" ),
220
      SEPARATOR,
221
      getAction( "insert.heading_1" ),
222
      SEPARATOR,
223
      getAction( "insert.unordered_list" ),
224
      getAction( "insert.ordered_list" )
225
    );
226
  }
227
228
  public static StatusBar createStatusBar() {
229
    return new EventedStatusBar();
230
  }
231
232
  /**
233
   * Adds a new action to the list of actions.
234
   *
235
   * @param key     The name of the action to register in {@link #sMap}.
236
   * @param handler Performs the action upon request.
237
   * @return The newly registered action.
238
   */
239
  private static Action addAction(
240
    final String key, final EventHandler<ActionEvent> handler ) {
241
    assert key != null;
242
    assert handler != null;
243
244
    final var action = Action
245
      .builder()
246
      .setId( key )
247
      .setHandler( handler )
248
      .build();
249
250
    sMap.put( key, action );
251
252
    return action;
253
  }
254
255
  private static Action getAction( final String key ) {
256
    return sMap.get( key );
257
  }
258
259
  public static Menu createMenu(
260
    final String text, final MenuAction... actions ) {
261
    return new Menu( text, null, createMenuItems( actions ) );
262
  }
263
264
  public static MenuItem[] createMenuItems( final MenuAction... actions ) {
265
    final var menuItems = new MenuItem[ actions.length ];
266
267
    for( var i = 0; i < actions.length; i++ ) {
268
      menuItems[ i ] = actions[ i ].createMenuItem();
269
    }
270
271
    return menuItems;
272
  }
273
274
  private static ToolBar createToolBar( final MenuAction... actions ) {
275
    return new ToolBar( createToolBarButtons( actions ) );
276
  }
277
278
  private static Node[] createToolBarButtons( final MenuAction... actions ) {
279
    final var len = actions.length;
280
    final var nodes = new Node[ len ];
281
282
    for( var i = 0; i < len; i++ ) {
283
      nodes[ i ] = actions[ i ].createToolBarNode();
284
    }
285
286
    return nodes;
287
  }
288
}
1289
A src/main/java/com/keenwrite/ui/actions/GuiCommands.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.actions;
16
7
import com.keenwrite.ExportFormat;
8
import com.keenwrite.MainPane;
9
import com.keenwrite.MainScene;
10
import com.keeTR."\{prefix}title"TR."\{prefix}header"TR."\{prefix}content"
A src/main/java/com/keenwrite/ui/actions/Keyboard.java
1
package com.keenwrite.ui.actions;
2
3
import javafx.scene.input.KeyCodeCombination;
4
import javafx.scene.input.KeyEvent;
5
6
import static javafx.scene.input.KeyCode.C;
7
import static javafx.scene.input.KeyCode.INSERT;
8
import static javafx.scene.input.KeyCombination.CONTROL_ANY;
9
10
public class Keyboard {
11
  public static final KeyCodeCombination CTRL_C =
12
    new KeyCodeCombination( C, CONTROL_ANY );
13
  public static final KeyCodeCombination CTRL_INSERT =
14
    new KeyCodeCombination( INSERT, CONTROL_ANY );
15
16
  /**
17
   * Answers whether the user issued a copy request via the keyboard.
18
   *
19
   * @param event The keyboard event to examine.
20
   * @return {@code true} if the user pressed Ctrl+C or Ctrl+Insert.
21
   */
22
  public static boolean isCopy( final KeyEvent event ) {
23
    return CTRL_C.match( event ) || CTRL_INSERT.match( event );
24
  }
25
}
126
A src/main/java/com/keenwrite/ui/actions/MenuAction.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.
A src/main/java/com/keenwrite/ui/actions/SeparatorAction.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.actions;
3
4
import javafx.scene.Node;
5
import javafx.scene.control.*;
16
7
/**
8
 * Represents a {@link MenuBar} or {@link ToolBar} action that has no
9
 * operatiofinal
A src/main/java/com/keenwrite/ui/actions/package-info.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
3
/**
4
 * This package contains classes that define commands as executable actions.
5
 */
6
package com.keenwrite.ui.actions;
17
A src/main/java/com/keenwrite/ui/adapters/DocumentAdapter.java
1
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved.events.StatusEvent
A src/main/java/com/keenwrite/ui/adapters/ReplacedElementAdapter.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.adapters;
3
4
import org.w3c.dom.Element;
5
import org.xhtmlrenderer.extend.ReplacedElementFactory;
6
import org.xhtmlrenderer.simple.extend.FormSubmissionListener;
7
8
/**
9
 * Allows subclasses to implement only specific events of interest.
10
 */
11
public abstract class ReplacedElementAdapter implements ReplacedElementFactory {
12
  @Override
13
  public void reset() {
14
  }
15
16
  @Override
17
  public void remove( final Element e ) {
18
  }
19
20
  @Override
21
  public void setFormSubmissionListener(
22
      final FormSubmissionListener listener ) {
23
  }
24
}
125
A src/main/java/com/keenwrite/ui/cells/AltTableCell.java
1
package com.keenwrite.ui.cells;
12
3
import javafx.scene.control.cell.TextFieldTableCell;
4
import javafx.uti
A src/main/java/com/keenwrite/ui/cells/AltTreeCell.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.cells;
13
A src/main/java/com/keenwrite/ui/cells/CellEditor.java
1
package com.keenwrite.ui.cells;
2
3
import javafx.beans.property.ObjectProperty;
4
import javafx.beans.property.Property;
5
import javafx.beans.property.SimpleStringProperty;
6
import javafx.beans.value.ChangeListener;
7
import javafx.beans.value.ObservableValue;
8
import javafx.event.EventHandler;
9
import javafx.scene.Node;
10
import javafx.scene.control.TableCell;
11
import javafx.scene.control.TextField;
12
import javafx.scene.control.TreeCell;
13
import javafx.scene.input.KeyEvent;
14
15
import java.util.function.Consumer;
16
17
import static javafx.application.Platform.runLater;
18
import static javafx.scene.input.KeyCode.ENTER;
19
import static javafx.scene.input.KeyCode.TAB;
20
import static javafx.scene.input.KeyEvent.KEY_RELEASED;
21
22
public class CellEditor {
23
  private FocusListener mFocusListener;
24
  private final KeyHandler mKeyHandler = new KeyHandler();
25
  private final Property<String> mInputText = new SimpleStringProperty();
26
  private final Consumer<String> mConsumer;
27
28
  /**
29
   * Responsible for accepting the text when users press the Enter or Tab key.
30
   */
31
  private class KeyHandler implements EventHandler<KeyEvent> {
32
    @Override
33
    public void handle( final KeyEvent event ) {
34
      if( event.getCode() == ENTER || event.getCode() == TAB ) {
35
        commitEdit();
36
        event.consume();
37
      }
38
    }
39
  }
40
41
  /**
42
   * Responsible for committing edits when focus is lost. This will also
43
   * deselect the input field when focus is gained so that typing text won't
44
   * overwrite the entire existing text.
45
   */
46
  private class FocusListener implements ChangeListener<Boolean> {
47
    private final TextField mInput;
48
49
    private FocusListener( final TextField input ) {
50
      mInput = input;
51
    }
52
53
    @Override
54
    public void changed(
55
      final ObservableValue<? extends Boolean> c,
56
      final Boolean endedFocus, final Boolean beganFocus ) {
57
58
      if( beganFocus ) {
59
        runLater( mInput::deselect );
60
      }
61
      else if( endedFocus ) {
62
        commitEdit();
63
      }
64
    }
65
  }
66
67
  /**
68
   * Generalized cell editor suitable for use with {@link TableCell} or
69
   * {@link TreeCell} instances.
70
   *
71
   * @param consumer        Converts the field input text to the required
72
   *                        data type.
73
   * @param graphicProperty Defines the graphical user input field.
74
   */
75
  public CellEditor(
76
    final Consumer<String> consumer,
77
    final ObjectProperty<Node> graphicProperty ) {
78
    assert consumer != null;
79
    mConsumer = consumer;
80
81
    init( graphicProperty );
82
  }
83
84
  private void init( final ObjectProperty<Node> graphicProperty ) {
85
    // When the text field is added as the graphics context, we hook into
86
    // the changed value to get a handle on the text field. From there it is
87
    // possible to add change the keyboard and focus behaviours.
88
    graphicProperty.addListener( ( c, o, n ) -> {
89
      if( o instanceof TextField ) {
90
        o.removeEventHandler( KEY_RELEASED, mKeyHandler );
91
        o.focusedProperty().removeListener( mFocusListener );
92
      }
93
94
      if( n instanceof final TextField input ) {
95
        n.addEventFilter( KEY_RELEASED, mKeyHandler );
96
        mInputText.bind( input.textProperty() );
97
        mFocusListener = new FocusListener( input );
98
        n.focusedProperty().addListener( mFocusListener );
99
      }
100
    } );
101
  }
102
103
  private void commitEdit() {
104
    mConsumer.accept( mInputText.getValue() );
105
  }
106
}
1107
A src/main/java/com/keenwrite/ui/clipboard/SystemClipboard.java
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
}
175
A src/main/java/com/keenwrite/ui/controls/BrowseButton.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.controls;
6
7
import com.keenwrite.Messages;
8
import javafx.event.ActionEvent;
9
import javafx.scene.control.Button;
10
import javafx.stage.DirectoryChooser;
11
12
import java.io.File;
13
import java.util.function.Consumer;
14
15
import static com.keenwrite.ui.fonts.IconFactory.createGraphic;
16
import static org.controlsfx.glyphfont.FontAwesome.Glyph.FILE_ALT;
17
18
/**
19
 * Responsible for browsing directories.
20
 */
21
public class BrowseButton extends Button {
22
  /**
23
   * Initial directory.
24
   */
25
  private final File mDirectory;
26
27
  /**
28
   * Called when the user accepts a directory.
29
   */
30
  private final Consumer<File> mConsumer;
31
32
  public BrowseButton( final File directory, final Consumer<File> consumer ) {
33
    assert directory != null;
34
    assert consumer != null;
35
36
    mDirectory = directory;
37
    mConsumer = consumer;
38
39
    setGraphic( createGraphic( FILE_ALT ) );
40
    setOnAction( this::browse );
41
  }
42
43
  public void browse( final ActionEvent ignored ) {
44
    final var chooser = new DirectoryChooser();
45
    chooser.setTitle( Messages.get( "BrowseDirectoryButton.chooser.title" ) );
46
    chooser.setInitialDirectory( mDirectory );
47
48
    final var result = chooser.showDialog( getScene().getWindow() );
49
50
    if( result != null ) {
51
      mConsumer.accept( result );
52
    }
53
  }
54
}
155
A src/main/java/com/keenwrite/ui/controls/EventedStatusBar.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.controls;
3
4
import com.keenwrite.events.StatusEvent;
5
import org.controlsfx.control.StatusBar;
6
import org.greenrobot.eventbus.Subscribe;
17
8
import static comtoString
A src/main/java/com/keenwrite/ui/controls/SearchBar.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.controls;
3
4
import javafx.beansstaticstaticstaticstatic
5
                                        }
16
A src/main/java/com/keenwrite/ui/dialogs/AbstractDialog.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.dialogs;
6
7
import com.keenwrite.service.events.impl.ButtonOrderPane;
8
import javafx.scene.control.Dialog;
9
import javafx.stage.Stage;
10
import javafx.stage.Window;
11
12
import static com.keenwrite.constants.GraphicsConstants.ICON_DIALOG;
13
import static com.keenwrite.Messages.get;
14
import static com.keenwrite.util.Strings.validate;
15
import static javafx.scene.control.ButtonType.CANCEL;
16
import static javafx.scene.control.ButtonType.OK;
17
18
/**
19
 * Superclass that abstracts common behaviours for all dialogs.
20
 *
21
 * @param <T> The type of dialog to create (usually String).
22
 */
23
public abstract class AbstractDialog<T> extends Dialog<T> {
24
25
  /**
26
   * Ensures that all dialogs can be closed.
27
   *
28
   * @param owner The parent window of this dialog.
29
   * @param title The messages title to display in the title bar.
30
   */
31
  public AbstractDialog( final Window owner, final String title ) {
32
    assert owner != null;
33
    assert validate( title );
34
35
    setTitle( get( title ) );
36
    setResizable( true );
37
38
    initOwner( owner );
39
    initCloseAction();
40
    initDialogPane();
41
    initDialogButtons();
42
    initComponents();
43
44
    if( owner instanceof Stage stage ) {
45
      initIcon( stage );
46
    }
47
  }
48
49
  /**
50
   * Initialize the component layout.
51
   */
52
  protected abstract void initComponents();
53
54
  /**
55
   * Set the dialog to use a button order pane with an OK and a CANCEL button.
56
   */
57
  protected void initDialogPane() {
58
    setDialogPane( new ButtonOrderPane() );
59
  }
60
61
  /**
62
   * Set an OK and CANCEL button on the dialog.
63
   */
64
  protected void initDialogButtons() {
65
    getDialogPane().getButtonTypes().addAll( OK, CANCEL );
66
  }
67
68
  /**
69
   * Attaches a close request to the dialog's [X] button so that the user
70
   * can always close the window, even if there's an error.
71
   */
72
  protected final void initCloseAction() {
73
    final var window = getDialogPane().getScene().getWindow();
74
    window.setOnCloseRequest( _ -> window.hide() );
75
  }
76
77
  private void initIcon( final Stage owner ) {
78
    owner.getIcons().add( ICON_DIALOG );
79
  }
80
}
181
A src/main/java/com/keenwrite/ui/dialogs/CustomDialog.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.dialogs;
6
7
import com.keenwrite.Messages;
8
import com.keenwrite.service.events.impl.ButtonOrderPane;
9
import javafx.application.Platform;
10
import javafx.beans.value.ChangeListener;
11
import javafx.geometry.Insets;
12
import javafx.scene.control.ButtonBar.ButtonData;
13
import javafx.scene.control.Dialog;
14
import javafx.scene.control.Label;
15
import javafx.scene.control.TextField;
16
import javafx.scene.layout.ColumnConstraints;
17
import javafx.scene.layout.GridPane;
18
import javafx.stage.Window;
19
20
import java.util.LinkedList;
21
import java.util.List;
22
23
import static com.keenwrite.Messages.get;
24
import static com.keenwrite.util.Strings.validate;
25
import static javafx.scene.control.ButtonType.CANCEL;
26
import static javafx.scene.control.ButtonType.OK;
27
import static javafx.scene.layout.Priority.ALWAYS;
28
import static javafx.scene.layout.Priority.NEVER;
29
30
/**
31
 * TODO: This class could be combined with {@link AbstractDialog}, either
32
 *   directly or through inheritance.
33
 *
34
 * @param <T> The type of data returned from the dialog upon acceptance.
35
 */
36
public abstract class CustomDialog<T> extends Dialog<T> {
37
  private final GridPane mContentPane = new GridPane( 10, 10 );
38
  private final List<TextField> mInputFields = new LinkedList<>();
39
40
  public CustomDialog( final Window owner, final String title ) {
41
    assert owner != null;
42
    assert validate( title );
43
44
    initOwner( owner );
45
    setTitle( get( title ) );
46
    setResizable( true );
47
  }
48
49
  /**
50
   * Allows for late binding so that input fields can be populated after
51
   * the constructor is called.
52
   */
53
  protected void initialize() {
54
    initDialogPane();
55
    initDialogButtons();
56
    initInputFields();
57
    initContentPane();
58
59
    assert !mInputFields.isEmpty();
60
61
    final var first = mInputFields.getFirst();
62
    assert first != null;
63
64
    Platform.runLater( first::requestFocus );
65
66
    setResultConverter( button -> {
67
      final ButtonData data = button == null ? null : button.getButtonData();
68
      return data == ButtonData.OK_DONE ? handleAccept() : null;
69
    } );
70
  }
71
72
  /**
73
   * Invoked when the user selects the OK button to confirm the input values.
74
   *
75
   * @return The type of data provided by using the dialog.
76
   */
77
  protected abstract T handleAccept();
78
79
  /**
80
   * Subclasses must call this method at least once.
81
   *
82
   * @param id     The unique identifier for the input field.
83
   * @param label  The input field's label property key.
84
   * @param prompt The prompt property key, which provides context.
85
   * @param value  The initial value to provide for the field.
86
   * @see Messages#get(String)
87
   */
88
  protected void addInputField(
89
    final String id,
90
    final String label,
91
    final String prompt,
92
    final String value,
93
    final ChangeListener<String> listener ) {
94
    assert validate( id );
95
    assert validate( label );
96
    assert validate( prompt );
97
    assert validate( value );
98
99
    final int row = mInputFields.size();
100
    final Label fieldLabel = new Label( get( label ) );
101
    final TextField fieldInput = new TextField();
102
103
    fieldInput.setPromptText( get( prompt ) );
104
    fieldInput.setId( id );
105
    fieldInput.textProperty().addListener( listener );
106
    fieldInput.setText( value );
107
108
    mContentPane.add( fieldLabel, 0, row );
109
    mContentPane.add( fieldInput, 1, row );
110
    mInputFields.add( fieldInput );
111
  }
112
113
  /**
114
   * Subclasses must add at least one input field.
115
   */
116
  protected abstract void initInputFields();
117
118
  /**
119
   * Set the dialog to use a button order pane with an OK and a CANCEL button.
120
   */
121
  protected void initDialogPane() {
122
    setDialogPane( new ButtonOrderPane() );
123
  }
124
125
  /**
126
   * Set an OK and CANCEL button on the dialog.
127
   */
128
  protected void initDialogButtons() {
129
    getDialogPane().getButtonTypes().addAll( OK, CANCEL );
130
  }
131
132
  /**
133
   * Called after the input fields have been added. This adds the input
134
   * fields to the main dialog pane.
135
   */
136
  protected void initContentPane() {
137
    mContentPane.setPadding( new Insets( 20, 10, 10, 10 ) );
138
139
    final var cc1 = new ColumnConstraints();
140
    final var cc2 = new ColumnConstraints();
141
142
    cc1.setHgrow( NEVER );
143
    cc2.setHgrow( ALWAYS );
144
    cc2.setMinWidth( 250 );
145
    mContentPane.getColumnConstraints().addAll( cc1, cc2 );
146
147
    getDialogPane().setContent( mContentPane );
148
  }
149
}
1150
A src/main/java/com/keenwrite/ui/dialogs/ExportDialog.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.dialogs;
16
7
import com.keenwrite.events.ExportFailedEvent;
8
import com.keenwrite.io.SysFile;
9
import com.keenwrite.util.DiacriTR."\{get( key )}:"
A src/main/java/com/keenwrite/ui/dialogs/ExportSettings.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.dialogs;
6
7
import com.keenwrite.util.GenericBuilder;
8
import javafx.beans.property.StringProperty;
19
10
/**
11
 * Provides export settings such as the selected theme a
A src/main/java/com/keenwrite/ui/dialogs/HyperlinkDialog.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.dialogs;
6
7
import com.keenwrite.ui.models.HyperlinkModel;
8
import javafx.stage.Window;
19
10
/**
11
 * Dialog to insert or edit a MaTR."\{PREFIX}title"TR."\{PREFIX}label.text", STR."\{PREFIX}prompt.text"TR."\{PREFIX}label.url", STR."\{PREFIX}prompt.url"TR."\{PREFIX}label.title", STR."\{PREFIX}prompt.title"
A src/main/java/com/keenwrite/ui/dialogs/ImageDialog.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.dialogs;
6
7
import com.keenwrite.ui.models.ImageModel;
8
import javafx.stage.Window;
9
10
/**
11
 * Dialog to enter a MaimageImageimage.";
12
13
  private final ImageModel mModel;
114
15
  public ImageImage
A src/main/java/com/keenwrite/ui/dialogs/OpenUrlDialog.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.dialogs;
6
7
import com.keenwrite.events.FileOpenEvent;
8
import javafx.stage.Window;
19
10
import java.io.File;
11
import java.net.URITR."\{DOWNLOAD}status."TR."\{PREFIX}title"TR."\{PREFIX}label.url", STR."\{PREFIX}prompt.url"TR."\{DOWNLOAD}fetch"TR."\{DOWNLOAD}exists"TR."\{STATUS}\{suffix}"TR."\{DOWNLOAD}success" STR."\{DOWNLOAD}failure", uri )
A src/main/java/com/keenwrite/ui/explorer/FilePicker.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.explorer;
3
4
import java.io.File;
5
import java.nio.file.Path;
6
import java.util.List;
7
import java.util.Optional;
18
9
/**
10
 * Responsible for providing the user w;
A src/main/java/com/keenwrite/ui/explorer/FilePickerFactory.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.explorer;
13
4
import com.keenwrite.Messages;
5
import com.keenwrite.Workspace;
6
import javafx.beans.mDirectory.get().toPath()
A src/main/java/com/keenwrite/ui/explorer/FilesView.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.explorer;
13
4
import com.keenwrite.events.FileOpenEvent;
5
import com.keenwrite.io.SysFile;
6
import com.keenwrite.ui.controls.BrowseButton;
7
import javafx.beansio.SysFile.toFiltoFile( path toFile( path.normalize() ) );toFile( path 
A src/main/java/com/keenwrite/ui/fonts/IconFactory.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.fonts;
13
4
import com.keenwrite.io.MediaType;
5
import com.keenwrite.io.MediaTypeExtension;
6
import com.keenwrite.io.SysFile;
7
import javafx.scene.Node;
8
import fromFilename
A src/main/java/com/keenwrite/ui/heuristics/DocumentStatistics.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.heuristics;
3
4
import com.keenwrite.events.DocumentChangedEvent;
5
import com.keenwrite.;
6
import com.keenwrite.preferences.Workspace;
7
import com.keenwrite.ui.actions.Keyboard;
8
import com.keenwrite.ui.clipboard.SystemClipboardkeenAppAppui.heuristics.DocumentStatistics.StatEntrySelectionMode.MULTIPLE extends TableView<StatEntry> {
9
10
  private WordCounter mWordCounter;mWordCounter = WordCounter.create( workspace.getLocale for rendering, the parsed
11
   * If that text differs in its hash code
12
   * implication
13
   * replaced.
14
   * outsidefinal var document = event.getDocument();
15
        final var wordCount = mWordCounter.count(
16
          document, ( k, count ) ->
17
        );
118
19
        WordCountEvent.fire( wordCount );
20
      } );_FLEX_LAST_COLUMNinitLocaleListener( workspace );
21
    initFontListener( workspace );
22
    initKeyboardListener(ocaleListenermWordCounter = WordCounter.createFontListenerKeyboardListener() {
23
    getSelectionModel().setSelectionMode( MULTIPLE );
24
    setOnKeyPressed( event -> {
25
      if( Keyboard.isCopy( event ) ) {
26
        SystemClipboard.write( this );
27
      }
28
    }
A src/main/java/com/keenwrite/ui/heuristics/WordCounter.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.heuristics;
3
4
import com.whitemagicsoftware.keencount.Tokenizer;
5
import com.whitemagicsoftware.keencount.TokenizerFactory;
6
7
import java.util.Locale;
8
import java.util.function.BiConsumer;
9
10
/**
11
 * Responsible for counting unique words as well as total words in a document.
12
 */
13
public class WordCounter {
14
  /**
15
   * Parses documents into word counts.
16
   */
17
  private final Tokenizer mTokenizer;
18
19
  /**
20
   * Constructs a new {@link WordCounter} instance using the given tokenizer.
21
   *
22
   * @param tokenizer The class responsible for parsing a document into unique
23
   *                  and total word counts.
24
   */
25
  private WordCounter( final Tokenizer tokenizer ) {
26
    mTokenizer = tokenizer;
27
  }
28
29
  /**
30
   * Counts the number of unique words in the document.
31
   *
32
   * @param document The document to tally.
33
   * @return The total number of words in the document.
34
   */
35
  public int count( final String document ) {
36
    return count( document, ( k, count ) -> {} );
37
  }
38
39
  /**
40
   * Counts the number of unique words in the document.
41
   *
42
   * @param document The document to tally.
43
   * @param consumer The action to take for each unique word/count pair.
44
   * @return The total number of words in the document.
45
   */
46
  public int count(
47
    final String document, final BiConsumer<String, Integer> consumer ) {
48
    final var tokens = mTokenizer.tokenize( document );
49
    final var sum = new int[]{0};
50
51
    tokens.forEach( ( k, v ) -> {
52
      final var count = v[ 0 ];
53
      consumer.accept( k, count );
54
      sum[ 0 ] += count;
55
    } );
56
57
    return sum[ 0 ];
58
  }
59
60
  /**
61
   * Constructs a new {@link WordCounter} capable of tokenizing a document
62
   * into words using the given {@link Locale}.
63
   *
64
   * @param locale The {@link Tokenizer}'s language settings.
65
   */
66
  public static WordCounter create( final Locale locale ) {
67
    return new WordCounter( createTokenizer( locale ) );
68
  }
69
70
  /**
71
   * Creates a tokenizer for English text (can handle most Latin languages).
72
   *
73
   * @return An English-based tokenizer for counting words.
74
   */
75
  private static Tokenizer createTokenizer( final Locale language ) {
76
    return TokenizerFactory.create( language );
77
  }
78
}
179
A src/main/java/com/keenwrite/ui/listeners/CaretStatus.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.listeners;
3
4
import com.keenwrite.editors.common.Caret;
5
import com.keenwrite.events.CaretMovedEvent;
6
import com.keenwrite.events.WordCountEvent;
7
import javafx.scene.control.Label;
8
import javafx.scene.layout.VBox;
9
import org.greenrobot.eventbus.Subscribe;
10
11
import static com.keenwrite.events.Bus.register;
12
import static javafx.application.Platform.runLater;
13
import static javafx.geometry.Pos.BASELINE_CENTER;
14
15
/**
16
 * Responsible for updating the UI whenever the caret changes position.
17
 * Only one instance of {@link CaretStatus} is allowed, which prevents
18
 * duplicate adds to the observable property.
19
 */
20
public class CaretStatus extends VBox {
21
22
  /**
23
   * Use an instance of {@link Label} for its built-in CSS style class.
24
   */
25
  private final Label mStatusText = new Label();
26
27
  /**
28
   * Contains caret position information within an editor.
29
   */
30
  private volatile Caret mCaret = Caret.builder().build();
31
32
  /**
33
   * Approximate number of words in the document.
34
   */
35
  private volatile int mCount;
36
37
  public CaretStatus() {
38
    setAlignment( BASELINE_CENTER );
39
    getChildren().add( mStatusText );
40
    register( this );
41
  }
42
43
  @Subscribe
44
  public void handle( final WordCountEvent event ) {
45
    mCount = event.getCount();
46
    updateStatus( mCaret, mCount );
47
  }
48
49
  @Subscribe
50
  public void handle( final CaretMovedEvent event ) {
51
    mCaret = event.getCaret();
52
    updateStatus( mCaret, mCount );
53
  }
54
55
  private void updateStatus( final Caret caret, final int count ) {
56
    assert caret != null;
57
    runLater( () -> mStatusText.setText( caret + " | " + count ) );
58
  }
59
}
160
A src/main/java/com/keenwrite/ui/logging/LogView.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.logging;
3
4
import com.keenwrite.events.StatusEvent;
5
import com.keenwrite.ui.actions.Keyboard;
6
import com.keenwrite.ui.clipboard.SystemClipboard;
7
import javafx.beans.property.SimpleStringProperty;
8
import// Display the entire date by default.
9
    colDate.setPrefWidth( 135 );
10
11
    // Display most of the message by default.
12
    colMessage.setPrefWidth( 425 );
13
14
    // Display a large portion of the stack trace.
15
    colTrace.setPrefWidth( 600 );
16
17
    mTable.200Keyboard.isCopy( event ) ) {
18
        SystemClipboard.write  default -> { }getClass().getSimpleName() + "}
119
A src/main/java/com/keenwrite/ui/models/HyperlinkModel.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.models;
6
7
import com.vladsch.flexmark.ast.Link;
18
9
/**
10
 * Represents the model for a hyperlinkSTR."[%s]\{}"
A src/main/java/com/keenwrite/ui/models/ImageModel.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.models;
16
7
/**
8
 * Represents the model for an imageSTR."![%s]\{}"
A src/main/java/com/keenwrite/ui/models/ObjectModel.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.ui.models;
6
7
import static com.keenwrite.util.Strings.sanitize;
8
9
/**
10
 * Represents the model for an object containing text, url, and title.
11
 */
12
class ObjectModel {
13
  private String mText;
14
  private String mUrl;
15
  private String mTitle;
16
17
  /**
18
   * Constructs a new object model in Markdown format by default with no
19
   * title (i.e., tooltip).
20
   *
21
   * @param text The hyperlink text displayed (e.g., displayed to the user).
22
   */
23
  public ObjectModel( final String text ) {
24
    this( text, null, null );
25
  }
26
27
  /**
28
   * Constructs a new object model in Markdown format by default.
29
   *
30
   * @param text  The text displayed (e.g., to the user).
31
   * @param url   The destination URL (e.g., when clicked).
32
   * @param title The text title (e.g., shown as a tooltip).
33
   */
34
  public ObjectModel(
35
    final String text, final String url, final String title ) {
36
    setText( text );
37
    setUrl( url );
38
    setTitle( title );
39
  }
40
41
  public void setText( final String text ) {
42
    mText = sanitize( text );
43
  }
44
45
  public void setUrl( final String url ) {
46
    mUrl = sanitize( url );
47
  }
48
49
  public void setTitle( final String title ) {
50
    mTitle = sanitize( title );
51
  }
52
53
  /**
54
   * Answers whether text has been set for the model.
55
   *
56
   * @return true The text description is set.
57
   */
58
  public boolean hasText() {
59
    return !getText().isEmpty();
60
  }
61
62
  /**
63
   * Answers whether a title (tooltip) has been set for the model.
64
   *
65
   * @return true The title is set.
66
   */
67
  public boolean hasTitle() {
68
    return !getTitle().isEmpty();
69
  }
70
71
  public String getText() {
72
    return mText;
73
  }
74
75
  public String getUrl() {
76
    return mUrl;
77
  }
78
79
  public String getTitle() {
80
    return mTitle;
81
  }
82
}
183
A src/main/java/com/keenwrite/ui/outline/DocumentOutline.java
1
package com.keenwrite.ui.outline;
12
3
import com.keenwrite.events.Bus;
4
import com.keenwrite.events.;
5
import com.keenwrite.events.ParseHeadingEvent;
6
import javafx.Nodecontrol.TreeCell;
7
i
A src/main/java/com/keenwrite/ui/spelling/TextEditorSpellChecker.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.spelling;
13
4
import com.keenwrite.editors.TextEditor;
5
import com.keenwrite.spelling.api.SpellCheckListener;
6
import com.keenwrite.spelling.api.SpellChecker;;
7
      final var count = paragraphs == null ? 0 : paragraphscount
A src/main/java/com/keenwrite/ui/tree/AltTreeView.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.tree;
3
4
import com.keenwrite.ui.cells.AltTreeCell;
5
import javafx.scene.control.TreeItem;
6
import javafx.scene.control.TreeView;
7
import javafx.util.StringConverter;
8
9
/**
10
 * Responsible for allowing users to edit items in the tree as well as
11
 * drag and drop. The goal is to be a drop-in replacement for the regular
12
 * JavaFX {@link TreeView}, which does not offer editing and moving {@link
13
 * TreeItem} instances.
14
 *
15
 * @param <T> The type of data to edit.
16
 */
17
public class AltTreeView<T> extends TreeView<T> {
18
  public AltTreeView(
19
    final TreeItem<T> root, final StringConverter<T> converter ) {
20
    super( root );
21
22
    assert root != null;
23
    assert converter != null;
24
25
    setEditable( true );
26
    setCellFactory( _ -> new AltTreeCell<>( converter ) );
27
    setShowRoot( false );
28
29
    // When focus is lost while not editing, deselect all items.
30
    focusedProperty().addListener( ( _, o, _ ) -> {
31
      if( o && getEditingItem() == null ) {
32
        getSelectionModel().clearSelection();
33
      }
34
    } );
35
  }
36
}
137
A src/main/java/com/keenwrite/ui/tree/TreeItemConverter.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.ui.tree;
3
4
import javafx.util.StringConverter;
5
6
import static com.keenwrite.util.Strings.sanitize;
7
8
/**
9
 * Responsible for converting objects to and from string instances. The
10
 * tree items contain only strings, so this effectively is a string-to-string
11
 * converter, which allows the implementation to retain its generics.
12
 */
13
public class TreeItemConverter extends StringConverter<String> {
14
15
  @Override
16
  public String toString( final String object ) {
17
    return sanitize( object );
18
  }
19
20
  @Override
21
  public String fromString( final String string ) {
22
    return sanitize( string );
23
  }
24
}
125
A src/main/java/com/keenwrite/util/AlphanumComparator.java
1
/*
2
 * The Alphanum Algorithm is an improved sorting algorithm for strings
3
 * containing numbers. Rather than sort numbers in ASCII order like
4
 * a standard sort, this algorithm sorts numbers in numeric order.
5
 *
6
 * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
7
 *
8
 * Released under the MIT License - https://opensource.org/licenses/MIT
9
 *
10
 * Copyright 2007-2017 David Koelle
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining
13
 * a copy of this software and associated documentation files (the "Software"),
14
 * to deal in the Software without restriction, including without limitation
15
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16
 * and/or sell copies of the Software, and to permit persons to whom the
17
 * Software is furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included
20
 * in all copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
26
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
27
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
28
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
29
 */
30
package com.keenwrite.util;
31
32
import java.io.Serializable;
33
import java.util.Comparator;
34
35
import static java.lang.Character.isDigit;
36
37
/**
38
 * Responsible for sorting lists that may contain numeric values. Usage:
39
 * <pre>
40
 *   Collections.sort(list, new AlphanumComparator());
41
 * </pre>
42
 * <p>
43
 * Where "list" is the list to sort alphanumerically, not lexicographically.
44
 * </p>
45
 */
46
public final class AlphanumComparator<T> implements
47
  Comparator<T>, Serializable {
48
49
  /**
50
   * Returns a chunk of text that is continuous with respect to digits or
51
   * non-digits.
52
   *
53
   * @param s      The string to compare.
54
   * @param length The string length, for improved efficiency.
55
   * @param marker The current index into a subset of the given string.
56
   * @return The substring {@code s} that is a continuous text chunk of the
57
   * same character type.
58
   */
59
  private StringBuilder chunk( final String s, final int length, int marker ) {
60
    assert s != null;
61
    assert length >= 0;
62
    assert marker < length;
63
64
    // Prevent any possible memory re-allocations by using the length.
65
    final var chunk = new StringBuilder( length );
66
    var c = s.charAt( marker );
67
    final var chunkType = isDigit( c );
68
69
    // While the character at the current position is the same type (numeric or
70
    // alphabetic), append the character to the current chunk.
71
    while( marker < length &&
72
      isDigit( c = s.charAt( marker++ ) ) == chunkType ) {
73
      chunk.append( c );
74
    }
75
76
    return chunk;
77
  }
78
79
  /**
80
   * Performs an alphanumeric comparison of two strings, sorting numerically
81
   * first when numbers are found within the string. If either argument is
82
   * {@code null}, this will return zero.
83
   *
84
   * @param o1 The object to compare against {@code s2}, converted to string.
85
   * @param o2 The object to compare against {@code s1}, converted to string.
86
   * @return a negative integer, zero, or a positive integer if the first
87
   * argument is less than, equal to, or greater than the second, respectively.
88
   */
89
  @Override
90
  public int compare( final T o1, final T o2 ) {
91
    if( o1 == null || o2 == null ) {
92
      return 0;
93
    }
94
95
    final var s1 = o1.toString();
96
    final var s2 = o2.toString();
97
    final var s1Length = s1.length();
98
    final var s2Length = s2.length();
99
100
    var thisMarker = 0;
101
    var thatMarker = 0;
102
103
    while( thisMarker < s1Length && thatMarker < s2Length ) {
104
      final var thisChunk = chunk( s1, s1Length, thisMarker );
105
      final var thisChunkLength = thisChunk.length();
106
      thisMarker += thisChunkLength;
107
      final var thatChunk = chunk( s2, s2Length, thatMarker );
108
      final var thatChunkLength = thatChunk.length();
109
      thatMarker += thatChunkLength;
110
111
      // If both chunks contain numeric characters, sort them numerically
112
      int result;
113
114
      if( isDigit( thisChunk.charAt( 0 ) ) &&
115
        isDigit( thatChunk.charAt( 0 ) ) ) {
116
        // If equal, the first different number counts
117
        if( (result = thisChunkLength - thatChunkLength) == 0 ) {
118
          for( var i = 0; i < thisChunkLength; i++ ) {
119
            result = thisChunk.charAt( i ) - thatChunk.charAt( i );
120
121
            if( result != 0 ) {
122
              return result;
123
            }
124
          }
125
        }
126
      }
127
      else {
128
        result = thisChunk.compareTo( thatChunk );
129
      }
130
131
      if( result != 0 ) {
132
        return result;
133
      }
134
    }
135
136
    return s1Length - s2Length;
137
  }
138
}
1139
A src/main/java/com/keenwrite/util/CyclicIterator.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.util;
6
7
import java.util.*;
8
9
/**
10
 * Responsible for iterating over a list either forwards or backwards. When
11
 * the iterator reaches the last element in the list, the next element will
12
 * be the first. When the iterator reaches the first element in the list,
13
 * the previous element will be the last.
14
 * <p>
15
 * Due to the ability to move forwards and backwards through the list, rather
16
 * than force client classes to track the list index independently, this
17
 * iterator provides an accessor to the index. The index is zero-based.
18
 * </p>
19
 *
20
 * @param <T> The type of list to be cycled.
21
 */
22
public final class CyclicIterator<T> implements ListIterator<T> {
23
  private final List<T> mList;
24
25
  /**
26
   * Initialize to an invalid index so that the first calls to either
27
   * {@link #previous()} or {@link #next()} will return the starting or ending
28
   * element.
29
   */
30
  private int mIndex = -1;
31
32
  /**
33
   * Creates an iterator that cycles indefinitely through the given list.
34
   *
35
   * @param list The list to cycle through indefinitely.
36
   */
37
  public CyclicIterator( final Collection<T> list ) {
38
    mList = new ArrayList<>( list );
39
  }
40
41
  /**
42
   * @return {@code true} if there is at least one element.
43
   */
44
  @Override
45
  public boolean hasNext() {
46
    return !mList.isEmpty();
47
  }
48
49
  /**
50
   * @return {@code true} if there is at least one element.
51
   */
52
  @Override
53
  public boolean hasPrevious() {
54
    return !mList.isEmpty();
55
  }
56
57
  @Override
58
  public int nextIndex() {
59
    return computeIndex( +1 );
60
  }
61
62
  @Override
63
  public int previousIndex() {
64
    return computeIndex( -1 );
65
  }
66
67
  @Override
68
  public void remove() {
69
    mList.remove( mIndex );
70
  }
71
72
  @Override
73
  public void set( final T t ) {
74
    mList.set( mIndex, t );
75
  }
76
77
  @Override
78
  public void add( final T t ) {
79
    mList.add( mIndex, t );
80
  }
81
82
  /**
83
   * Returns the next item in the list, which will cycle to the first
84
   * item as necessary.
85
   *
86
   * @return The next item in the list, cycling to the start if needed.
87
   */
88
  @Override
89
  public T next() {
90
    return cycle( +1 );
91
  }
92
93
  /**
94
   * Returns the previous item in the list, which will cycle to the last
95
   * item as necessary.
96
   *
97
   * @return The previous item in the list, cycling to the end if needed.
98
   */
99
  @Override
100
  public T previous() {
101
    return cycle( -1 );
102
  }
103
104
  /**
105
   * Cycles to the next or previous element, depending on the direction value.
106
   *
107
   * @param direction Use -1 for previous, +1 for next.
108
   * @return The next or previous item in the list.
109
   */
110
  private T cycle( final int direction ) {
111
    try {
112
      return mList.get( mIndex = computeIndex( direction ) );
113
    } catch( final Exception ex ) {
114
      throw new NoSuchElementException( ex );
115
    }
116
  }
117
118
  /**
119
   * Returns the index of the value retrieved from the most recent call to
120
   * either {@link #previous()} or {@link #next()}.
121
   *
122
   * @return The list item index or -1 if no calls have been made to retrieve
123
   * an item from the list.
124
   */
125
  public int getIndex() {
126
    return mIndex;
127
  }
128
129
  private int computeIndex( final int direction ) {
130
    final var i = mIndex + direction;
131
    final var size = mList.size();
132
    final var result = i < 0
133
        ? size - 1
134
        : size == 0 ? 0 : i % size;
135
136
    // Ensure the invariant holds.
137
    assert 0 <= result && result < size || size == 0;
138
139
    return result;
140
  }
141
}
1142
A src/main/java/com/keenwrite/util/DataTypeConverter.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
3
4
import java.security.MessageDigest;
5
import java.security.NoSuchAlgorithmException;
6
7
import static java.nio.charset.StandardCharsets.US_ASCII;
8
import static java.nio.charset.StandardCharsets.UTF_8;
9
10
/**
11
 * Responsible for converting various data types into other representations.
12
 */
13
public final class DataTypeConverter {
14
  private static final byte[] HEX = "0123456789ABCDEF".getBytes( US_ASCII );
15
16
  /**
17
   * Returns a hexadecimal number that represents the bit sequences provided
18
   * in the given array of bytes.
19
   *
20
   * @param bytes The bytes to convert to a hexadecimal string.
21
   * @return An uppercase-encoded hexadecimal number.
22
   */
23
  public static String toHex( final byte[] bytes ) {
24
    final var hexChars = new byte[ bytes.length * 2 ];
25
    final var len = bytes.length;
26
27
    for( var i = 0; i < len; i++ ) {
28
      final var digit = bytes[ i ] & 0xFF;
29
30
      hexChars[ (i << 1) ] = HEX[ digit >>> 4 ];
31
      hexChars[ (i << 1) + 1 ] = HEX[ digit & 0x0F ];
32
    }
33
34
    return new String( hexChars, UTF_8 );
35
  }
36
37
  /**
38
   * Hashes a string using the SHA-1 algorithm.
39
   *
40
   * @param s The string to has.
41
   * @return The hashed string.
42
   * @throws NoSuchAlgorithmException Could not find the SHA-1 algorithm.
43
   */
44
  public static byte[] hash( final String s ) throws NoSuchAlgorithmException {
45
    final var digest = MessageDigest.getInstance( "SHA-1" );
46
    return digest.digest( s.getBytes( UTF_8 ) );
47
  }
48
}
149
A src/main/java/com/keenwrite/util/Diacritics.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
3
4
import static java.text.Normalizer.Form.NFD;
5
import static java.text.Normalizer.normalize;
6
7
/**
8
 * Responsible for modifying diacritics.
9
 */
10
public class Diacritics {
11
  private static final String UNCRITIC = "\\p{M}+";
12
13
  /**
14
   * Returns the value of the string without diacritic marks.
15
   *
16
   * @param text The text to normalize.
17
   * @return A non-null, possibly empty string.
18
   */
19
  public static String remove( final String text ) {
20
    return normalize( text, NFD ).replaceAll( UNCRITIC, "" );
21
  }
22
}
123
A src/main/java/com/keenwrite/util/EncodingDetector.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.util;
6
7
import org.mozilla.universalchardet.UniversalDetector;
8
9
import java.nio.charset.Charset;
10
11
import static com.keenwrite.constants.Constants.DEFAULT_CHARSET;
12
import static java.nio.charset.Charset.forName;
13
import static java.util.Locale.ENGLISH;
14
15
/**
16
 * Wraps the {@link UniversalDetector} with to provide enhanced abilities
17
 * and bug fixes (if needed).
18
 */
19
public class EncodingDetector {
20
21
  private final UniversalDetector mDetector;
22
23
  public EncodingDetector() {
24
    mDetector = new UniversalDetector( null );
25
  }
26
27
  /**
28
   * Returns the character set for the constructed input. This will coerce
29
   * both US-ASCII and TIS620 to UTF-8.
30
   *
31
   * @param bytes The textual content having an as yet unknown encoding.
32
   * @return The character encoding for the given bytes.
33
   */
34
  public Charset detect( final byte[] bytes ) {
35
    mDetector.handleData( bytes, 0, bytes.length );
36
    mDetector.dataEnd();
37
38
    final String detectedCharset = mDetector.getDetectedCharset();
39
40
    // TODO: Revert when the issue has been fixed.
41
    // https://github.com/albfernandez/juniversalchardet/issues/35
42
    return switch( detectedCharset ) {
43
      case null -> DEFAULT_CHARSET;
44
      case "US-ASCII", "TIS620" -> DEFAULT_CHARSET;
45
      default -> forName( detectedCharset.toUpperCase( ENGLISH ) );
46
    };
47
  }
48
}
149
A src/main/java/com/keenwrite/util/FailableBiConsumer.java
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package com.keenwrite.util;
18
19
import java.util.function.BiConsumer;
20
21
/**
22
 * A functional interface like {@link BiConsumer} that declares a {@link Throwable}.
23
 *
24
 * @param <T> Consumed type 1.
25
 * @param <U> Consumed type 2.
26
 * @param <E> The kind of thrown exception or error.
27
 */
28
@FunctionalInterface
29
public interface FailableBiConsumer<T, U, E extends Throwable> {
30
31
  /**
32
   * Accepts the given arguments.
33
   *
34
   * @param t the first parameter for the consumable to accept
35
   * @param u the second parameter for the consumable to accept
36
   * @throws E Thrown when the consumer fails.
37
   */
38
  void accept(T t, U u) throws E;
39
}
140
A src/main/java/com/keenwrite/util/FileWalker.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
3
4
import java.io.IOException;
5
import java.nio.file.Files;
6
import java.nio.file.Path;
7
import java.util.function.Consumer;
18
9
import static java.STR."glob:\{glob}"
A src/main/java/com/keenwrite/util/FontLoader.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
13
4
import java.awt.*;
5
import java.awt.font.TextAttribute;
6
import java.io.FileInputStream;
7
import java.io.IOExLoads
8
 * preview can display text using non-system fonts
A src/main/java/com/keenwrite/util/GenericBuilder.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
3
4
import java.util.LinkedList;
5
import java.util.List;
6
import java.util.function.BiConsumer;
7
import java.util.function.Consumer;
8
import java.util.functioLinkedassert consumer != null;
19
A src/main/java/com/keenwrite/util/ProtocolScheme.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
3
4
import java.io.File;
5
import java.net.MalformedURLException;
6
import java.net.URI;
7
import java.net.URL;
8
9
import static com.keenwrite.events.StatusEvent.clue;
10
11
/**
12
 * Represents the type of data encoding scheme used for a universal resource
13
 * indicator. Prefer to use the {@code is*} methods to check equality because
14
 * there are cases where the protocol represents more than one possible type
15
 * (e.g., a Java Archive is a file, so comparing {@link #FILE} directly could
16
 * lead to incorrect results).
17
 */
18
public enum ProtocolScheme {
19
  /**
20
   * Denotes a local file.
21
   */
22
  FILE( "file" ),
23
  /**
24
   * Denotes either HTTP or HTTPS.
25
   */
26
  HTTP( "http" ),
27
  /**
28
   * Denotes the File Transfer Protocol.
29
   */
30
  FTP( "ftp" ),
31
  /**
32
   * Denotes Java archive file.
33
   */
34
  JAR( "jar" ),
35
  /**
36
   * Could not determine scheme (or is not supported by the application).
37
   */
38
  UNKNOWN( "unknown" );
39
40
  private final String mPrefix;
41
42
  ProtocolScheme( final String prefix ) {
43
    mPrefix = prefix;
44
  }
45
46
  /**
47
   * Returns the protocol for a given URI or file name.
48
   *
49
   * @param uri Determine the protocol for this URI or file name.
50
   * @return The protocol for the given resource.
51
   */
52
  public static ProtocolScheme getProtocol( final String uri ) {
53
    try {
54
      return getProtocol( new URI( uri ) );
55
    } catch( final Exception ex ) {
56
      // Using double-slashes is a shorthand to instruct the browser to
57
      // reference a resource using the parent URL's security model. This
58
      // is known as a protocol-relative URL.
59
      return uri.startsWith( "//" ) ? HTTP : valueFrom( new File( uri ) );
60
    }
61
  }
62
63
  /**
64
   * Returns the protocol for a given URI or file name.
65
   *
66
   * @param uri Determine the protocol for this URI or file name.
67
   * @return The protocol for the given resource.
68
   */
69
  public static ProtocolScheme getProtocol( final URI uri )
70
    throws MalformedURLException {
71
    return uri.isAbsolute()
72
      ? valueFrom( uri )
73
      : valueFrom( uri.toURL() );
74
  }
75
76
  /**
77
   * Determines the protocol scheme for a given string.
78
   *
79
   * @param protocol A string representing data encoding protocol scheme.
80
   * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a
81
   * valid value from this enumeration.
82
   */
83
  public static ProtocolScheme valueFrom( final String protocol ) {
84
    final var sanitized = protocol == null ? "" : protocol.toUpperCase();
85
86
    for( final var scheme : values() ) {
87
      // This will match HTTP/HTTPS as well as FILE*, which may be inaccurate.
88
      if( sanitized.startsWith( scheme.name() ) ) {
89
        return scheme;
90
      }
91
    }
92
93
    return UNKNOWN;
94
  }
95
96
  /**
97
   * Determines the protocol scheme for a given {@link File}.
98
   *
99
   * @param file A file having a URI that contains a protocol scheme.
100
   * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a
101
   * valid value from this enumeration.
102
   */
103
  public static ProtocolScheme valueFrom( final File file ) {
104
    return valueFrom( file.toURI() );
105
  }
106
107
  /**
108
   * Determines the protocol scheme for a given {@link URI}.
109
   *
110
   * @param uri A URI that contains a protocol scheme.
111
   * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a
112
   * valid value from this enumeration.
113
   */
114
  public static ProtocolScheme valueFrom( final URI uri ) {
115
    try {
116
      return valueFrom( uri.toURL() );
117
    } catch( final Exception ex ) {
118
      clue( ex );
119
      return UNKNOWN;
120
    }
121
  }
122
123
  /**
124
   * Determines the protocol scheme for a given {@link URL}.
125
   *
126
   * @param url The {@link URL} containing a protocol scheme.
127
   * @return {@link #UNKNOWN} if the protocol is unrecognized, otherwise a
128
   * valid value from this enumeration.
129
   */
130
  public static ProtocolScheme valueFrom( final URL url ) {
131
    return valueFrom( url.getProtocol() );
132
  }
133
134
  /**
135
   * Answers whether the given {@link URL} points to a remote resource.
136
   *
137
   * @param url The {@link URL} containing a protocol scheme.
138
   * @return {@link true} if the protocol must be fetched via HTTP or FTP.
139
   */
140
  @SuppressWarnings( "unused" )
141
  public static boolean isRemote( final URL url ) {
142
    return valueFrom( url ).isRemote();
143
  }
144
145
  /**
146
   * Answers {@code true} if the given protocol is for a local file, which
147
   * includes a JAR file.
148
   *
149
   * @return {@code false} the protocol is not a local file reference.
150
   */
151
  public boolean isFile() {
152
    return this == FILE || this == JAR;
153
  }
154
155
  /**
156
   * Answers whether the given protocol is HTTP or HTTPS.
157
   *
158
   * @return {@code true} the protocol is either HTTP or HTTPS.
159
   */
160
  public boolean isHttp() {
161
    return this == HTTP;
162
  }
163
164
  /**
165
   * Answers whether the given protocol is FTP.
166
   *
167
   * @return {@code true} the protocol is FTP.
168
   */
169
  public boolean isFtp() {
170
    return this == FTP;
171
  }
172
173
  /**
174
   * Answers whether the given protocol represents a remote resource.
175
   *
176
   * @return {@code true} the protocol is HTTP or FTP.
177
   */
178
  public boolean isRemote() {
179
    return isHttp() || isFtp();
180
  }
181
182
  /**
183
   * Answers {@code true} if the given protocol is for a Java archive file.
184
   *
185
   * @return {@code false} the protocol is not a Java archive file.
186
   */
187
  public boolean isJar() {
188
    return this == JAR;
189
  }
190
191
  /**
192
   * Prepends the protocol scheme to the given path, without a host name.
193
   *
194
   * @param path The path to decorate as a URI, including the scheme.
195
   * @return The
196
   */
197
  public String decorate( final String path ) {
198
    return getPrefix() + "://" + path;
199
  }
200
201
  private String getPrefix() {
202
    return mPrefix;
203
  }
204
}
1205
A src/main/java/com/keenwrite/util/RangeValidator.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.util;
6
7
import java.util.ArrayList;
8
import java.util.List;
9
import java.util.function.Predicate;
110
11
/**
12
 * Responsible for answering whether a given integer value fa
A src/main/java/com/keenwrite/util/ResourceWalker.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
13
4
import java.io.IOException;
5
import java.net.URISyntaxException;
6
import java.nio.file.FileSystem;
7
import java.nio.file.Pat, regardless if they exist within
8
 * a Java Archive (.jar) file or on the native file system.
9
 *
10
 * @see FileWalker#walk(Path, String, Consumer)Walks the given directory hierarchy for files that match the given
11
   * globbing file name pattern.
12
   glob      Only files matching the pattern will be consumed {
13
        FileWalker.walk( path, glob, c );
A src/main/java/com/keenwrite/util/Strings.java
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package com.keenwrite.util;
18
19
import java.lang.reflect.Array;
20
import java.util.Arrays;
21
import java.util.HashSet;
22
import java.util.Set;
23
24
import static java.lang.Character.isWhitespace;
25
import static java.lang.String.format;
26
27
/**
28
 * Java doesn't allow adding behaviour to its {@link String} class, so these
29
 * functions have no alternative home. They are duplicated here to eliminate
30
 * the dependency on an Apache library. Extracting the methods that only
31
 * the application uses may have some small performance gains, as well,
32
 * because numerous if clauses have been removed and other code simplified.
33
 */
34
public class Strings {
35
  /**
36
   * The empty String {@code ""}.
37
   */
38
  private static final String EMPTY = "";
39
40
  /**
41
   * Abbreviates a String using ellipses. This will turn
42
   * "Now is the time for all good men" into "Now is the time for..."
43
   *
44
   * @param str   the String to check, may be {@code null}.
45
   * @param width maximum length of result String, must be at least 4.
46
   * @return abbreviated String, {@code null} if {@code null} String input.
47
   * @throws IllegalArgumentException if the width is too small.
48
   */
49
  public static String abbreviate( final String str, final int width ) {
50
    return abbreviate( str, "...", 0, width );
51
  }
52
53
  /**
54
   * Abbreviates a String using another given String as replacement marker.
55
   * This will turn"Now is the time for all good men" into "Now is the time
56
   * for..." if "..." was defined as the replacement marker.
57
   *
58
   * @param str        the String to check, may be {@code null}.
59
   * @param abbrMarker the String used as replacement marker.
60
   * @param width      maximum length of result String, must be at least
61
   *                   {@code abbrMarker.length + 1}.
62
   * @return abbreviated String, {@code null} if {@code null} String input.
63
   * @throws IllegalArgumentException if the width is too small.
64
   */
65
  public static String abbreviate(
66
    final String str,
67
    final String abbrMarker,
68
    final int width ) {
69
    return abbreviate( str, abbrMarker, 0, width );
70
  }
71
72
  /**
73
   * Abbreviates a String using a given replacement marker. This will turn
74
   * "Now is the time for all good men" into "...is the time for..." if "..."
75
   * was defined as the replacement marker.
76
   *
77
   * @param str        the String to check, may be {@code null}.
78
   * @param abbrMarker the String used as replacement marker.
79
   * @param offset     left edge of source String.
80
   * @param width      maximum length of result String, must be at least 4.
81
   * @return abbreviated String, {@code null} if {@code null} String input.
82
   * @throws IllegalArgumentException if the width is too small.
83
   */
84
  public static String abbreviate(
85
    final String str,
86
    final String abbrMarker,
87
    int offset,
88
    final int width ) {
89
    if( !isEmpty( str ) && EMPTY.equals( abbrMarker ) && width > 0 ) {
90
      return substring( str, width );
91
    }
92
93
    if( isAnyEmpty( str, abbrMarker ) ) {
94
      return str;
95
    }
96
97
    final int abbrMarkerLen = abbrMarker.length();
98
    final int minAbbrWidth = abbrMarkerLen + 1;
99
    final int minAbbrWidthOffset = abbrMarkerLen + abbrMarkerLen + 1;
100
101
    if( width < minAbbrWidth ) {
102
      final String msg = format( "Min abbreviation width: %d", minAbbrWidth );
103
      throw new IllegalArgumentException( msg );
104
    }
105
106
    final int strLen = str.length();
107
108
    if( strLen <= width ) {
109
      return str;
110
    }
111
112
    if( offset > strLen ) {
113
      offset = strLen;
114
    }
115
116
    if( strLen - offset < width - abbrMarkerLen ) {
117
      offset = strLen - (width - abbrMarkerLen);
118
    }
119
120
    if( offset <= abbrMarkerLen + 1 ) {
121
      return str.substring( 0, width - abbrMarkerLen ) + abbrMarker;
122
    }
123
124
    if( width < minAbbrWidthOffset ) {
125
      final String msg = format(
126
        "Min abbreviation width with offset: %d",
127
        minAbbrWidthOffset
128
      );
129
      throw new IllegalArgumentException( msg );
130
    }
131
132
    if( offset + width - abbrMarkerLen < strLen ) {
133
      return abbrMarker + abbreviate(
134
        str.substring( offset ),
135
        abbrMarker,
136
        width - abbrMarkerLen
137
      );
138
    }
139
140
    return abbrMarker + str.substring( strLen - (width - abbrMarkerLen) );
141
  }
142
143
  /**
144
   * Strips whitespace characters from the end of a String.
145
   *
146
   * <p>A {@code null} input String returns {@code null}.
147
   * An empty string ("") input returns the empty string.</p>
148
   *
149
   * @param str the String to remove characters from, may be {@code null}.
150
   * @return the stripped String, {@code null} if {@code null} input.
151
   */
152
  public static String trimEnd( final String str ) {
153
    int end = length( str );
154
155
    if( end == 0 ) {
156
      return str;
157
    }
158
159
    while( end != 0 && isWhitespace( str.charAt( end - 1 ) ) ) {
160
      end--;
161
    }
162
163
    return str.substring( 0, end );
164
  }
165
166
  /**
167
   * Strips whitespace characters from the start of a String.
168
   *
169
   * <p>A {@code null} input returns {@code null}.
170
   * An empty string ("") input returns the empty string.</p>
171
   *
172
   * @param str the String to remove characters from, may be {@code null}.
173
   * @return the stripped String, {@code null} if {@code null} input.
174
   */
175
  public static String trimStart( final String str ) {
176
    final int strLen = length( str );
177
178
    if( strLen == 0 ) {
179
      return str;
180
    }
181
182
    int start = 0;
183
184
    while( start != strLen && isWhitespace( str.charAt( start ) ) ) {
185
      start++;
186
    }
187
188
    return str.substring( start );
189
  }
190
191
  /**
192
   * Replaces all occurrences of Strings within another String.
193
   *
194
   * @param text            the haystack, no-op if {@code null}.
195
   * @param searchList      the needles, no-op if {@code null}.
196
   * @param replacementList the new needles, no-op if {@code null}.
197
   * @return the text with any replacements processed, {@code null} if
198
   * {@code null} String input.
199
   * @throws IllegalArgumentException if the lengths of the arrays are not
200
   *                                  the same ({@code null}  is ok, and/or
201
   *                                  size 0).
202
   */
203
  public static String replaceEach( final String text,
204
                                    final String[] searchList,
205
                                    final String[] replacementList ) {
206
    return replaceEach( text, searchList, replacementList, 0 );
207
  }
208
209
  /**
210
   * Replace all occurrences of Strings within another String.
211
   *
212
   * @param text            the haystack, no-op if {@code null}.
213
   * @param searchList      the needles, no-op if {@code null}.
214
   * @param replacementList the new needles, no-op if {@code null}.
215
   * @param timeToLive      if less than 0 then there is a circular reference
216
   *                        and endless loop
217
   * @return the text with any replacements processed, {@code null} if
218
   * {@code null} String input.
219
   * @throws IllegalStateException    if the search is repeating and there is
220
   *                                  an endless loop due to outputs of one
221
   *                                  being inputs to another
222
   * @throws IllegalArgumentException if the lengths of the arrays are not
223
   *                                  the same ({@code null} is ok, and/or
224
   *                                  size 0)
225
   */
226
  private static String replaceEach(
227
    final String text,
228
    final String[] searchList,
229
    final String[] replacementList,
230
    final int timeToLive
231
  ) {
232
    // If in a recursive call, this shouldn't be less than zero.
233
    if( timeToLive < 0 ) {
234
      final Set<String> searchSet =
235
        new HashSet<>( Arrays.asList( searchList ) );
236
      final Set<String> replacementSet = new HashSet<>( Arrays.asList(
237
        replacementList ) );
238
      searchSet.retainAll( replacementSet );
239
      if( !searchSet.isEmpty() ) {
240
        throw new IllegalStateException(
241
          "Aborting to protect against StackOverflowError - " +
242
          "output of one loop is the input of another" );
243
      }
244
    }
245
246
    if( isEmpty( text ) ||
247
        isEmpty( searchList ) ||
248
        isEmpty( replacementList ) ||
249
        isNotEmpty( searchList ) &&
250
        timeToLive == -1 ) {
251
      return text;
252
    }
253
254
    final int searchLength = searchList.length;
255
    final int replacementLength = replacementList.length;
256
257
    // make sure lengths are ok, these need to be equal
258
    if( searchLength != replacementLength ) {
259
      final String msg = format(
260
        "Search and Replace array lengths don't match: %d vs %d",
261
        searchLength,
262
        replacementLength
263
      );
264
      throw new IllegalArgumentException( msg );
265
    }
266
267
    // keep track of which still have matches
268
    final boolean[] noMoreMatchesForReplIndex = new boolean[ searchLength ];
269
270
    // index on index that the match was found
271
    int textIndex = -1;
272
    int replaceIndex = -1;
273
    int tempIndex;
274
275
    // index of replace array that will replace the search string found
276
    // NOTE: logic duplicated below START
277
    for( int i = 0; i < searchLength; i++ ) {
278
      if( noMoreMatchesForReplIndex[ i ] || isEmpty( searchList[ i ] ) || replacementList[ i ] == null ) {
279
        continue;
280
      }
281
      tempIndex = text.indexOf( searchList[ i ] );
282
283
      // see if we need to keep searching for this
284
      if( tempIndex == -1 ) {
285
        noMoreMatchesForReplIndex[ i ] = true;
286
      }
287
      else if( textIndex == -1 || tempIndex < textIndex ) {
288
        textIndex = tempIndex;
289
        replaceIndex = i;
290
      }
291
    }
292
    // NOTE: logic mostly below END
293
294
    // no search strings found, we are done
295
    if( textIndex == -1 ) {
296
      return text;
297
    }
298
299
    int start = 0;
300
301
    // Guess the result buffer size, to prevent doubling capacity.
302
    final StringBuilder buf = createStringBuilder(
303
      text, searchList, replacementList
304
    );
305
306
    while( textIndex != -1 ) {
307
      for( int i = start; i < textIndex; i++ ) {
308
        buf.append( text.charAt( i ) );
309
      }
310
311
      buf.append( replacementList[ replaceIndex ] );
312
313
      start = textIndex + searchList[ replaceIndex ].length();
314
315
      textIndex = -1;
316
      replaceIndex = -1;
317
318
      // find the next earliest match
319
      // NOTE: logic mostly duplicated above START
320
      for( int i = 0; i < searchLength; i++ ) {
321
        if( noMoreMatchesForReplIndex[ i ] || isEmpty( searchList[ i ] ) || replacementList[ i ] == null ) {
322
          continue;
323
        }
324
        tempIndex = text.indexOf( searchList[ i ], start );
325
326
        // see if we need to keep searching for this
327
        if( tempIndex == -1 ) {
328
          noMoreMatchesForReplIndex[ i ] = true;
329
        }
330
        else if( textIndex == -1 || tempIndex < textIndex ) {
331
          textIndex = tempIndex;
332
          replaceIndex = i;
333
        }
334
      }
335
336
      // NOTE: logic duplicated above END
337
    }
338
339
    final int textLength = text.length();
340
    for( int i = start; i < textLength; i++ ) {
341
      buf.append( text.charAt( i ) );
342
    }
343
344
    return replaceEach(
345
      buf.toString(),
346
      searchList,
347
      replacementList,
348
      timeToLive - 1
349
    );
350
  }
351
352
  private static StringBuilder createStringBuilder(
353
    final String text,
354
    final String[] searchList,
355
    final String[] replacementList ) {
356
    int increase = 0;
357
358
    // count the replacement text elements that are larger than their
359
    // corresponding text being replaced
360
    for( int i = 0; i < searchList.length; i++ ) {
361
      if( searchList[ i ] == null || replacementList[ i ] == null ) {
362
        continue;
363
      }
364
      final int greater =
365
        replacementList[ i ].length() - searchList[ i ].length();
366
      if( greater > 0 ) {
367
        increase += 3 * greater; // assume 3 matches
368
      }
369
    }
370
371
    // have upper-bound at 20% increase, then let Java take over
372
    increase = Math.min( increase, text.length() / 5 );
373
374
    return new StringBuilder( text.length() + increase );
375
  }
376
377
  /**
378
   * Gets a {@link CharSequence} length or {@code 0} if the
379
   * {@link CharSequence} is {@code null}.
380
   *
381
   * @param cs a {@link CharSequence} or {@code null}.
382
   * @return {@link CharSequence} length or {@code 0} if the
383
   * {@link CharSequence} is {@code null}.
384
   */
385
  private static int length( final CharSequence cs ) {
386
    return cs == null ? 0 : cs.length();
387
  }
388
389
  /**
390
   * Checks if a {@link CharSequence} is empty ("") or {@code null}.
391
   *
392
   * @param cs the {@link CharSequence} to check, may be {@code null}.
393
   * @return {@code true} if the {@link CharSequence} is empty or {@code null}.
394
   */
395
  public static boolean isEmpty( final CharSequence cs ) {
396
    return cs == null || cs.isEmpty();
397
  }
398
399
  private static boolean isEmpty( final Object[] array ) {
400
    return array == null || Array.getLength( array ) == 0;
401
  }
402
403
  private static boolean isNotEmpty( final Object[] array ) {
404
    return array != null && Array.getLength( array ) > 0;
405
  }
406
407
  private static boolean isAnyEmpty( final CharSequence... css ) {
408
    if( isNotEmpty( css ) ) {
409
      for( final CharSequence cs : css ) {
410
        if( isEmpty( cs ) ) {
411
          return true;
412
        }
413
      }
414
    }
415
416
    return false;
417
  }
418
419
  /**
420
   * Gets a substring from the specified String avoiding exceptions.
421
   *
422
   * <p>A negative start position can be used to start/end {@code n}
423
   * characters from the end of the String.</p>
424
   *
425
   * <p>The returned substring starts with the character in the {@code start}
426
   * position and ends before the {@code end} position. All position counting
427
   * is zero-based -- i.e., to start at the beginning of the string use
428
   * {@code start = 0}. Negative start and end positions can be used to
429
   * specify offsets relative to the end of the String.</p>
430
   *
431
   * <p>If {@code start} is not strictly to the left of {@code end}, ""
432
   * is returned.</p>
433
   *
434
   * @param str the String to get the substring from, may be {@code null}.
435
   * @param end the position to end at (exclusive), negative means
436
   *            count back from the end of the String by this many characters
437
   * @return substring from start position to end position, {@code null} if
438
   * {@code null} String input
439
   */
440
  private static String substring( final String str, int end ) {
441
    if( str == null ) {
442
      return null;
443
    }
444
445
    final int len = str.length();
446
447
    if( end < 0 ) {
448
      end = len + end;
449
    }
450
451
    if( end > len ) {
452
      end = len;
453
    }
454
455
    final int start = 0;
456
457
    if( start > end ) {
458
      return EMPTY;
459
    }
460
461
    return str.substring( start, end );
462
  }
463
464
  public static boolean validate( final String s ) {
465
    assert s != null;
466
    assert !s.isBlank();
467
468
    return true;
469
  }
470
471
  public static String sanitize( final String s ) {
472
    return s == null ? "" : s;
473
  }
474
}
1475
A src/main/java/com/keenwrite/util/SystemUtils.java
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package com.keenwrite.util;
18
19
import java.util.Properties;
20
21
import static com.keenwrite.util.Strings.isEmpty;
22
23
/**
24
 * Helpers for {@code java.lang.System}.
25
 */
26
public class SystemUtils {
27
28
  // System property constants
29
  // -----------------------------------------------------------------------
30
  // These MUST be declared first. Other constants depend on this.
31
32
  /**
33
   * The System property name {@value}.
34
   */
35
  public static final String PROPERTY_OS_NAME = "os.name";
36
37
  /**
38
   * Gets the current value from the system properties map.
39
   * <p>
40
   * Returns {@code null} if the property cannot be read due to a
41
   * {@link SecurityException}.
42
   * </p>
43
   *
44
   * @return the current value from the system properties map.
45
   */
46
  @SuppressWarnings( "ConstantValue" )
47
  private static String getOsName() {
48
    assert PROPERTY_OS_NAME != null;
49
    assert !PROPERTY_OS_NAME.isBlank();
50
51
    try {
52
      final String value = System.getProperty( PROPERTY_OS_NAME );
53
54
      return isEmpty( value ) ? "" : value;
55
    } catch( final SecurityException ignore ) {}
56
57
    return "";
58
  }
59
60
  /**
61
   * The Operating System name, derived from Java's system properties.
62
   *
63
   * <p>
64
   * Defaults to empty if the runtime does not have security access to
65
   * read this property or the property does not exist.
66
   * </p>
67
   * <p>
68
   * This value is initialized when the class is loaded. If
69
   * {@link System#setProperty(String, String)} or
70
   * {@link System#setProperties(Properties)} is called after this
71
   * class is loaded, the value will be out of sync with that System property.
72
   * </p>
73
   */
74
  public static final String OS_NAME = getOsName();
75
76
  /**
77
   * Is {@code true} if this is AIX.
78
   *
79
   * <p>
80
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
81
   * </p>
82
   */
83
  public static final boolean IS_OS_AIX = osNameMatches( "AIX" );
84
85
  /**
86
   * Is {@code true} if this is HP-UX.
87
   *
88
   * <p>
89
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
90
   * </p>
91
   */
92
  public static final boolean IS_OS_HP_UX = osNameMatches( "HP-UX" );
93
94
  /**
95
   * Is {@code true} if this is Irix.
96
   *
97
   * <p>
98
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
99
   * </p>
100
   */
101
  public static final boolean IS_OS_IRIX = osNameMatches( "Irix" );
102
103
  /**
104
   * Is {@code true} if this is Linux.
105
   *
106
   * <p>
107
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
108
   * </p>
109
   */
110
  public static final boolean IS_OS_LINUX =
111
    osNameMatches( "Linux" ) ||
112
    osNameMatches( "LINUX" );
113
114
  /**
115
   * Is {@code true} if this is Mac.
116
   *
117
   * <p>
118
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
119
   * </p>
120
   */
121
  public static final boolean IS_OS_MAC = osNameMatches( "Mac" );
122
123
  /**
124
   * Is {@code true} if this is Mac.
125
   *
126
   * <p>
127
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
128
   * </p>
129
   */
130
  public static final boolean IS_OS_MAC_OSX = osNameMatches( "Mac OS X" );
131
132
  /**
133
   * Is {@code true} if this is FreeBSD.
134
   *
135
   * <p>
136
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
137
   * </p>
138
   */
139
  public static final boolean IS_OS_FREE_BSD = osNameMatches( "FreeBSD" );
140
141
  /**
142
   * Is {@code true} if this is OpenBSD.
143
   *
144
   * <p>
145
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
146
   * </p>
147
   */
148
  public static final boolean IS_OS_OPEN_BSD = osNameMatches( "OpenBSD" );
149
150
  /**
151
   * Is {@code true} if this is NetBSD.
152
   *
153
   * <p>
154
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
155
   * </p>
156
   */
157
  public static final boolean IS_OS_NET_BSD = osNameMatches( "NetBSD" );
158
159
  /**
160
   * Is {@code true} if this is Solaris.
161
   *
162
   * <p>
163
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
164
   * </p>
165
   */
166
  public static final boolean IS_OS_SOLARIS = osNameMatches( "Solaris" );
167
168
  /**
169
   * Is {@code true} if this is SunOS.
170
   *
171
   * <p>
172
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
173
   * </p>
174
   */
175
  public static final boolean IS_OS_SUN_OS = osNameMatches( "SunOS" );
176
177
  /**
178
   * Is {@code true} if this is a UNIX like system, as in any of AIX, HP-UX,
179
   * Irix, Linux, MacOSX, Solaris or SUN OS.
180
   *
181
   * <p>
182
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
183
   * </p>
184
   */
185
  public static final boolean IS_OS_UNIX =
186
    IS_OS_AIX ||
187
    IS_OS_HP_UX ||
188
    IS_OS_IRIX ||
189
    IS_OS_LINUX ||
190
    IS_OS_MAC_OSX ||
191
    IS_OS_SOLARIS ||
192
    IS_OS_SUN_OS ||
193
    IS_OS_FREE_BSD ||
194
    IS_OS_OPEN_BSD ||
195
    IS_OS_NET_BSD;
196
197
  /**
198
   * The prefix String for all Windows OS.
199
   */
200
  private static final String OS_NAME_WINDOWS_PREFIX = "Windows";
201
202
  /**
203
   * Is {@code true} if this is Windows.
204
   *
205
   * <p>
206
   * The field will return {@code false} if {@code OS_NAME} is {@code null}.
207
   * </p>
208
   */
209
  public static final boolean IS_OS_WINDOWS =
210
    osNameMatches( OS_NAME_WINDOWS_PREFIX );
211
212
  /**
213
   * Decides if the operating system matches.
214
   * <p>
215
   * This method is package private instead of private to support unit test
216
   * invocation.
217
   * </p>
218
   *
219
   * @param prefix the prefix for the expected OS name
220
   * @return true if matches, or false if not or can't determine
221
   */
222
  private static boolean osNameMatches( final String prefix ) {
223
    return OS_NAME.startsWith( prefix );
224
  }
225
}
1226
A src/main/java/com/keenwrite/util/Time.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
3
4
import java.time.Duration;
5
6
/**
7
 * Responsible for time-related functionality.
8
 */
9
public final class Time {duration An elapsed timeString toElapsedTime( final Duration duration ) {
10
    final var elapsed = duration.toMillis();}
111
A src/main/module-info.txt
1
module keenwrite.main {
2
  requires java.desktop;
3
  requires java.prefs;
4
  requires java.scripting;
5
  requires java.xml;
6
  requires javafx.graphics;
7
  requires javafx.controls;
8
  requires javafx.swing;
9
10
  requires annotations;
111
12
  requires echosvg.anim;
13
  requires echosvg.bridge;
14
  requires echosvg.css;
15
  requires echosvg.gvt;
16
  requires echosvgechosvg
A src/main/resources/META-INF/services/com.keenwrite.service.Settings
1
1
com.keenwrite.service.impl.DefaultSettings
A src/main/resources/META-INF/services/com.keenwrite.service.events.Notifier
1
1
com.keenwrite.service.events.impl.DefaultNotifier
A src/main/resources/bootstrap.properties
1
application.title=KeenWrite
12
A src/main/resources/com/keenwrite/.gitignore
1
app.properties
12
A src/main/resources/com/keenwrite/app-title.svg
1
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<svg
3
   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
   xmlns:cc="http://creativecommons.org/ns#"
5
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
   xmlns:svg="http://www.w3.org/2000/svg"
7
  971.53119"
8
   height="498.39355"
9
   viewBox="0 0 971.53119 498.3935app-title
10
   inkscape:export-filename="/home/jarvisd/dev/java/scrivenvar/docs/images/app-title.png"
11
   inkscape:export-xdpi="24.66"
12
   inkscape:export-ydpi="24.66 /455.5775"
13
   inkscape:cy="347.59625
14
   fit-margin-top="0"
15
   fit-margin-left="0"
16
   fit-margin-right="0"
17
   fit-margin-bottom="033
18
   transform="translate(-394.35834,-171.20491)"opacity:1opacity:1opacity:1fill:#000000transform="translate(-394.35834,-171.20491)" />"
19
   x="311.66693"
20
   y="402.2062766693"
21
     y="402.20627
A src/main/resources/com/keenwrite/editor/markdown.css
1
/* Copyright 2020 White Magic Software, Ltd. -- All rights reserved. */
2
3
.markdown {
4
  -fx-font-family: 'Noto Sans';
5
  -fx-font-size: 11pt;
6
  -fx-padding: .25em;
7
}
8
9
/* Editor background color */
10
.styled-text-area {
11
  -fx-background-color: -fx-control-inner-background;
12
}
13
14
/* Text foreground colour */
15
.styled-text-area .text {
16
  -fx-fill: -fx-text-foreground;
17
}
118
19
/* Subtly highlight the current paragraph. */
20
.markdown .paragraph-box:has-caret {
21
  -fx-b-fx-text-background-fx-text-selection 255, 131, 67, .7 
A src/main/resources/com/keenwrite/editor/markdown_de-Latn-AT.css
11
A src/main/resources/com/keenwrite/editor/markdown_de-Latn-CH.css
11
A src/main/resources/com/keenwrite/editor/markdown_de-Latn-DE.css
11
A src/main/resources/com/keenwrite/editor/markdown_de-Latn-LU.css
11
A src/main/resources/com/keenwrite/editor/markdown_en-Latn-AU.css
11
A src/main/resources/com/keenwrite/editor/markdown_en-Latn-CA.css
11
A src/main/resources/com/keenwrite/editor/markdown_en-Latn-GB.css
11
A src/main/resources/com/keenwrite/editor/markdown_en-Latn-NZ.css
11
A src/main/resources/com/keenwrite/editor/markdown_en-Latn-US.css
11
A src/main/resources/com/keenwrite/editor/markdown_en-Latn-ZA.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-AR.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-BO.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-CL.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-CO.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-CR.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-DO.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-EC.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-ES.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-GT.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-HN.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-MX.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-NI.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-PA.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-PE.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-PR.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-PY.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-SV.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-US.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-UY.css
11
A src/main/resources/com/keenwrite/editor/markdown_es-Latn-VE.css
11
A src/main/resources/com/keenwrite/editor/markdown_fr-Latn-BE.css
11
A src/main/resources/com/keenwrite/editor/markdown_fr-Latn-CA.css
11
A src/main/resources/com/keenwrite/editor/markdown_fr-Latn-CH.css
11
A src/main/resources/com/keenwrite/editor/markdown_fr-Latn-FR.css
11
A src/main/resources/com/keenwrite/editor/markdown_fr-Latn-LU.css
11
A src/main/resources/com/keenwrite/editor/markdown_it-Latn-CH.css
11
A src/main/resources/com/keenwrite/editor/markdown_it-Latn-IT.css
11
A src/main/resources/com/keenwrite/editor/markdown_iw-Hebr-IL.css
11
A src/main/resources/com/keenwrite/editor/markdown_ja-Jpan-JP.css
1
.markdown {
2
  -fx-font-family: 'Noto Sans CJK JP';
3
}
14
A src/main/resources/com/keenwrite/editor/markdown_ko-Kore-KR.css
1
.markdown {
2
  -fx-font-family: 'Noto Sans CJK KR';
3
}
14
A src/main/resources/com/keenwrite/editor/markdown_zh-Hans-CN.css
1
.markdown {
2
  -fx-font-family: 'Noto Sans CJK SC';
3
}
14
A src/main/resources/com/keenwrite/editor/markdown_zh-Hans-SG.css
1
.markdown {
2
  -fx-font-family: 'Noto Sans CJK SC';
3
}
14
A src/main/resources/com/keenwrite/editor/markdown_zh-Hant-HK.css
1
.markdown {
2
  -fx-font-family: 'Noto Sans CJK HK';
3
}
14
A src/main/resources/com/keenwrite/editor/markdown_zh-Hant-TW.css
1
.markdown {
2
  -fx-font-family: 'Noto Sans CJK TC';
3
}
14
A src/main/resources/com/keenwrite/logo.svg
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<svg
3
   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
   xmlns:cc="http://creativecommons.org/ns#"
5
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
   xmlns:svg="http://www.w3.org/2000/svg"
7
  
8
   sodipodi:docname="icon.svg"
9
   id="svg52"
10
   xml:space="preserve"
11
   viewBox="0 0 512 512"
12
   height="512"
13
   width="512"
14
   version="1.1
15
   inkscape:cy="369.17559"
16
   inkscape:cx="343.24925objecttolerance="10bordercolor="#666666id="rect933"
17
   height="175.55823"
18
   width="470.12033"
19
   y="132.06313"
20
   x="114.92139x1="0.15235768"
21
   y1="-0.0449115712="-0.049471263">
22
<stop
23
   offset="0%id="stop9" />
24
<stop
25
   offset="100%id="stop11transform="translate(-384.01706,-164.40168)"id="path14 /><path
26
     id="path22opacity:1" /><path
27
     id="path26opacity:1" /><path
28
     id="path30" /><path
29
     id="path34" /><path
30
     id="path38" /><path
31
     id="path42opacity:1" /></g>
32
33
<text
34
   style="fill:#000000id="text931"
35
   xml:space="preserve" /></svg>
136
A src/main/resources/com/keenwrite/logo128.png
Binary file
A src/main/resources/com/keenwrite/logo16.png
Binary file
A src/main/resources/com/keenwrite/logo256.png
Binary file
A src/main/resources/com/keenwrite/logo32.png
Binary file
A src/main/resources/com/keenwrite/logo512.png
Binary file
A src/main/resources/com/keenwrite/logo64.png
Binary file
A src/main/resources/com/keenwrite/messages.properties
1
# ########################################################################
2
# Main Application Window
3
# ########################################################################
4
5
# suppress inspection "UnusedProperty" for whole file
16
7
# #####################
A src/main/resources/com/keenwrite/preview/webview.css
1
body,h1,h2,h3,h4,h5,h6,ol,p,ul{margin:0;padding:0}img{max-width:100%;height:auto}table{table-collapse:collapse;table-spacing:0;border-spacing:0}
12
3
/* Do not use points (pt): FlyingSaucer on Debian fails to render. */
4
body {
5
  color: #454545;
6
  background-c (" attr(data-type) " reference)
A src/main/resources/com/keenwrite/preview/webview_ja-Jpan-JP.css
1
body {
2
  font-family: 'Noto Serif CJK JP';
3
}
4
5
pre, code, tt {
6
  font-family: 'Noto Sans Mono CJK JP';
7
}
18
A src/main/resources/com/keenwrite/preview/webview_ko-Kore-KR.css
1
body {
2
  font-family: 'Noto Serif CJK KR';
3
}
4
5
pre, code, tt {
6
  font-family: 'Noto Sans Mono CJK KR';
7
}
18
A src/main/resources/com/keenwrite/preview/webview_zh-Hans-CN.css
1
body {
2
  font-family: 'Noto Serif CJK SC';
3
}
4
5
pre, code, tt {
6
  font-family: 'Noto Sans Mono CJK SC';
7
}
18
A src/main/resources/com/keenwrite/preview/webview_zh-Hans-SG.css
1
body {
2
  font-family: 'Noto Serif CJK SC';
3
}
4
5
pre, code, tt {
6
  font-family: 'Noto Sans Mono CJK SC';
7
}
18
A src/main/resources/com/keenwrite/preview/webview_zh-Hant-HK.css
1
body {
2
  font-family: 'Noto Serif CJK SC';
3
}
4
5
pre, code, tt {
6
  font-family: 'Noto Sans Mono CJK HK';
7
}
18
A src/main/resources/com/keenwrite/preview/webview_zh-Hant-TW.css
1
body {
2
  font-family: 'Noto Serif CJK TC';
3
}
4
5
pre, code, tt {
6
  font-family: 'Noto Sans Mono CJK TC';
7
}
18
A src/main/resources/com/keenwrite/settings.properties
1
# suppress inspection "UnusedProperty" for whole file
2
3
# ########################################################################
4
# Application
5
# ########################################################################
6
7
application.title=keenwrite
8
application.package=com/${application.title}
9
application.messages= com.${application.title}.messages
10
11
# Suppress multiple file modified notifications for one logical modification.
12
# Given in milliseconds.
13
application.watchdog.timeout=50
14
15
# ########################################################################
16
# Preferences
17
# ########################################################################
18
19
preferences.root=com.${application.title}
20
preferences.root.state=state
21
preferences.root.options=options
22
preferences.root.definition.source=definition.source
23
24
# ########################################################################
25
# File and Path References
26
# ########################################################################
27
28
file.stylesheet.application.dir=${application.package}/skins
29
file.stylesheet.application.base=${file.stylesheet.application.dir}/scene.css
30
file.stylesheet.application.skin=${file.stylesheet.application.dir}/{0}.css
31
file.stylesheet.markdown=${application.package}/editor/markdown.css
32
# {0} language code, {1} script code, {2} country code
33
file.stylesheet.markdown.locale=${application.package}/editor/markdown_{0}-{1}-{2}.css
34
file.stylesheet.xml=${application.package}/xml.css
35
36
# Preview styles are loaded statically through a class's classloader.
37
file.stylesheet.preview=webview.css
38
# {0} language code, {1} script code, {2} country code
39
file.stylesheet.preview.locale=webview_{0}-{1}-{2}.css
40
41
file.logo.16=${application.package}/logo16.png
42
file.logo.32=${application.package}/logo32.png
43
file.logo.128=${application.package}/logo128.png
44
file.logo.256=${application.package}/logo256.png
45
file.logo.512=${application.package}/logo512.png
46
47
# Default file name when a new file is created.
48
# This ensures that the file type can always be
49
# discerned so that the correct type of variable
50
# reference can be inserted.
51
file.default.document.prefix=untitled
52
file.default.document.suffix=md
53
file.default.document=${file.default.document.prefix}.${file.default.document.suffix}
54
file.default.definition=variables.yaml
55
56
# Default file name to be replaced by the most
57
# recently exported file name.
58
file.default.pdf=untitled.pdf
59
60
# ########################################################################
61
# File name Extensions
62
# ########################################################################
63
64
# Comma-separated list of definition file name extensions.
65
definition.file.ext.json=*.json
66
definition.file.ext.toml=*.toml
67
definition.file.ext.yaml=*.yml,*.yaml
68
definition.file.ext.properties=*.properties,*.props
69
70
# Comma-separated list of file name extensions.
71
file.ext.rmarkdown=*.Rmd
72
file.ext.rxml=*.Rxml
73
file.ext.source=*.md,*.markdown,*.mkdown,*.mdown,*.mkdn,*.mkd,*.mdwn,*.mdtxt,*.mdtext,*.text,*.txt,${file.ext.rmarkdown},${file.ext.rxml}
74
file.ext.definition=${definition.file.ext.yaml}
75
file.ext.xml=*.xml,${file.ext.rxml}
76
file.ext.all=*.*
77
78
# File name extension search order for images.
79
file.ext.image.order=svg pdf png jpg tiff
80
81
# ########################################################################
82
# Variable Name Editor
83
# ########################################################################
84
85
# Maximum number of characters for a variable name. A variable is defined
86
# as one or more non-whitespace characters up to this maximum length.
87
editor.variable.maxLength=256
88
89
# ########################################################################
90
# Dialog Preferences
91
# ########################################################################
92
93
dialog.alert.button.order.mac=L_HE+U+FBIX_NCYOA_R
94
dialog.alert.button.order.linux=L_HE+UNYACBXIO_R
95
dialog.alert.button.order.windows=L_E+U+FBXI_YNOCAH_R
96
97
# Ensures a consistent button order for alert dialogs across platforms (because
98
# the default button order on Linux defies all logic).
99
dialog.alert.button.order=${dialog.alert.button.order.windows}
1100
A src/main/resources/com/keenwrite/skins/count_darcula.css
1
.root {
2
  -fx-base: rgb( 43, 43, 43 );
3
  -fx-background: -fx-base;: -fx-base;
4
187, 187, 187 );
5
  -fx-mid-text-color: derive( -fx-base, 100%derive( -fx-base, 25% );
6
  -fx-text-foreground: -fx-light-text-color;
7
  -fx-text-background7.5% );
8
  -fx-text-selection45% );
9
10
  /* Make controls ( buttons, thumb, etc. ) slightly lighter */
11
  -fx-color: derive( -fx-base20% );
12
}
113
14
.caret {
15
  -fx-stroke: -fx-accentfx-light-text-color;
16
  -fx-fill:hover {
17
  -fx-effect: dropshadow( three-pass-box, rgba( 0, 0, 0, 0.2 ), 4, 0, 0, 0 ) -fx-light-text-color
A src/main/resources/com/keenwrite/skins/haunted_grey.css
1
/* https://stackoverflow.com/a/58441758/59087
2
 */
3
.root { 
4
  -fx-accent: #1e74c6;
5
  -fx-focus-color: -fx-accent;
6
  -fx-base: #373e43;
7
  -fx-control-inner-background: derive( -fx-base, 35% );
8
  -fx-control-inner-background-alt: -fx-control-inner-background;
9
10
  -fx-light-text-color: derive( -fx-base, 150% );
11
  -fx-mid-text-color: derive( -fx-base, 100% );
12
  -fx-dark-text-color: derive( -fx-base, 25% );
13
  -fx-text-foreground: -fx-light-text-color;
14
  -fx-text-background: derive( -fx-control-inner-background, 7.5% );
15
  -fx-text-selection: derive( -fx-control-inner-background, 45% );
16
}
17
18
.glyph-icon {
19
  -fx-text-fill: -fx-light-text-color;
20
  -fx-fill: -fx-light-text-color;
21
}
22
23
.glyph-icon:hover {
24
  -fx-effect: dropshadow( three-pass-box, rgba( 0, 0, 0, 0.2 ), 4, 0, 0, 0 );
25
}
26
27
.label {
28
  -fx-text-fill: -fx-light-text-color;
29
}
30
31
.text-field {
32
  -fx-prompt-text-fill: gray;
33
}
34
35
.button {
36
  -fx-focus-traversable: false;
37
}
38
39
.button:hover {
40
  -fx-text-fill: white;
41
}
42
43
.separator *.line { 
44
  -fx-background-color: #3C3C3C;
45
  -fx-border-style: solid;
46
  -fx-border-width: 1px;
47
}
48
49
.scroll-bar {
50
  -fx-background-color: derive( -fx-base, 45% );
51
}
52
53
.button:default {
54
  -fx-base: -fx-accent;
55
} 
56
57
.table-view {
58
  -fx-selection-bar-non-focused: derive( -fx-base, 50% );
59
}
60
61
.table-view .column-header .label {
62
  -fx-alignment: CENTER_LEFT;
63
  -fx-font-weight: none;
64
}
65
66
.list-cell:even,
67
.list-cell:odd,
68
.table-row-cell:even,
69
.table-row-cell:odd {  
70
  -fx-control-inner-background: derive( -fx-base, 15% );
71
}
72
73
.list-cell:empty,
74
.table-row-cell:empty {
75
  -fx-background-color: transparent;
76
}
77
78
.list-cell,
79
.table-row-cell {
80
  -fx-border-color: transparent;
81
  -fx-table-cell-border-color: transparent;
82
}
83
84
/* Avoid clipping text descenders in statistics table row. */
85
.table-row-cell {
86
  -fx-cell-size: 30px;
87
}
188
A src/main/resources/com/keenwrite/skins/modena_dark.css
1
/* https://github.com/joffrey-bion/javafx-themes/blob/master/css/modena_dark.css
2
 */
3
.root {
4
  -fx-base: rgb( 50, 50, 50 );
5
  -fx-background: -fx-base;
16
7
  /* Make controls ( buttons, thumb, etc. ) slightly lighter */
8
  -fx-color: derive( -fx-base      
A src/main/resources/com/keenwrite/skins/modena_light.css
1
.root {
2
  -fx-text-foreground: -fx-dark-text-color;
3
  -fx-text-background: derive( -fx-accent, 124% );
4
  -fx-text-selection: #a6d2ff;
5
}
16
A src/main/resources/com/keenwrite/skins/monokai.css
1
/*
2
 * Theme contributed by mery6299
3
 *
4
 * https://github.com/mery6299
5
 */
6
.root { 
7
  -fx-accent: #75715E;
8
  -fx-focus-color: -fx-accent;
9
  -fx-base: #262626;
10
  -fx-control-inner-background: -fx-base;
11
  -fx-control-inner-background-alt: -fx-control-inner-backgroundtheme-text-selection: #78dce8;
12
  -theme-search-selection: #ffd8662theme-text-selection, -50% );
13
}
14
15
/* Caret colour */
16
.styled-text-area .caret {
17
  -fx-stroke: white;
18
}
19
20
/* Spelling errors */
21
.markdown .spelling {
22
  -rtfx-underline-color: #fc9867;
23
}
24
25
/* Search result */
26
.markdown .search {
27
  -rtfx-background-color: derive( -theme-search-selection, -2derive( -fx-accent, -25% )
28
/* Toolbar */
29
.tool-bar .button:hoveaccent, -25% );
30
  -fx-color: -fx-hover-base;
31
}
32
33
/* Tabs */
34
.tab-pane *.tab-header-background {
35
	-fx-background-color: -fx-base;
36
}
37
38
.tab:selected {
39
	-fx-background-color: derive( #A9DC76, -30% );
40
}
41
142
A src/main/resources/com/keenwrite/skins/scene.css
1
.tool-bar {
2
  -fx-spacing: 0;
3
}
4
5
.tool-bar .button {
6
  -fx-background-color: transparent;
7
}
8
9
.tool-bar .button:hover {
10
  -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border, -fx-inner-border, -fx-body-color;
11
  -fx-color: -fx-hover-base;
12
}
13
14
.tool-bar .button:armed {
15
  -fx-color: -fx-pressed-base;
16
}
17
18
/* Definition editor drag and drop target.
19
 */
20
.drop-target {
21
  -fx-border-color: #eea82f;
22
  -fx-border-width: 0 0 2 0;
23
  -fx-padding: 3 3 1 3
24
}
125
A src/main/resources/com/keenwrite/skins/silver_cavern.css
1
/* https://toedter.com/2011/10/26/java-fx-2-0-css-styling/
2
 */
3
.root {
4
  -fx-base: rgb( 50, 50, 50 );
5
  -fx-background: -fx-base;
6
  -fx-control-inner-background: -fx-base;
17
8
 : derive( -fx-base, 150% );
9
  -fx-mid-text-color: derive( -fx-        
A src/main/resources/com/keenwrite/skins/solarized_dark.css
1
/* https://ethanschoonover.com/solarized
2
 */
3
.root {
4
  /* Solarized: base03 */
5
  -fx-base: rgb( 0, 43, 54 );
6
  -fx-background: -fx-base;
7
8
  /* Brighten controls */
9
  -fx-40% );
110
11
  -fx-control-inner-background: -fx-base;
12
 , 5
A src/main/resources/com/keenwrite/skins/vampire_byte.css
1
/* https://github.com/Col-E/Recaf/blob/master/src/main/resources/style/ui-dark.css
2
 */
3
.root {
4
  -fx-base: rgb( 45, 45, 46 );
5
  -fx-background: -fx-base;
6
7
  /* Brighten controls */
8
  -fx-color: derive( -fx-base, -40% );
19
10
  /* Control background */
11
      /* Text colors */
12
             /* Accent colors */
13
  -fx-accent: rgb( 51, 51, 52 );
14
              -item {
15
      -fx-border-insets: -1;
16
    {
17
  -fx-min-width: 80px;
18
    -fx-border-insets: -1;
19
        -fx-opacity: 1.0;
20
              -fx-padding: 2 2 1 2;
21
  -fx-border-insets: 0;
22
  1 1;
23
      -fx-padding: 2;
24
  -fx-border-insets: 0;
25
          6, 56, 59 );
26
        -fx-padding: -1;
27
        -fx-padding: -1;
28
  -fx-border-insets: 0;
29
  1 0 1;
30
      -fx-padding: 2;
31
        -fx-padding: 0;
32
      1 0 0;
33
                      hyperlink {
34
                                          
A src/main/resources/com/keenwrite/ui/fonts/icons/3g2.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/3ga.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/3gp.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/7z.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/aa.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/aac.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ac.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M56.633 44.984V63.27c0 .363-.367.73-.735.73H1.102c-.368 0-.735-.367-.735-.73V61.62c0-.363.367-.73.735-.73h2.39l28.5-28.344L2.391 3.109c-.184-.183-.184-.367-.184-.55V.73c0-.363.367-.73.734-.73h52.957c.368 0 .735.367.735.73v18.106c0 .363-.367.73-.735.73h-2.023c-.367 0-.734-.367-.734-.73 0-7.684-4.598-14.082-12.688-14.082H19.121l24.09 24.137c.367.367.367.73 0 1.097L19.676 53.395h20.777c5.516 0 10.297-3.473 12.133-8.594.184-.367.367-.551.738-.551h2.758c.367 0 .55.367.55.734zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/accdb.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M66.824 40.465c-.176 1.59-2.097 2.824-5.945 3.707-3.852.879-8.75 1.41-14.52 1.41h-4.898v9.172c1.574.176 3.324.176 4.898.176 5.77 0 10.668-.528 14.52-1.586 3.848-1.059 5.945-2.293 6.121-3.707-.176-.352-.176-9.172-.176-9.172zm-20.64-6.7c-1.75 0-3.325 0-4.899-.18v9.352h4.899c5.773 0 10.671-.53 14.52-1.59s5.944-2.292 5.944-3.702v-8.997c-.171 1.586-2.097 2.825-6.12 3.704-3.673 1.058-8.571 1.59-14.344 1.414zm0-11.468c-1.75 0-3.325 0-4.899-.176v9.352c1.574.175 3.324.175 4.899.175 5.773 0 10.671-.53 14.695-1.59C64.727 29 66.824 27.767 67 26.356V17.36c-.176 1.59-2.098 2.825-6.121 3.704-4.024.707-8.922 1.234-14.695 1.234zm0-13.05c-1.75 0-3.325 0-4.899.175v10.406c1.574.176 3.324.176 4.899.176 5.773 0 10.671-.527 14.695-1.586 3.848-1.059 5.945-2.293 6.121-3.703-.176-1.59-2.098-2.824-6.121-3.883-4.024-1.055-8.922-1.41-14.695-1.586zM18.02 23.886c-.176.527-.528 2.293-1.227 5.293l-1.223 5.113h5.07l-1.222-5.113c-.7-3-1.227-4.766-1.227-5.293zM0 7.129v49.918l37.785 6.527V.426zm23.09 37.219-1.399-5.645-6.996-.176-1.398 5.29-4.375-.352 6.648-23.813 5.07-.351 7.348 25.222zm0 0" fill="#a03537"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/accdt.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M66.824 40.465c-.176 1.59-2.097 2.824-5.945 3.707-3.852.879-8.75 1.41-14.52 1.41h-4.898v9.172c1.574.176 3.324.176 4.898.176 5.77 0 10.668-.528 14.52-1.586 3.848-1.059 5.945-2.293 6.121-3.707-.176-.352-.176-9.172-.176-9.172zm-20.64-6.7c-1.75 0-3.325 0-4.899-.18v9.352h4.899c5.773 0 10.671-.53 14.52-1.59s5.944-2.292 5.944-3.702v-8.997c-.171 1.586-2.097 2.825-6.12 3.704-3.673 1.058-8.571 1.59-14.344 1.414zm0-11.468c-1.75 0-3.325 0-4.899-.176v9.352c1.574.175 3.324.175 4.899.175 5.773 0 10.671-.53 14.695-1.59C64.727 29 66.824 27.767 67 26.356V17.36c-.176 1.59-2.098 2.825-6.121 3.704-4.024.707-8.922 1.234-14.695 1.234zm0-13.05c-1.75 0-3.325 0-4.899.175v10.406c1.574.176 3.324.176 4.899.176 5.773 0 10.671-.527 14.695-1.586 3.848-1.059 5.945-2.293 6.121-3.703-.176-1.59-2.098-2.824-6.121-3.883-4.024-1.055-8.922-1.41-14.695-1.586zM18.02 23.886c-.176.527-.528 2.293-1.227 5.293l-1.223 5.113h5.07l-1.222-5.113c-.7-3-1.227-4.766-1.227-5.293zM0 7.129v49.918l37.785 6.527V.426zm23.09 37.219-1.399-5.645-6.996-.176-1.398 5.29-4.375-.352 6.648-23.813 5.07-.351 7.348 25.222zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ace.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/adn.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M66.824 40.465c-.176 1.59-2.097 2.824-5.945 3.707-3.852.879-8.75 1.41-14.52 1.41h-4.898v9.172c1.574.176 3.324.176 4.898.176 5.77 0 10.668-.528 14.52-1.586 3.848-1.059 5.945-2.293 6.121-3.707-.176-.352-.176-9.172-.176-9.172zm-20.64-6.7c-1.75 0-3.325 0-4.899-.18v9.352h4.899c5.773 0 10.671-.53 14.52-1.59s5.944-2.292 5.944-3.702v-8.997c-.171 1.586-2.097 2.825-6.12 3.704-3.673 1.058-8.571 1.59-14.344 1.414zm0-11.468c-1.75 0-3.325 0-4.899-.176v9.352c1.574.175 3.324.175 4.899.175 5.773 0 10.671-.53 14.695-1.59C64.727 29 66.824 27.767 67 26.356V17.36c-.176 1.59-2.098 2.825-6.121 3.704-4.024.707-8.922 1.234-14.695 1.234zm0-13.05c-1.75 0-3.325 0-4.899.175v10.406c1.574.176 3.324.176 4.899.176 5.773 0 10.671-.527 14.695-1.586 3.848-1.059 5.945-2.293 6.121-3.703-.176-1.59-2.098-2.824-6.121-3.883-4.024-1.055-8.922-1.41-14.695-1.586zM18.02 23.886c-.176.527-.528 2.293-1.227 5.293l-1.223 5.113h5.07l-1.222-5.113c-.7-3-1.227-4.766-1.227-5.293zM0 7.129v49.918l37.785 6.527V.426zm23.09 37.219-1.399-5.645-6.996-.176-1.398 5.29-4.375-.352 6.648-23.813 5.07-.351 7.348 25.222zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ai.svg
1
1
<svg height="64" width="66" xmlns="http://www.w3.org/2000/svg"><path d="M40.266 62.762 34.922 45.55h-19.98l-5.34 17.21h-7.32L21.468 5.59h7.32l19.39 57.172zM26.813 19.438c-.594-2.176-1.387-5.145-1.583-7.32h-.199c-.394 1.98-.988 4.945-1.781 7.32L16.523 41H33.54zm33.039-9.891c-2.375 0-4.356-1.781-4.356-4.156s1.98-4.153 4.356-4.153c2.37 0 4.351 1.778 4.351 4.153 0 2.375-1.98 4.156-4.351 4.156zm-3.563 53.215V18.05h7.32v44.71zm0 0" fill="#fea500" stroke="#fea500" stroke-miterlimit="10" stroke-width="2.47295"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/aif.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/aifc.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/aiff.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ait.svg
1
1
<svg height="64" width="66" xmlns="http://www.w3.org/2000/svg"><path d="M40.266 62.762 34.922 45.55h-19.98l-5.34 17.21h-7.32L21.468 5.59h7.32l19.39 57.172zM26.813 19.438c-.594-2.176-1.387-5.145-1.583-7.32h-.199c-.394 1.98-.988 4.945-1.781 7.32L16.523 41H33.54zm33.039-9.891c-2.375 0-4.356-1.781-4.356-4.156s1.98-4.153 4.356-4.153c2.37 0 4.351 1.778 4.351 4.153 0 2.375-1.98 4.156-4.351 4.156zm-3.563 53.215V18.05h7.32v44.71zm0 0" fill="#fea500" stroke="#fea500" stroke-miterlimit="10" stroke-width="2.47295"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/amr.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ani.svg
1
1
<svg height="64" width="53" xmlns="http://www.w3.org/2000/svg"><path d="M49.023.375H3.977C1.703.375 0 2.07 0 4.328 0 6.59 1.703 8.281 3.977 8.281h.187v3.953c0 8.286 5.11 15.625 12.684 18.637.379.188.757.563.757.942v.375c0 .378-.378.753-.757.94C9.086 36.142 3.977 43.48 4.164 51.767v3.953h-.187C1.703 55.719 0 57.41 0 59.672c0 2.258 1.703 3.953 3.977 3.953h45.046c2.274 0 3.977-1.695 3.977-3.953 0-2.262-1.703-3.953-3.977-3.953h-.187v-3.953c0-8.286-5.11-15.625-12.684-18.637-.379-.188-.757-.563-.757-.941v-.376c0-.378.378-.753.757-.94 7.762-3.013 12.871-10.352 12.684-18.638V8.281h.187C51.297 8.281 53 6.59 53 4.328 53 2.07 51.297.375 49.023.375zm-5.488 11.86c0 6.023-3.785 11.484-9.465 13.742-2.46.941-4.164 3.199-4.164 5.836v.375c0 2.636 1.703 4.894 4.164 5.835 5.68 2.258 9.465 7.72 9.465 13.743v3.953H9.465v-3.953c0-6.024 3.785-11.485 9.465-13.743 2.46-.941 4.164-3.199 4.164-5.836v-.374c0-2.637-1.703-4.895-4.164-5.836-5.68-2.258-9.465-7.72-9.465-13.743V8.281h34.07zm-28.77 6.777c-.378-.567-.19-1.317.38-1.696.187-.187.375-.187.753-.187H37.29c.566 0 1.137.566 1.137 1.129 0 .187 0 .566-.192.754-1.324 1.883-3.027 3.199-5.109 3.953-2.27.754-3.977 2.445-5.11 4.515-.378.754-1.328 1.133-2.081.567-.192-.188-.57-.375-.57-.567-1.134-2.07-2.84-3.761-5.11-4.515-2.274-.942-4.164-2.258-5.488-3.953zM29.907 42.73a6.64 6.64 0 0 0 4.164 1.504c2.84 0 5.301 1.883 5.868 4.707v.188c.19.566.19 1.129.19 1.883s-.565 1.316-1.323 1.316H14.008c-.758 0-1.324-.562-1.324-1.316 0-.567.187-1.317.187-1.883v-.188c.758-2.636 3.219-4.52 6.059-4.52a6.64 6.64 0 0 0 4.164-1.503c.758-.754 1.511-1.508 1.89-2.45.38-.562.95-.937 1.703-.75.57.188.95.376 1.137.75.57.755 1.137 1.509 2.082 2.262zm0 0" fill="#8ed200"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/apk.svg
1
1
<svg height="64" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M10.867 48.152c0 1.551 1.04 2.582 2.602 2.582h2.773v9.305c0 2.066 1.91 3.961 3.992 3.961s3.989-1.895 3.989-3.96v-9.306h5.379v9.305c0 2.066 1.91 3.961 3.992 3.961s3.988-1.895 3.988-3.96v-9.306h2.602c1.562 0 2.605-1.03 2.605-2.757V21.449H10.867zM4.097 21.45C2.017 21.45.11 23.344.11 25.41v18.606c0 2.066 1.907 3.96 3.989 3.96s3.992-1.894 3.992-3.96V25.41c0-2.066-1.735-3.96-3.992-3.96zm45.805 0c-2.082 0-3.992 1.895-3.992 3.961v18.606c0 2.066 1.91 3.96 3.992 3.96s3.989-1.894 3.989-3.96V25.41c0-2.066-1.907-3.96-3.989-3.96zM36.367 5.945l3.473-3.449c.52-.516.52-1.375 0-1.894a1.373 1.373 0 0 0-1.91 0L33.94 4.566c-1.91-1.379-4.34-1.894-6.941-1.894-2.777 0-5.031.515-7.285 1.55L15.898.259c-.523-.344-1.562-.344-2.082 0-.347.515-.347 1.55 0 2.066l3.47 3.446c-3.817 2.93-6.419 7.41-6.419 12.75h32.266c0-5.168-2.602-9.82-6.766-12.575zm-14.746 7.407h-2.773v-2.586h2.773zm13.531 0H32.38v-2.586h2.773zm0 0" fill="#a4ca39"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/app.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.887 38.266c-2.684-.84-4.532-3.36-4.532-6.215 0-2.852 1.848-5.371 4.532-6.211.84-.336 1.343-1.172 1.008-2.012-.84-3.023-1.848-5.707-3.524-8.394-.504-.84-1.344-1.008-2.184-.672-1.007.504-2.015.84-3.19.84-3.692 0-6.548-3.024-6.548-6.547 0-1.176.336-2.184.84-3.188.504-.84.168-1.68-.672-2.183a40.47 40.47 0 0 0-8.39-3.528c-.84-.168-1.68.168-2.016 1.008C37.37 3.852 34.855 5.7 32 5.7s-5.371-1.847-6.21-4.535C25.452.324 24.612-.18 23.772.156c-3.02.84-5.707 1.848-8.39 3.528-.84.503-1.008 1.343-.672 2.183.504 1.004.84 2.012.84 3.188 0 3.691-3.024 6.547-6.547 6.547-1.176 0-2.184-.336-3.191-.84-.84-.504-1.68-.168-2.184.672a40.699 40.699 0 0 0-3.524 8.394c-.167.84.168 1.676 1.008 2.012 2.684.84 4.532 3.36 4.532 6.21 0 2.856-1.848 5.376-4.532 6.216-.84.332-1.343 1.172-1.008 2.011.84 3.024 1.848 5.707 3.524 8.395.504.84 1.344 1.008 2.184.672 1.007-.504 2.015-.84 3.19-.84 3.692 0 6.548 3.02 6.548 6.547 0 1.176-.336 2.183-.84 3.187-.504.84-.168 1.68.672 2.184a40.47 40.47 0 0 0 8.39 3.527h.336c.672 0 1.344-.504 1.512-1.176.84-2.687 3.356-4.535 6.211-4.535s5.371 1.848 6.211 4.535c.336.84 1.176 1.344 2.016 1.008 3.02-.84 5.707-1.847 8.39-3.527.84-.504 1.008-1.344.672-2.184-.504-1.004-.84-2.011-.84-3.187 0-3.692 3.024-6.547 6.547-6.547 1.176 0 2.184.336 3.192.84.84.504 1.68.168 2.183-.672a40.698 40.698 0 0 0 3.524-8.395c.503-.672 0-1.511-.84-1.843zm-30.719 3.691c-5.371 0-9.902-4.363-9.902-9.906 0-5.371 4.363-9.903 9.902-9.903 5.371 0 9.902 4.364 9.902 9.903 0 5.375-4.53 9.906-9.902 9.906zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/applescript.svg
1
1
<svg height="64" width="68" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M51.6 33H28.3c-3.2 0-5.9 2.601-5.9 5.9v26.3h-5.9c0 3.2 2.599 5.9 5.9 5.9h23.4c3.2 0 5.9-2.601 5.9-5.9V41.7h5.9v-2.9a5.98 5.98 0 0 0-6-5.8zm-2.9 31.6c0 1.9-1.5 3.4-3.4 3.4H23.9c1.399-1 1.399-2.9 1.399-2.9V38.9c0-1.6 1.3-2.9 2.902-2.9 1.598 0 2.899 1.3 2.899 2.9v2.901h17.6zM34.1 38.9V36h17.6c2.7 0 2.9 1.7 2.9 2.9zm0 0" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.62485 0 0 1.6469 -26.2 -53.722)"/><path d="M28.719 46.082c-.489-.332-.973-.824-1.461-1.32-.488-.492-.813-1.153-1.137-1.645-.812-1.316-1.625-2.637-1.953-4.117-.484-1.648-.813-3.293-.813-4.941 0-1.813.329-3.293 1.141-4.61.484-.988 1.297-1.976 2.438-2.472a5.777 5.777 0 0 1 3.246-.989c.328 0 .812 0 1.3.164.325.164.653.164 1.137.496.653.164.977.329 1.14.493.325.164.65.164.973.164.165 0 .489 0 .653-.164.16 0 .484-.164.972-.328s.813-.329 1.137-.329c.488-.168.813-.168 1.137-.332.488 0 .812-.164 1.3 0 .813 0 1.466.164 2.278.496 1.137.493 2.11 1.153 2.762 2.305-.324.164-.489.328-.813.66-.488.492-.976 1.153-1.465 1.645-.484.824-.812 1.812-.648 2.965 0 1.316.324 2.304.977 3.293.484.66.972 1.32 1.785 1.812l.976.496c-.164.492-.324.82-.488 1.317-.324.988-.813 1.812-1.461 2.632-.488.66-.812 1.32-1.14 1.649l-1.297 1.316a3.095 3.095 0 0 1-1.625.496c-.329 0-.813 0-1.141-.164-.324-.164-.649-.164-.973-.332-.324-.164-.648-.328-.976-.328-.485-.164-.813-.164-1.297-.164-.488 0-.977 0-1.301.164-.324.164-.652.164-.977.328-.488.168-.812.332-.972.332-.328.164-.813.164-1.14.164-1.298-.66-1.786-.824-2.274-1.152zm7.636-21.246c-.812.328-1.46.492-2.273.492-.164-.656 0-1.48.324-2.305.324-.656.649-1.316 1.137-1.976a5.093 5.093 0 0 1 1.789-1.48c.813-.329 1.461-.66 2.11-.66.163.823 0 1.484-.325 2.304-.324.66-.648 1.484-1.137 1.977-.324.824-.812 1.32-1.625 1.648zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/asax.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/asc.svg
1
1
<svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M0 61.766V2.234C0 1.004 1 0 2.223 0h53.332c.668 0 1.222.223 1.668.781l22.222 24.922c.332.445.555.89.555 1.45v34.613C80 62.996 79 64 77.777 64H2.223A2.234 2.234 0 0 1 0 61.766zm75.555-33.72-21-23.577H4.445V59.53h71.11zm0 0"/><path d="M53.332 29.055V4.469c0-1.227 1-2.235 2.223-2.235a2.236 2.236 0 0 1 2.222 2.235V26.82h17.778a2.234 2.234 0 0 1 0 4.47h-20a2.236 2.236 0 0 1-2.223-2.235zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ascx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/asf.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ash.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ashx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/asm.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/asmx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/asp.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#c33"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/aspx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/asx.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/au.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/aup.svg
1
1
<svg height="64" width="68" xmlns="http://www.w3.org/2000/svg"><path d="M34 0c-7.133 0-13.672 1.98-18.625 5.547S7.051 14.266 7.051 20.21v8.719C3.09 31.902.316 38.242.316 45.574.316 55.68 5.664 64 12.203 64c1.586 0 3.371-.594 4.953-1.586V28.73c-.988-.593-2.18-1.187-3.367-1.386V20.21c0-3.566 1.785-6.738 5.352-9.113C22.707 8.52 28.055 6.738 34 6.738s11.098 1.782 14.86 4.36c3.566 2.574 5.35 5.746 5.35 9.113v6.934c-1.187.398-2.378.793-3.366 1.386v33.29c1.582.992 3.367 1.585 4.953 1.585 6.34 0 11.887-8.324 11.887-18.43 0-6.933-2.774-13.273-6.735-16.246v-8.52c0-5.944-3.37-11.292-8.324-14.663C47.672 2.18 41.133 0 34 0zm0 29.723-7.332 14.465-4.555-5.946-1.586 2.38v11.093l1.586-1.98 5.746 7.331L34 44.582l6.14 12.484 5.747-7.332 1.586 1.98V40.622l-1.586-2.379-4.356 6.14zm0 0" fill="#1493f6"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/avi.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/axd.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/aze.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M34.625 63.98c14.355-12.652 1.95-28.816-1.773-30.574.53 1.934.355 5.27-1.594 7.203-.887-4.918-4.785-11.07-10.278-13.18.883 7.032-3.19 11.95-4.964 14.41-1.418 2.286-9.922 14.06-1.418 22.141-20.38-6.328-15.239-26.535-9.391-35.496C11.41 19.172 18.676 11.617 17.078.02c9.926 3.515 16.66 13.882 18.434 21.789 3.367-3.164 3.898-8.786 3.011-11.95 6.914 2.813 28.536 41.47-3.898 54.121zm0 0" fill="#ff9800"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bak.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M0 0v36.57h13.715V13.715H36.57V0zm0 0"/><path d="M18.285 18.285V64H64V18.285zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bash.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bat.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M64 0H0v64h64zM12.8 12.633H6.399V6.23h6.403zm44.802 0h-38.57V6.23h38.57zm0 44.797H6.23V19.2h51.372zm0 0"/><path d="m16.336 24.59-4.547 4.547 7.41 7.41-7.41 7.242 4.547 4.547 11.957-11.79zm10.613 21.558h12.797v6.399H26.95zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bin.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M17.8.5c-2.9 0-5.4 2.801-5.4 6.2 0 3.4 2.4 6.2 5.4 6.2 2.9 0 5.399-2.8 5.399-6.2C23.199 3.302 20.8.5 17.8.5zm0 10.1c-1.6 0-3-1.7-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9-1.3 3.9-3 3.9zM7 11.8V1.7C7 1 6.5.5 5.8.5S4.6 1 4.6 1.7v10.1c0 .7.5 1.2 1.2 1.2S7 12.4 7 11.8zm-1.1 6.9C3 18.7.5 21.5.5 24.9s2.4 6.2 5.4 6.2 5.401-2.8 5.401-6.2c-.102-3.3-2.5-6.2-5.4-6.2zm0 10.2c-1.6 0-3-1.699-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9c-.1 2.1-1.4 3.9-3 3.9zM19 30V19.9c0-.7-.5-1.2-1.2-1.2s-1.2.5-1.2 1.2V30c0 .7.5 1.2 1.2 1.2S19 30.7 19 30zM31.3 12.7V2.6c0-.7-.499-1.2-1.2-1.2-.7 0-1.1.5-1.1 1.2v10.099c0 .701.5 1.2 1.2 1.2s1.1-.6 1.1-1.2zm-1.2 6.9c-2.9 0-5.401 2.8-5.401 6.2 0 3.4 2.4 6.202 5.4 6.202 2.901 0 5.402-2.802 5.402-6.202S33.1 19.6 30.1 19.6zm0 10.102c-1.6 0-3-1.7-3-3.902 0-2.099 1.3-3.9 3-3.9s3 1.7 3 3.9c0 2.202-1.3 3.902-3 3.902zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" transform="matrix(1.91667 0 0 1.9394 0 .485)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/blank.svg
1
1
<svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M0 61.766V2.234C0 1.004 1 0 2.223 0h53.332c.668 0 1.222.223 1.668.781l22.222 24.922c.332.445.555.89.555 1.45v34.613C80 62.996 79 64 77.777 64H2.223A2.234 2.234 0 0 1 0 61.766zm75.555-33.72-21-23.577H4.445V59.53h71.11zm0 0"/><path d="M53.332 29.055V4.469c0-1.227 1-2.235 2.223-2.235a2.236 2.236 0 0 1 2.222 2.235V26.82h17.778a2.234 2.234 0 0 1 0 4.47h-20a2.236 2.236 0 0 1-2.223-2.235zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bmp.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bowerrc.svg
1
1
<svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="c"><g filter="url(#a)"><path d="M0 0h72v64H0z" fill-opacity=".65"/></g></mask><clipPath id="b"><path d="M0 0h72v64H0z"/></clipPath><g clip-path="url(#b)" mask="url(#c)"><path d="M25.281.082h4.356l.168.168c1.34.34 2.843.508 4.183 1.18 4.524 1.855 7.203 5.39 8.875 9.937 0 .168.168.508.336.676 1.004.336 2.008.504 3.016.84l.164-.168c2.68-5.39 7.035-8.422 12.894-9.43 1.508-.34 3.016-.172 4.524.336-.168.168-.168.336-.336.336-2.012 2.191-3.516 4.547-3.852 7.41-.168 1.012-.336 1.852-.504 2.863-.5 2.528-1.34 5.055-3.347 6.907C53.246 23.497 50.062 24 46.883 24c-.168 0-.504-.168-.504-.504-.332-.508-.5-1.012-.668-1.687-2.176.507-2.848 1.011-3.016 2.863.672 0 1.34.172 2.012.172 5.691.672 11.387 1.515 16.91 2.691 2.68.676 5.192 1.348 7.703 2.528 1.34.675 2.012 1.683 2.344 3.199 0 .34.168.675.336.843v2.02c-.168.168-.168.508-.336.676-.332 1.011-.836 1.851-1.672 2.527-1.008.84-2.18 1.176-3.52 1.348.337 1.683-.667 2.691-1.84 3.367-.835.504-1.675.84-2.51 1.012-1.34.168-2.513 0-3.852 0-1.004 1.851-2.68 2.187-4.52 2.355-1.844.168-3.52-.336-5.023-1.347-.672.843-1.34 1.347-2.512 1.347-2.012.336-3.684-.168-5.36-.84l-.167-.168.167.168c1.172 2.188 2.344 4.211 3.348 6.399.336.676.336 1.683.336 2.36 0 1.515-.836 2.523-2.008 2.862-.168 1.18-.84 2.188-1.676 2.864-1.171.84-2.511 1.007-4.02 1.007-.167 0-.667-.167-.667 0-.672 1.012-1.844 1.348-2.848 1.856h-1.507c-1.004-.168-1.84-1.18-3.016-.34-.164.172-.5.172-.668.172-1.172 0-3.348-.676-3.852-2.36s-1.34-3.538-2.007-5.222l-.168.168c-1.508 3.035-4.856 4.21-8.04 2.695a14.392 14.392 0 0 1-4.351-3.367C1.67 46.402-1.508 35.621.668 23.496 2.676 12.043 11.551 3.286 22.77.926 23.44.422 24.445.25 25.28.082zm13.73 19.035c1.676.168 3.52.168 5.192-.676 1.508-.671 3.184-.671 4.688-.504 1.007.168 2.18.504 3.183.672-.504-.504-1.004-1.011-1.676-1.347-2.68-2.02-5.691-3.367-9.207-3.703-.336 0-.84 0-.84.168-1.003 1.515-1.671 3.03-1.671 4.882-.168 1.18.164 2.36.668 3.371 1.843-.843 3.851-1.687 5.691-2.527v-.508c-1.004.172-1.84.508-2.844.676-1.172.168-2.343.336-3.183-.504zM56.427 7.664c-2.176 2.356-3.684 5.05-5.024 7.746l3.684 3.2c1.176-1.516 1.508-3.2 2.012-4.883.668-3.032.836-6.235 3.18-8.59-4.352-.168-10.715 4.379-12.055 8.422l2.175 1.175c.168-.168.168-.504.336-.671.668-1.18 1.34-2.36 2.176-3.368 1.004-1.18 2.012-2.36 3.516-3.03zm-7.867 24.59c1.34.168 2.68.336 4.015.336l12.395 1.347c.668 0 1.172.168 1.84.168-.336-1.011-.836-1.851-1.676-2.19-.836-.337-1.84-.505-2.676-.84-3.684-.677-7.203-1.516-10.887-2.192-3.515-.504-7.199-.84-10.715-1.348-.167 0-.503 0-.671.172l-2.008 2.02c-3.684 2.695-7.703 3.367-12.055 3.367a25.933 25.933 0 0 0 3.348-1.18c3.515-1.344 6.531-3.2 8.707-6.23.336-.504.336-.84.168-1.348a12.356 12.356 0 0 1-1.508-5.895c0-2.527.836-4.714 2.68-6.566.336-.34.336-.676.168-1.012-1.172-2.695-2.848-4.715-5.36-6.234-3.015-1.852-6.363-2.02-9.543-1.516-7.535 1.18-13.23 5.055-17.25 11.285-3.683 5.895-4.855 12.297-4.015 19.204.332 2.187.836 4.21 1.84 6.062.167.504.335 1.012.671 1.516 4.52 8.254 11.551 12.968 20.93 13.64 5.527.34 10.383-2.695 12.055-7.914.336-1.18.84-2.36.168-3.539 1.172 1.012 2.68 1.516 4.355 1.688 1.672.168 3.012-.34 3.684-1.688.168 0 .332.172.5.172 1.34.672 2.68.84 4.187.672 1.844-.168 2.68-1.012 2.848-2.695.836.168 1.84.336 2.68.168 2.675 0 3.851-1.516 3.347-3.704 1.004 0 2.176 0 3.18-.167 1.008-.172 2.012-.676 2.68-1.856l-10.047-2.02-10.047-2.023c-.332.34 2.012.34 2.012.34zm-1.676-13.645c.336 1.012.504 1.852.668 2.696.168.504.336.504.84.504 1.672-.168 3.18-.504 4.52-1.176.167-.172.335-.172.839-.508l-4.02-1.008-.503.84h-.168v-1.012zm0 0" fill="#543828"/><path d="M5.02 40c-1.004-1.852-1.504-4.043-1.84-6.063-1.004-6.906.168-13.304 4.02-19.203 4.015-6.398 9.878-10.273 17.245-11.28 3.348-.509 6.532-.34 9.543 1.515 2.512 1.515 4.188 3.535 5.36 6.23.168.336.168.676-.168 1.012-1.672 1.851-2.512 4.043-2.68 6.566 0 2.024.504 4.043 1.508 5.895.336.508.168.844-.168 1.348-2.176 3.03-5.192 5.054-8.707 6.234-1.004.336-2.176.672-3.348 1.176 4.352 0 8.54-.672 12.055-3.368-.836 1.348-1.34 2.696-2.344 3.875-4.351 5.727-10.047 8.254-17.414 6.231-.668-.168-1.34-.336-2.008.336l-1.008.508c-3.18.84-6.695.504-10.046-1.012zm22.605-29.813c-3.516 0-6.195 2.696-6.195 6.403 0 3.367 2.847 6.23 6.363 6.23s6.195-2.863 6.195-6.398c0-3.54-2.843-6.402-6.363-6.235zm0 0" fill="#e95927"/><path d="M5.02 40c3.351 1.516 6.867 1.684 10.382.676.336-.172.836-.172 1.004-.508.672-.504 1.34-.504 2.012-.336 7.367 2.02 13.059-.504 17.414-6.23.836-1.18 1.508-2.528 2.344-3.875l2.008-2.02c.168-.172.504-.172.671-.172 3.516.508 7.2.844 10.715 1.348 3.684.676 7.203 1.347 10.887 2.191-.168 0-.336.168-.336.168-4.523.336-9.043.672-13.394 1.012h-2.348l10.047 2.02c-.164.335-.164 1.011-.332 1.18-.504.503-1.172 1.01-1.844 1.179-.836.168-1.676 0-2.512 0 .168.84 0 1.683-.668 2.355-.504.34-1.007.844-1.675 1.012-1.508.676-3.016.676-4.52-.168-.336 1.516-1.676 2.02-2.848 2.188-1.34.171-2.511 0-3.683-.84l-.168.168 1.004 2.359c.672 1.176.168 2.355-.168 3.535-1.672 5.223-6.528 8.422-12.055 7.918-9.379-.676-16.41-5.39-20.93-13.644-.672-.504-.84-1.012-1.007-1.516zm0 0" fill="#fbcd00"/><path d="M51.402 36.8c.84 0 1.844.169 2.512 0 .672-.167 1.34-.675 1.844-1.179.336-.168.336-.844.336-1.18l10.047 2.024c-.672 1.008-1.508 1.683-2.68 1.851-1.004.168-2.176.168-3.184.168.504 2.192-.668 3.707-3.347 3.707-.836 0-1.844-.171-2.68-.171-.168 1.687-1.004 2.527-2.848 2.695-1.504.168-2.843 0-4.183-.672 0-.336-.168-.336-.336-.336-.668 1.348-2.008 1.852-3.684 1.684s-3.015-.676-4.355-1.684l-1.004-2.36.168-.167c1.172.84 2.344 1.18 3.683.84 1.34-.168 2.512-.672 2.848-2.188 1.508.844 3.012.844 4.52.168.504-.168 1.171-.672 1.675-1.012.668-.504.836-1.347.668-2.187zm0 0" fill="#3daf00"/><path d="M56.426 7.664c-1.504.672-2.676 1.852-3.684 3.2-.836 1.011-1.504 2.187-2.176 3.366-.168.168-.168.336-.336.676l-2.175-1.18c1.34-4.21 7.703-8.59 12.054-8.421-2.343 2.527-2.343 5.726-3.18 8.59-.335 1.683-.835 3.367-2.007 4.882l-3.684-3.199c1.504-2.863 3.18-5.558 5.188-7.914.336-.172 0 0 0 0zm0 0" fill="#25a7f0"/><path d="M38.68 18.777c0-1.851.5-3.535 1.672-4.882.168-.168.671-.168.84-.168 3.515.168 6.359 1.683 9.206 3.703.504.34 1.172.843 1.676 1.347-1.004-.168-2.176-.504-3.183-.672-1.672-.335-3.18-.167-4.688.504-1.672.676-3.348.676-5.191.676zm0 0" fill="#cbcbca"/><path d="M48.559 32.254c4.52-.34 9.039-.676 13.394-1.012.168 0 .336 0 .336-.168.836.336 1.84.504 2.68.84 1.004.34 1.34 1.18 1.672 2.191-.668 0-1.34-.167-1.84-.167l-12.227-1.18zm0 0" fill="#3eae00"/><path d="m46.883 18.61 2.176.335v1.012h.168l.503-1.012 4.02 1.012c-.336.168-.504.336-.84.504-1.508.676-3.012 1.012-4.52 1.18-.335 0-.671 0-.84-.504-.163-.676-.5-1.516-.667-2.528zm0 0" fill="#25a5ec"/><path d="m38.68 18.777.164.34c1.008.84 2.18.84 3.183.672 1.004-.168 1.84-.504 2.848-.672v.504l-5.695 2.527c-.168-1.011-.5-2.191-.5-3.37zm0 0" fill="#c9c8c7"/><path d="M27.625 10.188c3.52 0 6.363 2.695 6.363 6.234 0 3.535-2.843 6.398-6.195 6.398-3.348 0-6.363-2.863-6.363-6.23 0-3.707 2.68-6.403 6.195-6.403zm3.684 6.234c0-2.192-1.672-3.875-3.684-3.875-2.008 0-3.684 1.683-3.852 3.875 0 2.02 1.676 3.871 3.688 3.871 2.344-.168 3.848-1.684 3.848-3.871zm0 0" fill="#fbcb00"/><path d="M56.426 7.664c.168.168 0 0 0 0zm0 0" fill="#543828"/><path d="M31.309 16.422c0 2.187-1.672 3.703-3.848 3.703-2.18 0-3.852-1.684-3.688-3.871 0-2.024 1.676-3.875 3.852-3.875 2.18.168 3.684 1.851 3.684 4.043zm-3.684-.168a8.37 8.37 0 0 0 1.844-.676c.668-.336.668-1.18 0-1.683-.836-.676-2.68-.676-3.516 0-.672.503-.672 1.347 0 1.683.5.336 1.004.504 1.672.676zm0 0" fill="#553928"/><path d="M27.625 16.254c-.668-.172-1.172-.34-1.672-.676-.672-.336-.672-1.18 0-1.683.836-.676 2.68-.676 3.516 0 .668.503.668 1.347 0 1.683a8.37 8.37 0 0 1-1.844.676zm0 0" fill="#fff"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bpg.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bz2.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/bzempty.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M47.734 32c8.914 12.195 3.325 28-11.785 31.46C28.695 52.927 40.33 43.59 47.734 32zM8.45 60.3l.453.302a18.118 18.118 0 0 0 9.215 3.011c-2.867-5.87-2.265-12.945 1.363-18.367l10.575-15.355c5.742-8.578 3.476-20.32-5.137-26.043l-.605-.45A18.106 18.106 0 0 0 15.098.387c2.87 6.02 2.265 12.945-1.36 18.367L3.16 34.109c-5.742 8.73-3.472 20.473 5.29 26.192zm27.047-17.76s3.02-4.067 4.531-6.474l4.383-6.476c5.438-7.977-4.082-14.3-5.289-14.45 1.207 2.407 0 7.376-1.512 9.786l-4.382 6.472c-2.414 3.762-1.508 8.73 2.27 11.141zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/c.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cab.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#4d1b9b"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cad.svg
1
1
<svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/caf.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cal.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M5.102 59.219v-10.11h11.605v10.11zm14.066 0v-10.11h12.836v10.11zM5.102 46.805V35.457h11.605v11.348zm14.066 0V35.457h12.836v11.348zM5.102 33.152V23.047h11.605v10.105zM34.645 59.22v-10.11H47.48v10.11zM19.168 33.152V23.047h12.836v10.105zm30.95 26.067v-10.11h11.605v10.11zM34.644 46.805V35.457H47.48v11.348zm-14.07-30.496c0 .53-.528 1.062-1.231 1.062h-2.637c-.703 0-1.23-.531-1.23-1.062V6.203c0-.535.527-1.066 1.23-1.066h2.461c.703 0 1.23.531 1.23 1.066V16.31zm29.542 30.496V35.457h11.606v11.348zM34.645 33.152V23.047H47.48v10.105zm15.472 0V23.047h11.606v10.105zm1.406-16.843c0 .53-.527 1.062-1.23 1.062h-2.637c-.703 0-1.23-.531-1.23-1.062V6.203c0-.535.527-1.066 1.23-1.066h2.637c.703 0 1.23.531 1.23 1.066zM67 14.004c0-2.484-2.285-4.434-5.102-4.434h-5.097V6.203c0-3.016-2.813-5.676-6.508-5.676h-2.637c-3.515 0-6.508 2.48-6.508 5.676V9.57H25.676V6.203c0-3.016-2.817-5.676-6.508-5.676h-2.637c-3.52 0-6.508 2.48-6.508 5.676V9.57H5.102C2.285 9.57 0 11.7 0 14.004v45.035c0 2.484 2.285 4.434 5.102 4.434h56.62c2.817 0 5.102-2.13 5.102-4.434V14.004zm0 0" fill="#111"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cdda.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M43.195 53.328v-24l13.93-2.308C55.008 15.11 44.605 6.043 32.09 6.043c-14.106 0-25.563 11.555-25.563 25.773 0 14.223 11.457 25.778 25.563 25.778 1.41 0 2.644-.18 4.055-.356 1.058-1.777 2.82-3.023 5.113-3.734.703-.176 1.41-.176 1.937-.176zM36.848 8.176l-2.82 12.09h-2.645l-1.938-12.09c2.82-1.422 7.403 0 7.403 0zM32.09 41.418c-5.29 0-9.52-4.266-9.52-9.602 0-5.332 4.23-9.597 9.52-9.597 5.289 0 9.52 4.265 9.52 9.597 0 5.336-4.231 9.602-9.52 9.602zm0-16.887c-4.055 0-7.227 3.2-7.227 7.285 0 4.09 3.172 7.29 7.227 7.29s7.226-3.2 7.226-7.29c0-4.086-3.171-7.285-7.226-7.285zm0 12.442c-2.82 0-5.113-2.309-5.113-5.157 0-2.843 2.293-5.152 5.113-5.152 2.82 0 5.113 2.309 5.113 5.152.176 2.848-2.293 5.157-5.113 5.157zm3.347 24.707c.18.71.356 1.246.708 1.957-1.41.175-2.645.355-4.055.355C14.637 63.992.355 49.594.355 31.996S14.637 0 32.09 0c15.512 0 28.558 11.375 31.203 26.129l-2.996.535C57.828 13.332 46.19 3.024 32.09 3.024 16.398 3.023 3.53 15.995 3.53 31.815c0 15.82 12.867 28.797 28.559 28.797 1.058 0 2.117 0 3.172-.175 0 .355 0 .886.175 1.242zm31.208-33.95v27.73c0 2.31-1.766 4.087-4.41 4.798-2.82.71-5.641-.531-6.344-2.664-.532-2.313 1.41-4.621 4.23-5.332 1.234-.356 2.645-.18 3.703.175V35.73l-14.808 2.665V59.19c0 1.957-1.766 3.91-4.235 4.621-2.82.711-5.816-.71-6.168-2.664-.531-2.312 1.41-4.62 4.23-5.332 1.235-.355 2.645-.18 3.704.176V31.105zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cer.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cfg.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cfm.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M11.427 34.125c-7.399 0-10.8-7.1-10.8-14.9-.099-7.7 3.3-14.9 10.8-14.9 2.3 0 4.2.6 5.7 1.6l-1 2.4c-1.1-.7-2.6-1.2-4.2-1.2-5.2 0-7.3 6-7.3 12.1 0 6 2.1 12 7.2 12 1.6 0 3.2-.5 4.2-1.2l1 2.5c-1.5 1-3.3 1.6-5.6 1.6zm14.901-20.8v20.3h-3.701v-20.3h-2.599v-2.3h2.599v-3.2c0-4.3 2.4-7.2 6.9-7.2h.8v2.4h-.3c-2 0-3.699 1-3.699 4.6v3.3h3.899v2.4zm0 0" fill="#679eb2" stroke="#679eb2" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(1.84155 0 0 1.8314 0 .18)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cfml.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M11.427 34.125c-7.399 0-10.8-7.1-10.8-14.9-.099-7.7 3.3-14.9 10.8-14.9 2.3 0 4.2.6 5.7 1.6l-1 2.4c-1.1-.7-2.6-1.2-4.2-1.2-5.2 0-7.3 6-7.3 12.1 0 6 2.1 12 7.2 12 1.6 0 3.2-.5 4.2-1.2l1 2.5c-1.5 1-3.3 1.6-5.6 1.6zm14.901-20.8v20.3h-3.701v-20.3h-2.599v-2.3h2.599v-3.2c0-4.3 2.4-7.2 6.9-7.2h.8v2.4h-.3c-2 0-3.699 1-3.699 4.6v3.3h3.899v2.4zm0 0" fill="#679eb2" stroke="#679eb2" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(1.84155 0 0 1.8314 0 .18)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cgi.svg
1
1
<svg height="64" width="73" xmlns="http://www.w3.org/2000/svg"><path d="M.184 46.813 26.828 64V51.383h45.625v-9.145H26.828V29.805zm45.988-34.196H.547v9.145h45.625v12.617l26.644-17.191L46.172 0zm0 0" fill="#666"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/chm.svg
1
1
<svg height="64" width="59" xmlns="http://www.w3.org/2000/svg"><path d="M59 0H13.41C6.613 0 0 2.668 0 10.668V64h48.273V10.668H6.613c0-3.914 2.684-5.336 5.367-5.336h41.477v53.336l5.363-5.336V0zm0 0" fill="#c93"/><path d="M21.992 40.18c0-5.512 6.434-6.403 6.434-10.493 0-1.777-1.61-3.199-3.754-3.199-2.324.18-4.11 1.778-4.11 1.778L17.88 24.89s2.683-2.848 7.332-2.848c4.289 0 8.402 2.668 8.402 7.289 0 6.402-6.797 7.113-6.797 11.203v1.422h-4.824zm0 5.152h4.824v4.445h-4.824zm0 0" fill="#fff"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/class.svg
1
1
<svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g fill="#f60" stroke="#f60" stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zM37.8 64.8c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cmd.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M64 0H0v64h64zM12.8 12.633H6.399V6.23h6.403zm44.802 0h-38.57V6.23h38.57zm0 44.797H6.23V19.2h51.372zm0 0"/><path d="m16.336 24.59-4.547 4.547 7.41 7.41-7.41 7.242 4.547 4.547 11.957-11.79zm10.613 21.558h12.797v6.399H26.95zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/coffee.svg
1
1
<svg height="64" width="79" xmlns="http://www.w3.org/2000/svg"><path d="M30.86 9.86c7.523-.567 9.59-5.458 18.43-6.212 4.323-.375 6.956.567 7.331 2.07.188 1.317-1.879 2.446-4.512 2.634-3.574.378-5.078-.938-5.453-2.258-2.633.187-3.008 1.32-3.008 2.258.188 1.507 3.57 3.011 9.024 2.449 6.207-.567 8.277-3.012 7.898-5.457-.562-3.2-5.453-5.836-14.101-5.082-11.098.941-11.098 6.02-18.43 6.773-3.008.188-4.89-.375-5.078-1.691-.188-1.13 1.316-1.883 3.008-1.883 1.695-.188 3.574.187 4.703.562.754-.375.941-.75.754-1.128-.192-1.13-2.633-1.692-5.457-1.317-5.64.567-5.64 3.012-5.453 4.14.754 2.634 4.89 4.516 10.343 4.141zM68.28 22.468c-6.957 1.691-15.797 2.633-26.328 2.633-10.906 0-19.742-1.13-26.512-2.633-6.207-1.696-9.402-3.39-10.718-5.082.562 3.953 1.691 7.902 3.007 11.668-1.503.941-3.007 2.257-4.324 3.761C.961 35.828-.168 39.402.02 42.98c.187 3.575 1.882 6.399 4.703 8.657 2.82 2.258 6.015 2.824 9.402 2.258 1.316-.188 2.82-.942 4.137-1.317-2.82 0-5.266-.941-7.711-2.824-2.633-1.883-4.512-4.703-4.89-7.902-.563-3.012 0-6.024 1.694-8.47.375-.566.75-.94 1.125-1.316.942 2.446 2.07 4.704 3.387 6.961 2.633 3.953 5.266 7.528 7.899 11.293 1.129 2.258 1.879 4.516 2.445 6.586 1.691 2.446 4.137 4.14 7.332 5.082 3.762 1.317 7.71 1.88 11.848 1.88h.375c3.949 0 8.273-.563 12.222-1.88a14.826 14.826 0 0 0 7.149-5.082h.187a27.312 27.312 0 0 1 2.258-6.586c2.629-3.765 5.262-7.34 7.895-11.293 3.574-6.398 6.02-13.738 7.335-21.64-1.128 2.07-4.515 3.761-10.53 5.082zm-52.84-5.457c6.957 1.691 15.793 2.633 26.325 2.633 10.906 0 19.558-.942 26.328-2.633C75.426 15.316 79 13.059 79 10.8c0-1.696-1.691-3.012-4.512-4.14.563.374 1.125 1.128 1.125 1.882 0 2.258-3.383 3.95-9.965 5.457-6.207 1.316-14.101 2.258-23.695 2.258-9.21 0-17.488-.942-23.504-2.258-6.394-1.695-9.777-3.387-9.777-5.457 0-.941.375-1.695 1.691-2.637-3.949 1.696-6.207 2.824-6.207 4.895.188 2.258 3.762 4.515 11.285 6.21zm0 0" fill="#28334c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/coffeelintignore.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/com.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M64 0H0v64h64zM12.8 12.633H6.399V6.23h6.403zm44.802 0h-38.57V6.23h38.57zm0 44.797H6.23V19.2h51.372zm0 0"/><path d="m16.336 24.59-4.547 4.547 7.41 7.41-7.41 7.242 4.547 4.547 11.957-11.79zm10.613 21.558h12.797v6.399H26.95zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/compile.svg
1
1
<svg height="64" width="68" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M59.906 48.902H0m60.715 7.387V19.035H68v44.8H1.617v-44.8h7.29V56.29zM16.675.164h36.106L34.648 18.543C28.82 12.637 22.668 6.398 16.676.164zm0 0"/><path d="M23.8 33.805v-7.383h7.286v7.383zm22.02 0h-7.285v-7.383h7.285zm-29.468 7.55h7.285v7.383h-7.285zm29.628 7.383v-7.383h7.286v7.383zm-7.609-7.383v7.383h-7.285v-7.383zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/conf.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/config.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cpp.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="43"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M21.02 0c9.93 0 14.718 5.578 14.718 5.578l-4.433 9.715s-3.903-3.957-9.399-3.957c-6.738 0-9.93 4.676-9.93 10.074 0 5.395 3.368 10.434 9.93 10.434 6.207 0 9.754-4.856 9.754-4.856l5.32 9.356S31.836 43 21.195 43C8.605 43 .273 34.004.273 21.59.093 9.355 8.605 0 21.02 0zm19.152 18.531h7.094v-8.093h5.851v8.093h7.094v6.117h-7.094v8.098h-5.851v-8.098h-7.094zm22.523 0h7.094v-8.093h5.852v8.093h7.093v6.117h-7.093v8.098h-6.032v-8.098h-7.093zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cptx.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M11.9 28C4.5 28 1 21.501 1 14.5 1 7.5 4.4 1 11.9 1c2.3 0 4.2.6 5.6 1.4l-1 2.1c-1-.6-2.7-1.1-4.2-1.1-5.2 0-7.2 5.5-7.2 11 0 5.4 2.101 10.9 7.2 10.9 1.6 0 3.1-.5 4.2-1.1l1 2.3c-1.4 1.1-3.2 1.5-5.6 1.5zM29.1 28c-1.302 0-2.702-.2-3.5-.501v8.4H22V8.2c1.9-1 4.4-1.4 7-1.4 6.5 0 10 4 10 10.3C39 23.9 35.1 28 29.1 28zM28.8 8.8c-1.1 0-2.4.199-3.2.601v16.1c.7.198 1.601.4 2.799.4 4.601 0 7.002-3.102 7.002-8.6-.102-5.201-1.9-8.5-6.601-8.5zm0 0" fill="#63b763" stroke="#63b763" stroke-miterlimit="10" stroke-width="2" transform="matrix(1.725 0 0 1.72973 0 .086)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cr2.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/crt.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/crypt.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#a03537"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cs.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/csh.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cson.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/css.svg
1
1
<svg height="64" width="75" xmlns="http://www.w3.org/2000/svg"><path d="M.5 19v-4.1c.9-.1 1.6-.2 2-.4.4-.2.8-.6 1.2-1.001.4-.5.5-1.1.7-1.9.1-.6.2-1.499.2-2.799 0-2.201.1-3.7.4-4.6.2-.8.6-1.6 1.2-2 .5-.5 1.4-.9 2.5-1.2.7-.2 1.9-.4 3.5-.4h.9v3.9c-1.3 0-2.2.1-2.6.3-.4.2-.6.4-.9.6-.2.3-.3.7-.3 1.501 0 .8-.1 2-.2 4.099-.101 1.2-.2 2-.4 2.801-.301.6-.6 1.2-1 1.8-.4.4-1 .9-1.8 1.399.7.4 1.3.8 1.8 1.3s.8 1.2 1.1 1.899c.3.702.4 1.802.4 3.001.1 1.9.1 3.1.1 3.599 0 .702.1 1.202.3 1.602.2.4.5.5.9.6.4.2 1.2.3 2.6.3v4.098h-1c-1.6 0-2.9-.1-3.701-.4-.9-.3-1.6-.6-2.2-1.2-.6-.6-.999-1.2-1.2-1.999-.198-.8-.299-2.1-.299-4 0-2-.1-3.5-.3-4.1-.3-.9-.7-1.601-1.201-2-.698-.5-1.5-.7-2.7-.7zm39.1 0c-.9.1-1.6.2-2 .4s-.8.6-1.2 1.001c-.4.5-.5 1.1-.7 1.9-.099.6-.2 1.499-.2 2.799 0 2.201-.1 3.7-.4 4.6-.2.9-.6 1.6-1.2 2-.5.5-1.4.9-2.5 1.2-.7.2-1.9.4-3.5.4h-.999v-4.1c1.298 0 2.1-.1 2.599-.3s.7-.4.899-.6c.2-.3.301-.7.301-1.501 0-.6.1-2 .2-3.999.099-1.2.3-2.1.5-2.8.3-.7.6-1.3 1.1-1.9.4-.5 1-.9 1.7-1.3-.901-.6-1.6-1.1-2-1.6-.5-.7-1-1.801-1.201-2.8-.199-.8-.299-2.6-.299-5.2 0-.8-.1-1.4-.301-1.8-.199-.3-.4-.5-.799-.6-.2-.3-1-.3-2.5-.3v-4h.999c1.602 0 2.9.1 3.7.4.902.3 1.6.6 2.2 1.2.6.6 1.002 1.2 1.2 2 .201.8.402 2.1.402 4 0 2 .098 3.4.299 4.1.299.9.7 1.601 1.2 1.9.5.4 1.401.6 2.5.6.1.1 0 4.3 0 4.3zm0 0" fill="#72a536" stroke="#72a536" stroke-miterlimit="10" transform="matrix(1.86825 0 0 1.87558 0 .209)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/csv.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="52"><path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1f7244;stroke-opacity:1;stroke-miterlimit:10" d="M0 1.5h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 7.4h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 13.3h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 19.2h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 25.1h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44" transform="matrix(1.9091 0 0 1.92593 0 .385)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cue.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/cur.svg
1
1
<svg height="64" width="55" xmlns="http://www.w3.org/2000/svg"><path d="M54.652 53.883 41.801 64 27.289 46.172l-9.3 11.351L.347 0l53.07 29.219-13.277 6.836zm0 0" fill="#8ed200"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dart.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="m10.98 2.23 12.672 10.356c-.355-3.75-1.96-7.856-4.46-10.356-1.786-1.785-3.391-2.5-5-2.14-1.426.18-2.5 1.07-3.212 2.14zM2.23 19.191c2.68 2.676 6.786 4.106 10.536 4.461L2.41 10.98c-1.25.891-2.14 1.786-2.32 3.211-.36 1.61.355 3.215 2.14 5zm51.06 22.672L41.862 53.29c1.43 1.43 3.75 2.676 6.07 3.035.715.18 1.25.18 1.965.18 1.07 0 2.141-.18 3.036-.715l7.675 7.676c.356.355.891.535 1.426.535s1.07-.18 1.43-.535c.715-.715.715-2.145 0-2.856l-7.676-7.675c1.606-3.575 0-8.57-2.5-11.07zM4.91 7.766l34.274 42.488 11.07-11.07L7.766 4.91c-.715-.715-1.965-.535-2.68.176-.89.715-.89 1.789-.176 2.68zm0 0" fill="#0091ea"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dat.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M17.8.5c-2.9 0-5.4 2.801-5.4 6.2 0 3.4 2.4 6.2 5.4 6.2 2.9 0 5.399-2.8 5.399-6.2C23.199 3.302 20.8.5 17.8.5zm0 10.1c-1.6 0-3-1.7-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9-1.3 3.9-3 3.9zM7 11.8V1.7C7 1 6.5.5 5.8.5S4.6 1 4.6 1.7v10.1c0 .7.5 1.2 1.2 1.2S7 12.4 7 11.8zm-1.1 6.9C3 18.7.5 21.5.5 24.9s2.4 6.2 5.4 6.2 5.401-2.8 5.401-6.2c-.102-3.3-2.5-6.2-5.4-6.2zm0 10.2c-1.6 0-3-1.699-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9c-.1 2.1-1.4 3.9-3 3.9zM19 30V19.9c0-.7-.5-1.2-1.2-1.2s-1.2.5-1.2 1.2V30c0 .7.5 1.2 1.2 1.2S19 30.7 19 30zM31.3 12.7V2.6c0-.7-.499-1.2-1.2-1.2-.7 0-1.1.5-1.1 1.2v10.099c0 .701.5 1.2 1.2 1.2s1.1-.6 1.1-1.2zm-1.2 6.9c-2.9 0-5.401 2.8-5.401 6.2 0 3.4 2.4 6.202 5.4 6.202 2.901 0 5.402-2.802 5.402-6.202S33.1 19.6 30.1 19.6zm0 10.102c-1.6 0-3-1.7-3-3.902 0-2.099 1.3-3.9 3-3.9s3 1.7 3 3.9c0 2.202-1.3 3.902-3 3.902zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" transform="matrix(1.91667 0 0 1.9394 0 .485)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/data.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M17.8.5c-2.9 0-5.4 2.801-5.4 6.2 0 3.4 2.4 6.2 5.4 6.2 2.9 0 5.399-2.8 5.399-6.2C23.199 3.302 20.8.5 17.8.5zm0 10.1c-1.6 0-3-1.7-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9-1.3 3.9-3 3.9zM7 11.8V1.7C7 1 6.5.5 5.8.5S4.6 1 4.6 1.7v10.1c0 .7.5 1.2 1.2 1.2S7 12.4 7 11.8zm-1.1 6.9C3 18.7.5 21.5.5 24.9s2.4 6.2 5.4 6.2 5.401-2.8 5.401-6.2c-.102-3.3-2.5-6.2-5.4-6.2zm0 10.2c-1.6 0-3-1.699-3-3.9 0-2.1 1.3-3.9 3-3.9s3 1.7 3 3.9c-.1 2.1-1.4 3.9-3 3.9zM19 30V19.9c0-.7-.5-1.2-1.2-1.2s-1.2.5-1.2 1.2V30c0 .7.5 1.2 1.2 1.2S19 30.7 19 30zM31.3 12.7V2.6c0-.7-.499-1.2-1.2-1.2-.7 0-1.1.5-1.1 1.2v10.099c0 .701.5 1.2 1.2 1.2s1.1-.6 1.1-1.2zm-1.2 6.9c-2.9 0-5.401 2.8-5.401 6.2 0 3.4 2.4 6.202 5.4 6.202 2.901 0 5.402-2.802 5.402-6.202S33.1 19.6 30.1 19.6zm0 10.102c-1.6 0-3-1.7-3-3.902 0-2.099 1.3-3.9 3-3.9s3 1.7 3 3.9c0 2.202-1.3 3.902-3 3.902zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" transform="matrix(1.91667 0 0 1.9394 0 .485)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/db.svg
1
1
<svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dbf.svg
1
1
<svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#a03537"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/deb.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#4d1b9b"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dgn.svg
1
1
<svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dist.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="54"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.242 25.867c-.5 0-1.1-.2-1.498-.6l-8.4-8.401c-.802-.799-.802-2.099 0-3l8.4-8.398c.8-.8 2.098-.8 2.999 0 .8.8.8 2.098 0 2.999l-6.9 6.9 6.9 6.9c.8.801.8 2.1 0 3-.401.4-1 .6-1.5.6zm25.1 0c-.499 0-1.099-.2-1.5-.6-.8-.8-.8-2.099 0-3.002l6.9-6.898-6.9-6.9c-.8-.8-.8-2.101 0-3 .8-.8 2.1-.8 3.001 0l8.398 8.4c.802.8.802 2.1 0 3l-8.398 8.4c-.4.4-1.001.6-1.5.6zm-16.7 4.1c-.199 0-.398 0-.698-.1-1.102-.401-1.702-1.5-1.301-2.6l8.398-25.1c.4-1.1 1.5-1.699 2.6-1.301 1.102.4 1.702 1.5 1.301 2.601l-8.398 25.1c-.202.899-1.102 1.4-1.901 1.4zm0 0" transform="matrix(1.74425 0 0 1.75713 0 .013)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/diz.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#bababa;fill-opacity:1" d="M72.277.21c-4.906 0-3.586 4.978-4.53 7.083-.376.77-.946 2.297-1.509 3.64l.375-.765c-.375.762-.566.953-1.32 1.527 0 0-3.398 2.11-6.422 3.832C55.285 9.211 48.863 5.57 41.875 5.57c-6.984 0-13.594 3.828-16.992 9.957-3.207-1.914-6.61-3.832-6.61-3.832-.753-.574-1.128-.765-1.32-1.527l.375.762c-.566-1.34-1.129-2.68-1.508-3.637C15.066 5 16.383.211 11.29.211c-4.915 0-3.97 6.7-5.477 9.379-.946 1.726-3.778 3.445-5.098 5.363-.192.188-.192.379-.192.574-.566.957-.753 2.297.192 3.637 2.453 4.211 6.039.766 8.305.383.945-.192 2.27-.192 3.777-.574l-.945.191c.757-.191 1.136 0 2.078.574 0 0 3.59 2.106 8.308 4.785v1.149c0 3.64.946 7.469 3.024 10.527a656.505 656.505 0 0 0-11.895 7.278c-.758.574-1.137.765-1.89.574h.753-3.585c-2.27 0-5.102-2.68-7.93.957-2.649 3.637 2.828 5.36 3.965 7.469 1.133 2.105-.38 7.843 4.34 7.27 4.535-.571 3.777-4.403 4.91-6.505.566-.765 1.136-1.918 1.699-3.062l-.375.765c.375-.578.754-.765 1.508-1.343 0 0 5.289-2.868 11.332-6.508v8.804c1.699.766 3.398 1.153 5.097 1.532v-4.785l2.454.574v4.785c1.699.387 3.402.578 5.101.578v-4.789h2.266v4.598c1.695 0 3.398-.196 5.097-.578v-4.786l2.453-.574v4.977c1.7-.38 3.399-.766 5.098-1.532v-8.804a396.942 396.942 0 0 1 11.332 6.508c.754.578 1.133.765 1.512 1.343.566.762.941 1.72 1.32 2.297.946 1.914.375 5.934 4.91 6.504 4.536.578 3.211-5.164 4.344-7.27 1.133-2.109 6.61-3.64 3.965-7.468-2.645-3.637-5.664-1.149-7.93-.957h-3.59.754c-.754 0-.941 0-1.886-.574 0 0-5.477-3.446-11.899-7.278 2.078-3.25 3.024-6.695 3.024-10.527v-1.149c4.722-2.68 8.308-4.785 8.308-4.785.754-.574 1.32-.574 2.078-.574l-.57-.191c1.516.191 2.836.382 3.781.574 2.266.383 5.852 3.828 8.309-.383 2.453-4.21-3.59-6.89-5.098-9.574-.945-1.719-.758-5.164-2.078-7.27 0-.195-.191-.386-.191-.386C74.735.785 73.793.402 72.277.21zM34.324 23.376c.567 0 .758 0 1.32.191 2.645.766 3.403 3.254 2.645 6.125-.754 2.684-3.586 4.403-6.23 3.637-2.645-.766-3.59-3.445-2.645-6.121.57-2.3 2.645-3.832 4.91-3.832zm14.73 0c2.266 0 4.532 1.531 5.098 3.637.754 2.68 0 5.172-2.644 6.129-2.645.761-5.477-.77-6.23-3.641-.755-2.68 0-5.168 2.644-6.125a1.19 1.19 0 0 0 1.133 0zm-7.363 12.824c.754 0 3.961 4.215 3.586 4.79-.379.574-6.797.574-7.175 0-.38-.575 3.02-4.79 3.59-4.79zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dll.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M67 .445 36.203 4.63v24.55H67zM30.797 5.172 0 9.355V29.18h30.797zM.18 34.82v19.825l30.797 4.183V35zm36.023 0v24.551L67 63.555V34.82zm0 0" fill="#666"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dmg.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M57.602 41.688c2.488 0 4.62 1.066 6.218 2.492L58.488 5.422C58.133 2.222 55.29.09 52.266.09H11.734c-3.199 0-5.867 2.133-6.402 5.332L0 44.18c1.777-1.426 3.91-2.493 6.398-2.493zm0 3.023H6.398A6.372 6.372 0 0 0 0 51.109v6.403a6.372 6.372 0 0 0 6.398 6.398h51.204A6.372 6.372 0 0 0 64 57.512v-6.403a6.372 6.372 0 0 0-6.398-6.398zM52.09 56.977h-3.91a1.97 1.97 0 0 1-1.957-1.954c0-1.066.886-1.957 1.957-1.957h3.91c1.066 0 1.953.891 1.953 1.957a1.97 1.97 0 0 1-1.953 1.954zm5.156 0a1.972 1.972 0 0 1-1.957-1.954c0-1.066.89-1.957 1.957-1.957s1.953.891 1.953 1.957c.18 1.067-.71 1.954-1.953 1.954zm0 0" fill="#4d1b9b"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dng.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/doc.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/docb.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/docm.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/docx.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dot.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dotm.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dotx.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/download.svg
1
1
<svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="c"><g filter="url(#a)"><path d="M0 0h72v64H0z" fill-opacity=".6"/></g></mask><clipPath id="b"><path d="M0 0h72v64H0z"/></clipPath><mask id="e"><g filter="url(#a)"><path d="M0 0h72v64H0z" fill-opacity=".6"/></g></mask><clipPath id="d"><path d="M0 0h72v64H0z"/></clipPath><g clip-path="url(#b)" mask="url(#c)"><path d="M67.418 37.824C70.199 40.454 72 44.391 72 48.656c0 8.207-6.71 14.934-14.89 14.934-8.184 0-14.891-6.727-14.891-14.934 0-.492 0-1.148.164-1.64.816-7.387 7.199-13.293 14.89-13.293 3.926-.164 7.528 1.64 10.145 4.101zm0 0" fill="#ef806f"/></g><g clip-path="url(#d)" mask="url(#e)"><path d="M68.563 32.082c0 1.148-.165 2.297-.325 3.61-3.11-2.626-7.039-4.102-11.129-4.102-8.672 0-16.035 6.562-17.02 15.262H11.782C5.238 46.852 0 41.602 0 35.035c0-5.086 3.273-9.515 7.691-11.16v-.656c0-6.07 4.91-10.992 10.965-10.992 1.961 0 3.762.492 5.399 1.312C25.69 6.152 32.398.41 40.418.41c9.328 0 16.855 7.547 16.855 16.902v.329c6.543 1.476 11.29 7.386 11.29 14.441zm0 0" fill="#1ea6c6"/></g><path d="m64.965 48.82-7.528 7.715-7.69-7.715h4.581V38.152h6.055V48.82zm0 0" fill="#fff"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dpj.svg
1
1
<svg height="63" width="49" xmlns="http://www.w3.org/2000/svg"><path d="M24.842 21.014c2.2-.7 4.4.7 4.202 2.8-.2 2.4-3.302 3.601-5.1 4.2l.1-.099-.1-.101c1.3-1 3.898-2.199 3.7-4.6-.1-1.2-1.001-2.098-2.702-2v-.2zm-16.099.401.1-.101c-1 0-1.901.4-2.701.7-.798.3-1.8.401-2.198 1.3.399.7 1.4.7 2.398.802 3.4.5 8.302.398 11.701 0 1.799-.201 3.4-.401 4.2-1.201l-.1-.101.1-.1c-3.4.402-7.8 1-11.9.8-1.301-.099-3-.099-3.7-.8.4-.7 1.4-.9 2.1-1.299zm19.9 14.099v-.1c-5.1 2.5-13.201 2.8-20.5 2.201l.1.1-.1.2c2.999.5 6.9.7 10.7.398 3.7-.198 8.199-.698 9.9-2.698zm-14.4-15.398h.1c-.8-1.802-2.3-2.602-2.499-4.7-.2-1.902.7-3.102 1.598-4 1.101-1.201 2.702-2.201 3.901-3.5 1.6-1.803 3.4-4.5 1.9-7.102l-.101.101-.299-.101c.4 2.5-.6 4.101-1.901 5.4-.999 1.201-2.6 2.201-4 3.3-1.6 1.3-3.7 2.901-3.1 5.3.502 2.302 2.8 3.9 4.102 5.4zm8-11.602-.1-.099c-2.7 1-6.701 2.6-7.1 5.698-.1 1.503.399 2.602.9 3.401.4.602 1.1 1 1.3 1.901.2.8 0 1.6-.2 2.2h.1l.1.1c1.099-.8 2.2-1.901 1.899-3.401-.198-1.5-1.9-2.5-2.1-3.899-.1-.802.1-1.5.401-1.9 1.1-1.701 3.5-2.902 4.8-4zm-13.8 17.401-.101-.101c-.5.301-1.5.4-1.4 1.2.1.8 1.5 1 2.2 1.2 3.7.8 9.2.3 11.902-.599l-.1-.101.1-.099c-.301-.101-.7-.7-1.3-.7-.502-.1-1.6.299-2.602.4-1.6.199-3.299.3-4.799.199-1.101-.1-4.5-.1-3.9-1.399zm.9 4.099.1-.1c-.6.201-1.3.4-1.3 1.1 0 .601 1.2 1 1.9 1.3 3.299 1 8.5.4 10.9-.699-.2-.302-.6-.4-.9-.601-.4-.1-.7-.3-1.1-.5-2.001.5-5.1.7-7.5.4-.7-.1-1.701-.1-1.9-.799zm17.699 3.2-.1-.099c-.098 1-1.3 1.1-2.1 1.3-.898.2-1.9.398-2.998.5-4.902.599-11.5.898-16.302 0-.898-.102-2.2-.401-2.499-1.102.4-.698 1.5-.8 2.399-1.198l-.098-.101.098-.1c-1.2.1-2.1.4-2.998.701-.7.3-1.701.698-1.902 1.5.6.8 1.801.8 2.8 1 6.6 1 15.7 1.198 21.402-.7.998-.401 3.098-1 2.1-1.901zm-3.7-5.099c.2 0 .4-.101.702-.2m.898-6.8c-.198 0-.399.1-.7.1m-2.2 1.7c.1 0 .2-.101.401-.101m-12.5-1.6c-.4.1-.8.1-1.3.201m-2.2 15.898c.499.2 1.099.2 1.7.401m20.5-2.2c.1-.1.2-.2.3-.399M19.043.814c0-.099-.098-.3-.098-.399m-4.702 19.7c.1.1.301.4.402.5m2.298 1.199c.1-.1.2-.198.3-.399m5.7-13.2c-.3.099-.499.2-.7.398m-1.198 18.802h.198m-12.6-1.8c0 .1-.2.1-.2.199m.9 4.2.101-.1m-2.8 2.701c-.4 0-.7.1-1 .1m21.399.5c0-.1-.1-.1-.1-.1h-.098" fill="#666" stroke="#666" stroke-miterlimit="10" transform="matrix(1.63519 0 0 1.61722 .336 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ds_store.svg
1
1
<svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><path d="M67.566 0H4.56C2.125 0 .339 1.953.339 4.234V48.04c0 2.445 1.946 4.234 4.22 4.234h62.843c2.438 0 4.223-1.953 4.223-4.234V4.4C71.949 1.952 70.004 0 67.566 0zm0 0" fill="#ced2d8"/><path d="M4.559 10.586h63.007v37.453H4.56zm0 0" fill="#f2f2f2"/><path d="M17.55 5.7a1.462 1.462 0 1 1-2.921 0 1.462 1.462 0 1 1 2.922 0zm0 0" fill="#54b845"/><path d="M12.516 5.7c0 .808-.653 1.464-1.461 1.464a1.465 1.465 0 0 1 0-2.93c.808 0 1.46.657 1.46 1.465zm0 0" fill="#fbd303"/><path d="M7.809 5.7c0 .808-.657 1.464-1.461 1.464-.809 0-1.461-.656-1.461-1.465 0-.808.652-1.465 1.46-1.465.805 0 1.462.657 1.462 1.465zm0 0" fill="#f0582f"/><path d="m57.5 38.594-4.223-1.137c-.324-1.793-1.136-3.422-1.949-4.887l2.11-3.582c.324-.328.164-.976-.16-1.304L50.19 24.59c-.324-.324-.812-.324-1.3-.164l-3.57 2.117c-1.462-.813-3.087-1.625-4.872-1.953l-1.136-4.07c-.165-.489-.489-.817-.977-.817h-4.223c-.484 0-.808.328-.972.817l-1.301 4.07c-1.785.328-3.41 1.14-4.871 1.953l-3.735-1.953c-.324-.324-.972-.164-1.297.164l-3.085 3.094c-.325.324-.325.812-.164 1.3l2.113 3.586c-.813 1.465-1.625 3.094-1.95 4.883l-4.062.977c-.484.164-.809.488-.809.98v4.23c0 .493.325.817.81.981l4.222 1.137c.324 1.793 1.136 3.422 1.949 4.887l-2.11 3.746c-.324.324-.164.976.16 1.304l3.087 3.094c.324.324.812.324 1.3.16l3.57-2.117c1.462.816 3.087 1.629 4.872 1.957l1.137 4.234c.164.489.488.813.976.813h4.223c.484 0 .812-.324.972-.813l1.137-4.234c1.785-.328 3.41-1.14 4.871-1.957l3.574 2.117c.325.328.973.164 1.297-.16l3.086-3.094c.325-.328.325-.816.164-1.304l-2.113-3.582c.813-1.465 1.625-3.094 1.95-4.887l4.058-.977c.488-.164.812-.488.812-.976v-4.399c.164-.488 0-.812-.484-1.14zM36.062 49.832c-4.382 0-7.957-3.582-7.957-7.98 0-4.395 3.575-7.98 7.957-7.98 4.387 0 7.958 3.585 7.958 7.98 0 4.398-3.57 7.98-7.958 7.98zm0 0" fill="#6eb1e1"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dsn.svg
1
1
<svg height="63" width="62" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="m11.426 26.762 27.386-4.489-1.007-4.32-27.387 4.488zm4.203 18.453 1.008 4.652 11.086-2.16v-4.652zm-1.516-6.985 13.61-2.492v-3.656c0-.332 0-.664.168-.996l-14.786 2.656zm13.61 16.625L13.945 57.68l-9.07-40.89 35.617-5.653 2.856 12.468c.672 0 1.343 0 1.847-.168L39.82 0 .34 5.652 12.434 60.34l15.457-3.156v-.168zm18.312-7.148c-8.566 0-15.625-2.824-15.625-6.148v6.148c0 3.492 7.059 6.152 15.625 6.152 8.57 0 15.625-2.828 15.625-6.152v-6.148c0 3.324-7.055 6.148-15.625 6.148zm0 9.14c-8.566 0-15.625-2.824-15.625-6.148v6.149C30.41 60.34 37.47 63 46.035 63c8.57 0 15.625-2.824 15.625-6.152v-6.149c0 3.492-7.055 6.149-15.625 6.149zm0-30.917c-8.566 0-15.625 2.828-15.625 6.152v6.316c0 3.493 7.059 6.149 15.625 6.149 8.57 0 15.625-2.824 15.625-6.149v-6.152c0-3.488-7.055-6.316-15.625-6.316zm0 0"/><path d="M46.035 36.902c-8.566 0-14.11-2.824-14.11-4.656 0-1.828 5.544-4.652 14.11-4.652 8.57 0 14.113 2.824 14.113 4.652 0 1.832-5.543 4.656-14.113 4.656zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dtd.svg
1
1
<svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="m14.974 16.374-11-5.5v-.1l11-5.398V1.574L.374 9.375v3l14.6 7.7zm7.502-3.2L23.174.376h-4.798l.698 12.8zm-1.701 8.002c1.6 0 2.701-1.401 2.701-3.102 0-1.898-1.102-3.1-2.701-3.1s-2.701 1.3-2.701 3.1c-.1 1.7 1 3.102 2.701 3.102zm5.8-19.602v3.802l11.2 5.399v.1l-11.2 5.5v3.7l14.6-7.7v-3.1zm-24 23.401h36.5v2.5h-36.5zm0 7.1h36.5v2.5h-36.5zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.81211 0 0 1.83119 .353 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dwg.svg
1
1
<svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/dxf.svg
1
1
<svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/el.svg
1
1
<svg height="63" width="45" xmlns="http://www.w3.org/2000/svg"><path d="M22.686 26.4h.8c0 2.401-.4 4.2-1.2 5.3-.801 1.1-1.8 1.7-3 1.7-1.001 0-1.9-.4-2.8-1.1-.9-.699-1.701-2.7-2.4-5.9l-2-8.9-6.902 15.599h-4.4l9.901-21.2c-.5-2.698-1.2-4.799-1.899-6.098-.701-1.3-1.7-2.002-2.7-2.002-.902 0-1.601.301-2.3 1-.6.7-1 1.701-1.1 3.1h-.8c0-2.299.5-4.1 1.4-5.4.899-1.3 1.898-2 3.2-2 .8 0 1.599.302 2.3 1.002.7.699 1.4 1.799 1.9 3.499.599 1.7 1.4 5.1 2.6 10.3l1.599 7.3c.701 3 1.4 5 2.1 6.1.7 1 1.6 1.5 2.6 1.5 1.9-.1 2.901-1.3 3.101-3.8zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" transform="matrix(1.87615 0 0 1.85407 0 .073)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/elf.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M68.633.102H16.39v7.226H5.738v2.274H0v2.062h5.738v2.684h10.653V26.94H5.738v2.477H0v2.066h5.738v2.27h10.653v13.625H5.738v2.48H0v2.063h5.738v2.273h10.653v9.703h52.242v-9.703h9.629v-2.48H84v-2.063h-5.738v-2.476h-9.63v-13.63h9.63v-2.062H84v-2.066h-5.738v-2.684h-9.63V14.141h9.63v-2.684H84V9.395h-5.738V7.12h-9.63zm-10.04 17.136c-2.253 0-4.097-1.86-4.097-4.129S56.34 8.98 58.594 8.98s4.097 1.86 4.097 4.13c0 2.476-1.843 4.128-4.097 4.128zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/eml.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="fill-rule:nonzero;fill:#7e57c2;fill-opacity:1;stroke-width:.75;stroke-linecap:butt;stroke-linejoin:miter;stroke:#7e57c2;stroke-opacity:1;stroke-miterlimit:10" d="M6.274 25.574h28.3l-9.698-9.3-4.501 3.802-4.5-3.802zm34.1-25.2v28.002H.376V.374zM26.976 14.576l10.7 10.298v-19.3zm-24.2 10.298 10.7-10.298-10.7-9.002zm1.4-21.7 15.9 13.4 15.9-13.4zm0 0" transform="matrix(2.06135 0 0 2.08166 0 .076)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/enc.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/eot.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/eps.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M16.223 21.805.09 55.844l3.012 3.015 20.035-20.035c-.711-1.594-.532-3.543.886-4.96 1.774-1.774 4.43-1.774 6.204 0 1.773 1.769 1.773 4.429 0 6.202-1.243 1.243-3.368 1.594-4.965.887L5.23 60.984 8.242 64l34.04-16.133L49.73 27.48 36.61 14.36zm46.625-4.075L46.184 1.062c-1.418-1.417-3.547-1.417-4.965 0L37.32 4.966c-1.422 1.418-1.422 3.543 0 4.965l16.664 16.664c1.418 1.418 3.543 1.418 4.965 0l3.899-3.903c1.418-1.418 1.418-3.543 0-4.96zm0 0" fill="#fea500"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/epub.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/eslintignore.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/exe.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.887 38.266c-2.684-.84-4.532-3.36-4.532-6.215 0-2.852 1.848-5.371 4.532-6.211.84-.336 1.343-1.172 1.008-2.012-.84-3.023-1.848-5.707-3.524-8.394-.504-.84-1.344-1.008-2.184-.672-1.007.504-2.015.84-3.19.84-3.692 0-6.548-3.024-6.548-6.547 0-1.176.336-2.184.84-3.188.504-.84.168-1.68-.672-2.183a40.47 40.47 0 0 0-8.39-3.528c-.84-.168-1.68.168-2.016 1.008C37.37 3.852 34.855 5.7 32 5.7s-5.371-1.847-6.21-4.535C25.452.324 24.612-.18 23.772.156c-3.02.84-5.707 1.848-8.39 3.528-.84.503-1.008 1.343-.672 2.183.504 1.004.84 2.012.84 3.188 0 3.691-3.024 6.547-6.547 6.547-1.176 0-2.184-.336-3.191-.84-.84-.504-1.68-.168-2.184.672a40.699 40.699 0 0 0-3.524 8.394c-.167.84.168 1.676 1.008 2.012 2.684.84 4.532 3.36 4.532 6.21 0 2.856-1.848 5.376-4.532 6.216-.84.332-1.343 1.172-1.008 2.011.84 3.024 1.848 5.707 3.524 8.395.504.84 1.344 1.008 2.184.672 1.007-.504 2.015-.84 3.19-.84 3.692 0 6.548 3.02 6.548 6.547 0 1.176-.336 2.183-.84 3.187-.504.84-.168 1.68.672 2.184a40.47 40.47 0 0 0 8.39 3.527h.336c.672 0 1.344-.504 1.512-1.176.84-2.687 3.356-4.535 6.211-4.535s5.371 1.848 6.211 4.535c.336.84 1.176 1.344 2.016 1.008 3.02-.84 5.707-1.847 8.39-3.527.84-.504 1.008-1.344.672-2.184-.504-1.004-.84-2.011-.84-3.187 0-3.692 3.024-6.547 6.547-6.547 1.176 0 2.184.336 3.192.84.84.504 1.68.168 2.183-.672a40.698 40.698 0 0 0 3.524-8.395c.503-.672 0-1.511-.84-1.843zm-30.719 3.691c-5.371 0-9.902-4.363-9.902-9.906 0-5.371 4.363-9.903 9.902-9.903 5.371 0 9.902 4.364 9.902 9.903 0 5.375-4.53 9.906-9.902 9.906zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/f4v.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M46.168 13.516c1.793-.711 3.766-.891 5.738-.891V.008c-8.605-.18-16.851 3.554-22.23 10.308-2.153 2.844-4.125 5.864-5.559 9.243l-4.12 10.128c-1.079 3.024-2.333 6.223-3.767 9.067a31.916 31.916 0 0 1-3.945 6.754c-1.254 1.777-3.047 3.199-5.02 4.09-2.152 1.066-4.66 1.597-7.171 1.597v12.797c8.605.18 16.851-3.554 22.23-10.308 1.613-2.309 3.227-4.797 4.485-7.286l3.406-8h14.879v-12.62h-9.86c.715-1.954 1.793-3.731 3.047-5.508.895-1.602 2.153-2.844 3.407-3.91 1.613-1.422 3.046-2.313 4.48-2.844zm0 0" fill="#d10407"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/fax.svg
1
1
<svg height="63" width="60" xmlns="http://www.w3.org/2000/svg"><path d="M21.516 22.047V3.832c0-1.91 1.574-3.297 3.324-3.297h20.816l10.672 10.582v10.754c0 .348-.351.695-.703.695h-3.672c-.351 0-.7-.347-.7-.695v-7.285H44.43c-1.399 0-2.446-1.04-2.446-2.43v-6.59H26.59v16.305c0 .348-.352.695-.7.695h-3.675c-.348.172-.7-.171-.7-.52zm0 39.203V28.984c0-.695.523-1.214 1.226-1.214h36.035c.7 0 1.223.52 1.223 1.214v27.063c0 3.469-2.973 6.418-6.473 6.418H22.914c-.875 0-1.398-.52-1.398-1.215zm25.363-24.98c0 1.562 1.226 2.601 2.625 2.601 1.398 0 2.625-1.215 2.625-2.601 0-1.56-1.227-2.602-2.625-2.602-1.399-.172-2.625 1.043-2.625 2.602zm0 8.847c0 1.563 1.226 2.602 2.625 2.602 1.574 0 2.625-1.215 2.625-2.602 0-1.387-1.227-2.601-2.625-2.773-1.399 0-2.625 1.21-2.625 2.773zM37.96 36.27c0 1.386 1.223 2.601 2.621 2.601 1.402 0 2.625-1.215 2.625-2.601s-1.223-2.602-2.625-2.602c-1.398-.172-2.621 1.043-2.621 2.602zm0 8.847c0 1.387 1.223 2.602 2.621 2.602 1.574 0 2.625-1.215 2.625-2.602 0-1.562-1.223-2.601-2.625-2.773-1.398 0-2.621 1.21-2.621 2.773zm0 8.848c0 1.387 1.223 2.601 2.621 2.601 1.574 0 2.625-1.214 2.625-2.601s-1.223-2.602-2.625-2.602c-1.398 0-2.621 1.215-2.621 2.602zM29.039 36.27c0 1.562 1.223 2.601 2.621 2.601 1.403 0 2.625-1.215 2.625-2.601 0-1.56-1.222-2.602-2.625-2.602-1.574-.172-2.62 1.043-2.62 2.602zm0 8.847c0 1.563 1.223 2.602 2.621 2.602 1.574 0 2.625-1.215 2.625-2.602 0-1.562-1.222-2.601-2.625-2.773-1.398 0-2.62 1.21-2.62 2.773zm0 8.848c0 1.387 1.223 2.601 2.621 2.601 1.574 0 2.625-1.214 2.625-2.601s-1.222-2.602-2.625-2.602c-1.574 0-2.62 1.215-2.62 2.602zm-22.566 8.5h8.57c.7 0 1.227-.52 1.227-1.215V20.832c0-.695-.528-1.215-1.227-1.215h-6.82C3.672 19.617 0 23.262 0 27.77v28.449c0 3.297 2.8 6.246 6.473 6.246zm0 0" fill="#fea500"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/fb2.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/fla.svg
1
1
<svg height="63" width="49" xmlns="http://www.w3.org/2000/svg"><path d="M4.524 3.224v10.102h8.5v2.598h-8.5v13.7h-3.9V.626h13.301v2.598zm14.402 26.3V.826h3.7v28.7zm0 0" fill="#d10407" stroke="#d10407" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(2.10753 0 0 2.07742 0 .079)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/flac.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/flv.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M46.168 13.516c1.793-.711 3.766-.891 5.738-.891V.008c-8.605-.18-16.851 3.554-22.23 10.308-2.153 2.844-4.125 5.864-5.559 9.243l-4.12 10.128c-1.079 3.024-2.333 6.223-3.767 9.067a31.916 31.916 0 0 1-3.945 6.754c-1.254 1.777-3.047 3.199-5.02 4.09-2.152 1.066-4.66 1.597-7.171 1.597v12.797c8.605.18 16.851-3.554 22.23-10.308 1.613-2.309 3.227-4.797 4.485-7.286l3.406-8h14.879v-12.62h-9.86c.715-1.954 1.793-3.731 3.047-5.508.895-1.602 2.153-2.844 3.407-3.91 1.613-1.422 3.046-2.313 4.48-2.844zm0 0" fill="#d10407"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/fnt.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/folder-link.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M81.227 64H2.773a2.514 2.514 0 0 1-2.535-2.54V21.333h83.524v40.129A2.514 2.514 0 0 1 81.227 64zm0 0" fill="#efce4a"/><path d="M33.008 10.059v-7.52A2.514 2.514 0 0 0 30.468 0H2.774A2.514 2.514 0 0 0 .238 2.54v18.792h83.524v-8.734a2.514 2.514 0 0 0-2.535-2.54zm0 0" fill="#ebba16"/><path d="m53.059 42.668-10.754-9.754v6.5H30.94v6.504h11.364v6.5zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/folder-up.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M81.227 64H2.773a2.514 2.514 0 0 1-2.535-2.54V21.333h83.524v40.129A2.514 2.514 0 0 1 81.227 64zm0 0" fill="#efce4a"/><path d="M33.008 10.059v-7.52A2.514 2.514 0 0 0 30.468 0H2.774A2.514 2.514 0 0 0 .238 2.54v18.792h83.524v-8.734a2.514 2.514 0 0 0-2.535-2.54zm0 0" fill="#ebba16"/><path d="m42 31.594-9.738 10.77h6.492v11.374h6.492V42.363h6.492zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/folder.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M81.227 64H2.773a2.514 2.514 0 0 1-2.535-2.54V21.333h83.524v40.129A2.514 2.514 0 0 1 81.227 64zm0 0" fill="#efce4a"/><path d="M33.008 10.059v-7.52A2.514 2.514 0 0 0 30.468 0H2.774A2.514 2.514 0 0 0 .238 2.54v18.792h83.524v-8.734a2.514 2.514 0 0 0-2.535-2.54zm0 0" fill="#ebba16"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/fon.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gdp.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M43.8 55.613H11.036c-3.586 0-6.488-2.906-6.488-6.5s2.902-6.504 6.488-6.504h12.797c5.8 0 10.582-4.793 10.582-10.609s-4.781-10.61-10.582-10.61h-6.996c.172.684.172 1.368.172 2.055 0 .684 0 1.368-.172 2.051h6.996c3.586 0 6.484 2.91 6.484 6.504s-2.898 6.504-6.484 6.504H11.035C5.23 38.504.453 43.293.453 49.114c0 5.816 4.777 10.609 10.582 10.609H43.97c-.168-.684-.168-1.371-.168-2.055zm10.067-4.277c-3.414 0-6.312 2.738-6.312 6.332S50.285 64 53.867 64c3.586 0 6.317-2.738 6.317-6.332s-2.73-6.332-6.317-6.332zM19.567 0H6.765C5.742 0 4.719.855 4.719 2.055v15.398C2.16 18.31.453 20.707.453 23.445c0 3.422 2.73 6.332 6.313 6.332 3.586 0 6.316-2.738 6.316-6.332 0-2.738-1.707-5.136-4.266-5.992v-4.789h10.75c1.024 0 2.047-.855 2.047-2.055V2.055C21.441 1.027 20.59 0 19.566 0zm34.3 4.277c-8.191 0-14.847 6.676-14.847 14.887 0 4.45 1.878 8.559 5.292 11.297l7.68 15.23c.68 1.54 2.899 1.54 3.582 0l7.68-15.23c3.414-2.91 5.289-7.016 5.289-11.297.172-8.21-6.484-14.887-14.676-14.887zm0 21.22c-3.414 0-6.312-2.74-6.312-6.333s2.73-6.328 6.312-6.328c3.586 0 6.317 2.734 6.317 6.328s-2.73 6.332-6.317 6.332zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gem.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M61.988 2.012v59.976L46.996 17.004zM2.012 61.988h59.976L17.004 46.996zm14.992-14.992 44.984 14.992L32 32zM32 32l29.988 29.988-14.992-44.984zM2.012 46.996v14.992l14.992-14.992zM32 32H17.004v14.996zm14.996-14.996H32V32zM61.988 2.012H46.996v14.992zM17.004 32 2.012 46.996h14.992zM32 17.004 17.004 32H32zM46.996 2.012 32 17.004h14.996zm0 0" fill="#666" stroke="#fff" stroke-width="1.66605"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gif.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gitattributes.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.773 29.176 34.824 1.226c-1.636-1.636-4.164-1.636-5.797 0L23.23 7.024l7.282 7.286c1.636-.594 3.718-.149 5.054 1.191 1.34 1.336 1.786 3.418 1.192 5.055l7.137 7.133a4.905 4.905 0 0 1 5.054 1.19 4.942 4.942 0 0 1-6.988 6.99c-1.488-1.49-1.785-3.571-1.043-5.356l-6.54-6.54v17.395l1.337.89a4.935 4.935 0 0 1 0 6.99 4.937 4.937 0 0 1-6.985 0c-1.933-1.934-2.082-5.056-.148-6.99.445-.444.89-.89 1.484-1.038V23.527c-.445-.297-1.039-.597-1.484-1.043-1.488-1.484-1.785-3.566-1.043-5.351l-7.137-7.285-19.175 19.18c-1.637 1.632-1.637 4.16 0 5.796l27.949 27.95c1.636 1.636 4.164 1.636 5.797 0l27.8-27.801c1.637-1.633 1.637-4.313 0-5.797zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gitignore.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/go.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="42"><path style="stroke:none;fill-rule:nonzero;fill:#2dbcaf;fill-opacity:1" d="M41.156 11.465c-3.449.906-5.804 1.633-9.254 2.539-.906.184-.906.184-1.632-.36-.907-.91-1.27-1.636-2.54-2-3.449-1.812-6.714-1.085-9.98.91-3.809 2.54-5.625 6.169-5.625 10.344 0 4.172 3.086 8.165 7.441 8.528 3.809.363 6.711-.906 9.254-3.63.36-.726.907-1.273 1.633-1.995H19.746c-1.09 0-1.27-.727-1.09-1.633.727-1.816 1.996-4.535 2.723-6.168.183-.184.363-.91 1.27-.91h20.14c0 1.633 0 2.902-.18 4.535-.726 3.996-1.996 7.621-4.535 10.887-3.992 5.265-9.074 8.347-15.61 9.258-5.44.722-10.339-.184-14.694-3.63C3.777 35.056 1.422 30.88.695 25.98c-.73-6.168 1.086-11.246 4.715-15.968C9.223 4.754 14.484 1.668 20.832.578c5.266-.906 10.164-.18 14.7 2.723 2.902 1.996 4.898 4.535 6.35 7.62 0 0 0 .18-.726.544zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#2dbcaf;fill-opacity:1" d="M59.117 41.766c-4.898 0-9.433-1.633-13.242-4.715-3.086-2.723-5.262-6.352-5.809-10.707-.906-6.352.727-11.793 4.54-16.692 3.988-5.265 8.89-8.168 15.421-9.254 5.625-.91 10.887-.363 15.606 2.723 4.351 2.902 7.074 7.074 7.62 12.156.907 7.438-1.089 13.61-6.35 18.688-3.63 3.629-8.165 6.168-13.063 7.078-1.633.363-3.266.723-4.723.723zm13.25-22.133c0-.727 0-1.274-.183-1.817-.907-5.445-6.168-8.527-11.25-7.257-5.262 1.086-8.348 4.351-9.797 9.433-.907 4.356 1.09 8.528 4.898 10.344 2.903 1.27 6.172 1.09 9.07-.184 4.54-2.175 6.899-5.804 7.262-10.52zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gpg.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gpl.svg
1
1
<svg height="64" width="65" xmlns="http://www.w3.org/2000/svg"><path d="M21.61 63.91c-.176 0-.352 0-.528-.176C8.434 58.934 0 46.488 0 32.977 0 14.844 14.582.09 32.5.09S65 14.844 65 32.977c0 13.511-8.434 25.957-21.082 30.757a1.12 1.12 0 0 1-1.055 0c-.351-.18-.527-.355-.699-.71l-7.027-18.669c-.352-.71.175-1.601.875-1.777 3.867-1.422 6.324-5.156 6.324-9.422 0-5.511-4.39-9.957-9.836-9.957s-9.836 4.446-9.836 9.957c0 4.09 2.633 7.82 6.324 9.422.7.356 1.051 1.067.875 1.777l-7.027 18.668c-.172.356-.348.711-.7.711 0 .176-.175.176-.527.176zm0 0" fill="#af7931"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gradle.svg
1
1
<svg height="64" width="81" xmlns="http://www.w3.org/2000/svg"><path d="M78.398 6.79C75.508 1.55 70.492 0 66.828 0c-4.437 0-8.101 2.328-7.328 4.074.191.387.965 2.133 1.543 2.906.77 1.165 2.121.196 2.508 0 1.347-.773 2.89-.968 4.433-.773 1.543.191 3.664 1.164 5.207 3.879 3.278 6.398-6.941 19.781-19.863 10.473-13.113-9.118-25.652-6.207-31.437-4.27-5.786 1.941-8.293 3.688-5.977 7.953 3.086 5.82 2.121 4.074 5.012 8.922 4.629 7.758 15.043-3.492 15.043-3.492-7.711 11.441-14.27 8.726-16.778 4.656-2.312-3.492-4.05-7.758-4.05-7.758C-4.336 33.55.87 64 .87 64h9.64C13.02 52.75 21.7 53.14 23.243 64h7.328c6.559-21.914 22.95 0 22.95 0h9.644c-2.7-14.934 5.398-19.586 10.414-28.316 5.399-8.922 10.223-19.586 4.82-28.895zM53.52 35.683c-5.012-1.746-3.278-6.786-3.278-6.786s4.434 1.356 10.414 3.489c-.191 1.36-3.277 4.46-7.136 3.297zm0 0" fill="#02303a"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/gz.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/h.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".25"><path d="M63.773.227H.227v63.546h63.546zM60.324 60.14H4.04V12.574h56.285zm0 0" stroke-width=".4539"/><path d="M9.305 18.203h45.39v6.352H9.305zm7.445 10.348h27.777v2.543H16.75zm3.992 7.808h27.781v2.723h-27.78zm-3.992 8.168h27.777v2.723H16.75zm3.992 8.352h27.781v2.723h-27.78zm0 0" stroke-width=".4539"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/handlebars.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="51"><path style="stroke:none;fill-rule:nonzero;fill:#c19770;fill-opacity:1" d="M30.605 30.332c2.782-.18 5.735.879 7.993 2.824 1.039.883 2.086 2.121 2.777 3.535 1.39-2.296 3.477-4.242 6.082-5.3a15.304 15.304 0 0 1 9.73 0 20.188 20.188 0 0 1 7.293 4.77 62.33 62.33 0 0 0 5.04 4.593c.867.883 1.91 1.41 2.953 1.766 1.562.53 3.297 0 4.343-1.06.868-1.058.868-2.827-.175-4.062-.696-.53-1.563-.53-2.258.176 0 0 0 .176-.176.176-.348.707-.348 1.59.176 2.121-.867-.531-1.563-1.59-1.738-2.828-.172-1.414.87-2.648 2.085-3.004 2.43-.883 5.387-.176 6.774 1.945 1.219 2.118 1.738 4.59 1.39 6.887-.347 2.3-1.566 4.242-3.472 5.656-2.606 1.766-5.734 2.649-9.035 2.297-2.953-.176-6.078-.707-8.688-1.945-4.687-1.941-9.031-4.414-13.722-6.008-1.563-.351-3.473-.879-5.04-1.059h-3.82c-1.562.18-3.125.532-4.515 1.06-4.864 1.593-9.207 4.241-13.899 6.187-3.996 1.59-8.336 2.296-12.504 1.414-2.433-.356-4.691-1.594-6.258-3.535a7.888 7.888 0 0 1-1.906-5.829c-.176-2.12.344-4.066 1.563-5.656 1.214-1.59 3.129-2.297 5.035-2.121 1.215 0 2.262.531 3.129 1.59.52.887.695 1.77.347 2.828-.347.883-.87 1.59-1.566 1.945.352-.53.523-1.414.172-2.12-.516-.708-1.559-.884-2.254-.356-.176 0-.176.18-.348.18-.87.882-.87 2.296-.347 3.355.695 1.059 1.738 1.766 2.949 1.945 1.742 0 3.48-.886 4.691-2.125 2.606-2.648 5.387-4.945 8.34-7.242 2.781-1.941 5.906-2.828 8.86-3zm0-30.215c2.782-.176 5.735.887 7.993 2.828 1.039.883 2.086 2.118 2.777 3.532 1.39-2.297 3.477-4.239 6.082-5.297a15.25 15.25 0 0 1 9.73 0 20.22 20.22 0 0 1 7.293 4.77 62.33 62.33 0 0 0 5.04 4.593c.867.883 1.91 1.414 2.953 1.766 1.562.53 3.297 0 4.343-1.059.868-1.063.868-2.828-.175-4.066-.696-.528-1.563-.528-2.258.18 0 0 0 .175-.176.175-.348.707-.348 1.59.176 2.121-.867-.531-1.563-1.59-1.738-2.828-.172-1.414.87-2.652 2.085-3.004 2.43-.883 5.387-.176 6.778 1.942 1.215 2.12 1.734 4.597 1.387 6.894-.348 2.297-1.563 4.238-3.473 5.652-2.606 1.77-5.734 2.649-9.035 2.297-2.953-.175-6.078-.707-8.684-1.941-4.691-1.945-9.035-4.418-13.723-6.008-1.562-.355-3.476-.887-5.039-1.062h-3.82c-1.566.175-3.129.53-4.52 1.062-4.863 1.59-9.206 4.238-13.898 6.18-3.992 1.593-8.336 2.297-12.504 1.414-2.433-.352-4.691-1.586-6.258-3.531a7.913 7.913 0 0 1-1.906-5.832C-.14 8.773.38 6.832 1.598 5.242c1.214-1.59 3.129-2.297 5.035-2.12 1.215 0 2.262.53 3.129 1.589.52.887.695 1.766.347 2.828-.347.883-.87 1.59-1.566 1.941.352-.527.523-1.41.172-2.117-.516-.707-1.559-.886-2.254-.351-.176 0-.176.172-.348.172-.87.882-.87 2.296-.347 3.359.695 1.059 1.738 1.766 2.949 1.941 1.742 0 3.48-.882 4.691-2.117 2.606-2.648 5.387-4.949 8.34-7.246 2.781-1.941 5.91-2.824 8.86-3.004zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/hbs.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="51"><path style="stroke:none;fill-rule:nonzero;fill:#c19770;fill-opacity:1" d="M30.605 30.332c2.782-.18 5.735.879 7.993 2.824 1.039.883 2.086 2.121 2.777 3.535 1.39-2.296 3.477-4.242 6.082-5.3a15.304 15.304 0 0 1 9.73 0 20.188 20.188 0 0 1 7.293 4.77 62.33 62.33 0 0 0 5.04 4.593c.867.883 1.91 1.41 2.953 1.766 1.562.53 3.297 0 4.343-1.06.868-1.058.868-2.827-.175-4.062-.696-.53-1.563-.53-2.258.176 0 0 0 .176-.176.176-.348.707-.348 1.59.176 2.121-.867-.531-1.563-1.59-1.738-2.828-.172-1.414.87-2.648 2.085-3.004 2.43-.883 5.387-.176 6.774 1.945 1.219 2.118 1.738 4.59 1.39 6.887-.347 2.3-1.566 4.242-3.472 5.656-2.606 1.766-5.734 2.649-9.035 2.297-2.953-.176-6.078-.707-8.688-1.945-4.687-1.941-9.031-4.414-13.722-6.008-1.563-.351-3.473-.879-5.04-1.059h-3.82c-1.562.18-3.125.532-4.515 1.06-4.864 1.593-9.207 4.241-13.899 6.187-3.996 1.59-8.336 2.296-12.504 1.414-2.433-.356-4.691-1.594-6.258-3.535a7.888 7.888 0 0 1-1.906-5.829c-.176-2.12.344-4.066 1.563-5.656 1.214-1.59 3.129-2.297 5.035-2.121 1.215 0 2.262.531 3.129 1.59.52.887.695 1.77.347 2.828-.347.883-.87 1.59-1.566 1.945.352-.53.523-1.414.172-2.12-.516-.708-1.559-.884-2.254-.356-.176 0-.176.18-.348.18-.87.882-.87 2.296-.347 3.355.695 1.059 1.738 1.766 2.949 1.945 1.742 0 3.48-.886 4.691-2.125 2.606-2.648 5.387-4.945 8.34-7.242 2.781-1.766 5.906-2.828 8.86-3zm0-30.215c2.782-.176 5.735.887 7.993 2.828 1.039.883 2.086 2.118 2.777 3.532 1.39-2.297 3.477-4.239 6.082-5.297a15.25 15.25 0 0 1 9.73 0 20.22 20.22 0 0 1 7.293 4.77 62.33 62.33 0 0 0 5.04 4.593c.867.883 1.91 1.414 2.953 1.766 1.562.53 3.297 0 4.343-1.059.868-1.063.868-2.828-.175-4.066-.696-.528-1.563-.528-2.258.18 0 0 0 .175-.176.175-.348.707-.348 1.59.176 2.121-.867-.531-1.563-1.59-1.738-2.828-.172-1.414.87-2.652 2.085-3.004 2.43-.883 5.387-.176 6.778 1.942 1.215 2.12 1.734 4.597 1.387 6.894-.348 2.297-1.563 4.238-3.473 5.652-2.606 1.77-5.734 2.649-9.035 2.297-2.953-.175-6.078-.707-8.684-1.941-4.691-1.945-9.035-4.418-13.723-6.008-1.562-.355-3.476-.887-5.039-1.062h-3.82c-1.566.175-3.129.53-4.52 1.062-4.863 1.59-9.206 4.238-13.898 6.18-3.992 1.593-8.336 2.297-12.504 1.414-2.433-.352-4.691-1.586-6.258-3.531a7.913 7.913 0 0 1-1.906-5.832C-.14 8.773.38 6.832 1.598 5.242c1.214-1.59 3.129-2.297 5.035-2.12 1.215 0 2.262.53 3.129 1.589.52.887.695 1.766.347 2.828-.347.883-.87 1.59-1.566 1.941.352-.527.523-1.41.172-2.117-.516-.707-1.559-.886-2.254-.351-.176 0-.176.172-.348.172-.87.882-.87 2.296-.347 3.359.695 1.059 1.738 1.766 2.949 1.941 1.742 0 3.48-.882 4.691-2.117 2.606-2.648 5.387-4.949 8.34-7.246 2.781-1.766 5.91-2.824 8.86-3.004zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/heic.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/hlp.svg
1
1
<svg height="64" width="59" xmlns="http://www.w3.org/2000/svg"><path d="M59 0H13.41C6.613 0 0 2.668 0 10.668V64h48.273V10.668H6.613c0-3.914 2.684-5.336 5.367-5.336h41.477v53.336l5.363-5.336V0zm0 0" fill="#c93"/><path d="M21.992 40.18c0-5.512 6.434-6.403 6.434-10.493 0-1.777-1.61-3.199-3.754-3.199-2.324.18-4.11 1.778-4.11 1.778L17.88 24.89s2.683-2.848 7.332-2.848c4.289 0 8.402 2.668 8.402 7.289 0 6.402-6.797 7.113-6.797 11.203v1.422h-4.824zm0 5.152h4.824v4.445h-4.824zm0 0" fill="#fff"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/hs.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="59"><path style="stroke:none;fill-rule:nonzero;fill:#8f4e8b;fill-opacity:1" d="m.469 59 19.367-29.5L.469 0h14.379l19.367 29.5L14.848 59zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#8f4e8b;fill-opacity:1" d="m19.836 59 19.363-29.5L19.836 0h14.379l38.73 59h-14.57L46.293 40.633 34.215 59zm46.59-17.191-6.328-9.965H82.53v9.965zm-9.59-14.653-6.516-9.965h32.211v9.965zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/hsl.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="59"><path style="stroke:none;fill-rule:nonzero;fill:#8f4e8b;fill-opacity:1" d="m.469 59 19.367-29.5L.469 0h14.379l19.367 29.5L14.848 59zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#8f4e8b;fill-opacity:1" d="m19.836 59 19.363-29.5L19.836 0h14.379l38.73 59h-14.57L46.293 40.633 34.215 59zm46.59-17.191-6.328-9.965H82.53v9.965zm-9.59-14.653-6.516-9.965h32.211v9.965zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/htm.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#d75b26;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#d75b26;stroke-opacity:1;stroke-miterlimit:10" d="M11.241 25.867c-.498 0-1.1-.2-1.5-.6l-8.398-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-7 6.9 6.9 6.9c.8.8.8 2.098 0 3-.4.4-.9.6-1.402.6zm25 0c-.5 0-1.099-.2-1.499-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.3c-.4.5-.898.7-1.5.7zm-16.698 4.1c-.2 0-.402 0-.7-.1-1.1-.399-1.7-1.5-1.3-2.599l8.399-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/html.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#d75b26;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#d75b26;stroke-opacity:1;stroke-miterlimit:10" d="M11.241 25.867c-.498 0-1.1-.2-1.5-.6l-8.398-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-7 6.9 6.9 6.9c.8.8.8 2.098 0 3-.4.4-.9.6-1.402.6zm25 0c-.5 0-1.099-.2-1.499-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.3c-.4.5-.898.7-1.5.7zm-16.698 4.1c-.2 0-.402 0-.7-.1-1.1-.399-1.7-1.5-1.3-2.599l8.399-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ibooks.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/icns.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M33.383.531c.176-.707.527-.707.699 0l7.176 22.582c.176.707.875 1.235 1.574 1.235h23.45c.698 0 .698.351.175.707L47.383 38.816c-.524.528-.7 1.235-.524 1.942l7.172 22.582c.176.707 0 .883-.523.351L34.434 49.754c-.524-.352-1.399-.352-1.926 0L13.438 63.69c-.528.356-.876.176-.528-.351l7.176-22.582c.176-.707 0-1.414-.527-1.942L.489 24.88c-.528-.356-.352-.707.175-.707H24.11c.7 0 1.399-.531 1.575-1.235zm0 0" fill="#8ed200"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ico.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M33.383.531c.176-.707.527-.707.699 0l7.176 22.582c.176.707.875 1.235 1.574 1.235h23.45c.698 0 .698.351.175.707L47.383 38.816c-.524.528-.7 1.235-.524 1.942l7.172 22.582c.176.707 0 .883-.523.351L34.434 49.754c-.524-.352-1.399-.352-1.926 0L13.438 63.69c-.528.356-.876.176-.528-.351l7.176-22.582c.176-.707 0-1.414-.527-1.942L.489 24.88c-.528-.356-.352-.707.175-.707H24.11c.7 0 1.399-.531 1.575-1.235zm0 0" fill="#8ed200"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ics.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M5.102 59.219v-10.11h11.605v10.11zm14.066 0v-10.11h12.836v10.11zM5.102 46.805V35.457h11.605v11.348zm14.066 0V35.457h12.836v11.348zM5.102 33.152V23.047h11.605v10.105zM34.645 59.22v-10.11H47.48v10.11zM19.168 33.152V23.047h12.836v10.105zm30.95 26.067v-10.11h11.605v10.11zM34.644 46.805V35.457H47.48v11.348zm-14.07-30.496c0 .53-.528 1.062-1.231 1.062h-2.637c-.703 0-1.23-.531-1.23-1.062V6.203c0-.535.527-1.066 1.23-1.066h2.461c.703 0 1.23.531 1.23 1.066V16.31zm29.542 30.496V35.457h11.606v11.348zM34.645 33.152V23.047H47.48v10.105zm15.472 0V23.047h11.606v10.105zm1.406-16.843c0 .53-.527 1.062-1.23 1.062h-2.637c-.703 0-1.23-.531-1.23-1.062V6.203c0-.535.527-1.066 1.23-1.066h2.637c.703 0 1.23.531 1.23 1.066zM67 14.004c0-2.484-2.285-4.434-5.102-4.434h-5.097V6.203c0-3.016-2.813-5.676-6.508-5.676h-2.637c-3.515 0-6.508 2.48-6.508 5.676V9.57H25.676V6.203c0-3.016-2.817-5.676-6.508-5.676h-2.637c-3.52 0-6.508 2.48-6.508 5.676V9.57H5.102C2.285 9.57 0 11.7 0 14.004v45.035c0 2.484 2.285 4.434 5.102 4.434h56.62c2.817 0 5.102-2.13 5.102-4.434V14.004zm0 0" fill="#111"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/idx.svg
1
1
<svg height="63" width="48" xmlns="http://www.w3.org/2000/svg"><path d="M47.66 38.852c0 3.89-2.894 17.074-2.894 17.074-.68 3.21-3.575 6.594-7.317 6.594H19.75c-2.555 0-4.938-1.524-5.957-3.891 0 0-8.68-15.887-12.082-21.637-2.383-4.054-2.383-4.054.68-5.746a2.905 2.905 0 0 1 1.703-.508c1.191 0 2.039.676 2.89 1.692l5.278 6.086 1.531 2.027V3.863c0-1.86 1.703-3.383 3.742-3.383 1.875 0 3.406 1.524 3.406 3.383l.68 23.664h1.531l.34-4.054c0-1.86 1.531-3.383 3.406-3.383 1.872 0 3.403 1.523 3.403 3.383l.34 4.898h1.53l.34-3.21c0-1.86 1.532-3.38 3.407-3.38 1.871 0 3.402 1.52 3.402 3.38l.34 3.21v.848h1.192l.34-1.692c0-1.859 1.53-3.379 3.406-3.379 1.87 0 3.402 1.52 3.402 3.38-.34 0-.34 7.437-.34 11.324zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/iff.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ifo.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0" fill="#bababa"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/image.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32C64 14.43 49.57 0 32 0S0 14.43 0 32s14.43 32 32 32c17.57-.629 32-14.43 32-32zm0 0" fill="#3c3"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/img.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/iml.svg
1
1
<svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zm-6.4-10.5c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/inc.svg
1
1
<svg height="64" width="71" xmlns="http://www.w3.org/2000/svg"><path d="m12.225 17.126-7.7 7.598c-.9 1.001-1.4 2.201-1.4 3.501 0 1.2.5 2.5 1.4 3.4l.1.1c.9.9 2.2 1.4 3.4 1.4 1.3 0 2.5-.5 3.5-1.4l8.6-8.6 9.3-9.3c.4-.4.6-1 .6-1.5s-.2-1.101-.6-1.5-1-.6-1.5-.6-1.1.2-1.5.6l-13.4 13.3c-.6.6-1.5.6-2.2 0-.6-.6-.6-1.6 0-2.2l13.3-13.299c1-1 2.3-1.502 3.7-1.502 1.3 0 2.7.501 3.7 1.502 1 .998 1.5 2.299 1.5 3.698 0 1.3-.5 2.7-1.5 3.701l-9.3 9.3-8.6 8.6c-1.5 1.5-3.6 2.3-5.6 2.3-2 0-4-.7-5.499-2.2l-.1-.1c-1.5-1.5-2.3-3.501-2.3-5.6 0-2.001.8-4.1 2.3-5.6l8.6-8.599 10.899-11c2-2.001 4.6-3.002 7.3-3.002s5.3 1 7.3 3.002c1.999 1.999 2.999 4.6 2.999 7.3 0 2.6-1 5.3-3 7.299l-14.9 14.9c-.6.599-1.599.599-2.199 0-.6-.6-.6-1.6 0-2.201l14.9-14.898c1.4-1.4 2.1-3.301 2.1-5.1s-.7-3.701-2.1-5.101-3.3-2.1-5.2-2.1c-1.9 0-3.7.7-5.1 2.1zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".25" transform="matrix(1.7579 0 0 1.76066 .65 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/indd.svg
1
1
<svg height="64" width="58" xmlns="http://www.w3.org/2000/svg"><path d="M.624 29.625V.725h3.9v28899-4-9.8901zm0 0 stroke-width="1.25" transform="matrix(2.09009 0 0 2.08311 0 .076)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/inf.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/info.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M35.2 13.332c-1.067 0-1.954.355-2.665 1.066-.715.711-1.07 1.602-1.07 2.668s.355 1.957 1.07 2.668c.711.711 1.598 1.067 2.664 1.067 1.067 0 1.957-.356 2.668-1.067.711-.71 1.067-1.601 1.067-2.668s-.356-1.957-1.067-2.668c-.535-.71-1.422-1.066-2.668-1.066zm1.777 12.09-.176.355h.176zm-.176.355c-3.555.535-6.934.711-10.489 1.246l-.355 1.598h.887c.535 0 1.066.18 1.422.535s.535.711.535 1.067c0 .53-.18.886-.535 2.132l-3.73 12.622C24.18 46.043 24 46.754 24 47.465c0 1.07.355 1.781 1.066 2.492.711.711 2.844.887 3.91.887 3.024 0 8-1.598 10.669-6.223l-2.133-1.242c-1.067 1.777-3.024 3.02-4.09 3.555-1.067.53-1.602.355-1.777.355-.18 0-.356 0-.536-.18-.175-.175-.175-.355-.175-.53 0-.356.175-1.067.53-2.134zm0 0"/><path d="M32 1.777C15.29 1.777 1.777 15.29 1.777 32S15.29 62.223 32 62.223 62.223 48.71 62.223 32 48.71 1.777 32 1.777zm0 3.38c14.934 0 26.844 12.09 26.844 26.843 0 14.934-12.09 26.844-26.844 26.844S5.156 46.754 5.156 32C5.156 17.066 17.066 5.156 32 5.156zm0 0" stroke="#999" stroke-miterlimit="10" stroke-width="3.55556"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ini.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M41.266 22.992c0-3.945-2.403-7.035-5.664-8.406V3.262C35.602 1.372 34.23 0 32.344 0s-3.262 1.371-3.262 3.262v11.324c-3.43 1.2-5.66 4.46-5.66 8.406 0 3.945 2.402 7.035 5.66 8.406 0 .172-.172.516-.172.688V60.57c0 1.887 1.375 3.258 3.262 3.258s3.258-1.371 3.258-3.258V31.914c0-.344 0-.516-.168-.687 3.601-1.028 6.004-4.29 6.004-8.235zm-9.094 2.574c-1.371 0-2.402-1.03-2.402-2.402 0-1.375 1.03-2.402 2.402-2.402s2.402 1.027 2.402 2.402c.172 1.2-1.031 2.402-2.402 2.402zM58.254 3.602c0-1.887-1.375-3.258-3.262-3.258s-3.262 1.37-3.262 3.258v26.597c-3.43 1.2-5.66 4.461-5.66 8.406 0 3.946 2.403 7.036 5.66 8.407 0 .172-.171.515-.171.687v13.04c0 1.89 1.375 3.261 3.261 3.261 1.887 0 3.262-1.371 3.262-3.262V47.7c0-.344 0-.515-.172-.687 3.43-1.2 5.66-4.461 5.66-8.407 0-3.945-2.402-7.035-5.66-8.406V3.602zm-3.262 37.406c-1.37 0-2.402-1.028-2.402-2.403 0-1.37 1.031-2.402 2.402-2.402 1.371 0 2.403 1.031 2.403 2.402 0 1.375-1.032 2.403-2.403 2.403zm-48.73 19.39c0 1.887 1.375 3.258 3.261 3.258 1.887 0 3.258-1.37 3.258-3.258V47.355c0-.343 0-.511-.172-.683 3.434-1.203 5.664-4.461 5.664-8.41 0-3.946-2.402-7.035-5.664-8.407V3.602c0-1.887-1.37-3.258-3.257-3.258S6.09 1.714 6.09 3.602v26.597C2.66 31.4.43 34.66.43 38.605c0 3.946 2.402 7.036 5.66 8.407 0 .172-.172.515-.172.687v13.04c0-.34.344-.34.344-.34zm3.261-24.367c1.372 0 2.403 1.032 2.403 2.403 0 1.375-1.031 2.402-2.403 2.402-1.375 0-2.402-1.027-2.402-2.402 0-1.371 1.027-2.403 2.402-2.403zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/inv.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M47.914 26.813V9.688L31.828 0 15.914 9.512v17.3L0 35.462v19.2L16.434 64 32 55.004 47.566 64 64 54.66V35.633zm-2.941 0-11.59 6.398V20.066l11.59-6.746zM31.828 3.633l11.414 6.918-11.414 6.746-11.07-6.918zM4.844 36.324l12.8-6.746 11.243 6.399-12.453 7.265zm12.972 9.512 12.625-7.262v13.492l-12.625 7.438zm17.47-9.86 11.245-6.398 12.797 6.918-11.762 6.746zm25.6 16.782-11.761 6.746V45.836l11.762-6.742zm0 0" fill="#938886"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/iso.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/j2.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M39.06.14s-7.599 3.599-13.7 4.4c-6.1.798-17.9 1.6-20.2 1-2.3-.601-4-1.201-4-1.201l.501 1.6-1.5 1.3.3.5h.6l1.9.201.3.799 1.1.1.501 1.8s2.2.2 2.9.2c.698 0 1.999-.101 1.999-.101v.7l.501.101v.801l-1.1.9h.3v.298s-2.4.201-3.302 0c-1-.098-1.098-.098-1.098-.098l-.101.098v.402h.2l.101 1.798 4.899-.198-.499 6.6v.798l-4.101-.198v-1.7h.9l.1-.701.801-.2.099-.2-3.2-.7-2.4.7.4.3h.4v.6h.8v1.6l-.899.2.2.4.2.1v1.301h.6v5.9l-.901.1.101 1.7.6.098-.101 4h2.5l-.499-3.8 3.798-.1-.299 2.1-.4 1.502h3.801v-3.702l2.599-.198-.099 2.398-.1 1.401h2.198l-.099-3.8h.301l.299-1.9h-.4l-.1-.8-.2-2.2.1-2.5h.501v-1.5h-.602l.101-1.8.701-.1v-.601l.4-.098.4-.301-2.5-.5-2.5.6.2.4h.6l.098.7h.701v1.6l-3-.1.1-1.7.101-1.5v-2l.1-2.099 5.999-.301 7.1-.4.101 1.299-.301 3.202-.099 2.999h-2.6v-2.1h.999v-.6l.4-.1v-.101h.4l.2-.299-3-.7-2.9.6.2.399h.3v.2h.4v.701h.9v1.9h-.9v.4h.3v1.6h.6l-.099 6.4-.802.2v.3h.301v1.6h.501l-.2 2.3-.2 1.9 3.1.101-.101-1.7-.2-1.401v-1.2h2.601l-.101 1.9v2l1.5.1 2-.1.6-.2-.4-1.1-.1-1.7-.2-1.2 2.5-.1-.1 1.5v2.3l1 .1h1.001l.499-.2-.3-2.2-.1-1.6h.4v-1.5h.1v-.299h-.5l-.099-1.1V24.74h.4v-1.6h.2v-.301h-.6V21.04l.898-.1-.098-.601h.4v-.1l.498-.3-2.7-.6-2.5.6.2.3h.402v.1h.4v.7h.898l.102 1.7H28.06l-.4-2.6-.299-1.4-.2-2.501.1-1.399 6.3-.5v-2.1l.298-.1v-.301l-.2-.098s-3.5.5-4.6.6c-.298 0-.298 0-.498.098v-.4l-1.3-.7v-.799h.5v-1.1s2.7-.2 3.7-.399c.999-.2 2.398-.502 2.398-.502l.701-1.798 1.3-.402.1-.299 3-.801.3-.299-1.1-2.499.1-.6.6-.301.4-1.299zM24.662 9.638v.901h.699v.9l-1.3.901-.1.299h.501v.2l-4.2.2v-.6l.2-.3v-.6l.099-.299v-.402l.2-.698zm-8.502.801-.098.6.298.4v.7l.301.5-.1.5.2.301-4.199.199v-.1l.499-.099v-.2l-1.099-.601-.1-.9h.5v-1.001zm6.101 14.1 2.5.2-.099 3.4.299 2.8-2.7-.1zm-16.9.1 4 .2-.3 2.3v2l.3 1.1-4 .2zm25.9 0v5.8l-2.6.2-.1-3.1-.3-2.8zm-19.5.1h3v5.4h-2.799l-.1-1.7v-1.7zm0 0" fill="#b41717" stroke="#b41717" stroke-miterlimit="10" stroke-width=".25" transform="matrix(1.7 0 0 1.71166 0 .105)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/jar.svg
1
1
<svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zm0 0" fill="#265db4" stroke="#265db4" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M37.8 64.8c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><g fill="#265db4" stroke="#265db4"><path d="M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/java.svg
1
1
<svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zm0 0" fill="#265db4" stroke="#265db4" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M37.8 64.8c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><g fill="#265db4" stroke="#265db4"><path d="M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/jpg.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/js.svg
1
1
<svg height="64" width="75" xmlns="http://www.w3.org/2000/svg"><path d="M.5 19v-4.1c.9-.1 1.6-.2 2-.4.4-.2.8-.6 1.2-1.001.4-.5.5-1.1.7-1.9.1-.6.2-1.499.2-2.799 0-2.201.1-3.7.4-4.6.2-.8.6-1.6 1.2-2 .5-.5 1.4-.9 2.5-1.2.7-.2 1.9-.4 3.5-.4h.9v3.9c-1.3 0-2.2.1-2.6.3-.4.2-.6.4-.9.6-.2.3-.3.7-.3 1.501 0 .8-.1 2-.2 4.099-.101 1.2-.2 2-.4 2.801-.301.6-.6 1.2-1 1.8-.4.4-1 .9-1.8 1.399.7.4 1.3.8 1.8 1.3s.8 1.2 1.1 1.899c.3.702.4 1.802.4 3.001.1 1.9.1 3.1.1 3.599 0 .702.1 1.202.3 1.602.2.4.5.5.9.6.4.2 1.2.3 2.6.3v4.098h-1c-1.6 0-2.9-.1-3.701-.4-.9-.3-1.6-.6-2.2-1.2-.6-.6-.999-1.2-1.2-1.999-.198-.8-.299-2.1-.299-4 0-2-.1-3.5-.3-4.1-.3-.9-.7-1.601-1.201-2-.698-.5-1.5-.7-2.7-.7zm39.1 0c-.9.1-1.6.2-2 .4s-.8.6-1.2 1.001c-.4.5-.5 1.1-.7 1.9-.099.6-.2 1.499-.2 2.799 0 2.201-.1 3.7-.4 4.6-.2.9-.6 1.6-1.2 2-.5.5-1.4.9-2.5 1.2-.7.2-1.9.4-3.5.4h-.999v-4.1c1.298 0 2.1-.1 2.599-.3s.7-.4.899-.6c.2-.3.301-.7.301-1.501 0-.6.1-2 .2-3.999.099-1.2.3-2.1.5-2.8.3-.7.6-1.3 1.1-1.9.4-.5 1-.9 1.7-1.3-.901-.6-1.6-1.1-2-1.6-.5-.7-1-1.801-1.201-2.8-.199-.8-.299-2.6-.299-5.2 0-.8-.1-1.4-.301-1.8-.199-.3-.4-.5-.799-.6-.2-.3-1-.3-2.5-.3v-4h.999c1.602 0 2.9.1 3.7.4.902.3 1.6.6 2.2 1.2.6.6 1.002 1.2 1.2 2 .201.8.402 2.1.402 4 0 2 .098 3.4.299 4.1.299.9.7 1.601 1.2 1.9.5.4 1.401.6 2.5.6.1.1 0 4.3 0 4.3zm0 0" fill="#307ac6" stroke="#307ac6" stroke-miterlimit="10" transform="matrix(1.86825 0 0 1.87558 0 .209)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/json.svg
1
1
<svg height="64" width="77" xmlns="http://www.w3.org/2000/svg"><g fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".5"><path d="M16.4 67.5v-4.1c.901-.1 1.6-.2 2-.4.4-.2.8-.6 1.2-1 .4-.5.5-1.1.7-1.9.1-.6.2-1.5.2-2.8 0-2.2.1-3.7.4-4.6.2-.8.599-1.6 1.2-2 .5-.5 1.4-.9 2.5-1.2.7-.2 1.9-.4 3.5-.4h.9V53c-1.3 0-2.2.1-2.6.3-.4.2-.6.4-.9.6-.2.3-.3.7-.3 1.5 0 .801-.1 2-.2 4.1-.1 1.2-.2 2-.4 2.8-.3.6-.6 1.2-1 1.8-.4.4-1 .9-1.8 1.401.7.4 1.301.8 1.8 1.299.5.499.801 1.2 1.1 1.9.3.7.4 1.8.4 3 .1 1.9.1 3.1.1 3.6 0 .7.1 1.2.3 1.6.199.4.5.5.9.6.4.2 1.2.3 2.6.3v4.1h-1c-1.6 0-2.9-.1-3.7-.4-.9-.3-1.6-.6-2.2-1.199-.601-.601-1-1.2-1.2-2.002-.2-.799-.3-2.1-.3-4 0-2-.1-3.5-.3-4.1-.3-.898-.7-1.6-1.2-1.999-.7-.5-1.5-.7-2.7-.7zm39.1 0c-.9.1-1.6.2-2 .4-.401.2-.8.6-1.2 1-.4.5-.499 1.1-.7 1.9-.1.6-.2 1.5-.2 2.8 0 2.2-.1 3.7-.4 4.6-.2.9-.6 1.6-1.2 2-.5.5-1.4.9-2.501 1.2-.698.2-1.9.4-3.5.4h-1v-4.1c1.3 0 2.101-.1 2.6-.3.501-.2.7-.4.902-.6.2-.3.3-.7.3-1.5 0-.601.098-2 .199-4 .1-1.2.3-2.099.499-2.8.302-.7.602-1.3 1.1-1.9.401-.5 1.001-.9 1.701-1.3-.9-.6-1.6-1.1-2-1.6-.5-.7-1-1.8-1.2-2.8-.2-.8-.3-2.6-.3-5.2 0-.8-.1-1.401-.3-1.8-.2-.3-.4-.5-.8-.6-.2-.3-1-.3-2.5-.3v-4h1c1.6 0 2.9.1 3.7.4.9.3 1.6.6 2.2 1.199.6.601 1 1.2 1.2 2.002.2.799.4 2.1.4 4 0 2 .1 3.4.301 4.1.3.898.698 1.6 1.2 1.9.499.399 1.398.598 2.499.598.1.1 0 4.302 0 4.302zm0 0" transform="matrix(1.90195 0 0 1.91617 -29.917 -93.413)"/><path d="M44.1 67.1c-.7-.3-1.2-.9-1.2-1.599 0-.701.5-1.4 1.2-1.6.299-.1.4-.3.299-.502-.3-.799-.499-1.598-.998-2.2-.1-.3-.4-.3-.602-.2-.2.1-.499.3-.799.3-1 0-1.7-.799-1.7-1.7 0-.3.1-.599.3-.799.1-.3 0-.4-.2-.6-.7-.4-1.499-.7-2.2-1-.3-.1-.4.1-.5.3-.3.7-.9 1.2-1.6 1.2s-1.4-.5-1.6-1.2c-.101-.3-.3-.4-.5-.3-.8.3-1.6.5-2.2 1-.301.1-.301.4-.2.6.2.3.3.5.3.8 0 1-.8 1.7-1.699 1.7-.302 0-.602-.1-.801-.3-.3-.1-.4 0-.6.2-.4.7-.7 1.5-1 2.2-.1.3.1.4.3.5.7.3 1.2.9 1.2 1.601 0 .7-.5 1.398-1.2 1.598-.3.1-.4.302-.3.502.3.799.5 1.6 1 2.2.1.299.4.299.6.2.3-.2.5-.3.801-.3.998 0 1.699.799 1.699 1.7 0 .3-.1.599-.3.799-.101.3 0 .4.2.6.7.4 1.5.7 2.2 1 .2 0 .399-.1.399-.3.302-.7.902-1.2 1.602-1.2.698 0 1.399.5 1.6 1.2.098.3.3.4.499.3.801-.3 1.6-.5 2.2-1 .3-.1.3-.4.2-.6-.1-.3-.301-.5-.301-.8 0-1 .801-1.7 1.7-1.7.3 0 .6.1.802.3.3.1.4 0 .6-.2.4-.701.7-1.5 1-2.2.199-.1.098-.4-.202-.5zm-8.3 1c-1.5 0-2.699-1.2-2.699-2.701 0-1.498 1.2-2.699 2.699-2.699 1.499 0 2.7 1.2 2.7 2.699.1 1.5-1.201 2.701-2.7 2.701zm0 0" transform="matrix(1.90195 0 0 1.91617 -29.917 -93.413)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/jsp.svg
1
1
<svg height="63" width="49" xmlns="http://www.w3.org/2000/svg"><path d="M24.842 21.014c2.2-.7 4.4.7 4.202 2.8-.2 2.4-3.302 3.601-5.1 4.2l.1-.099-.1-.101c1.3-1 3.898-2.199 3.7-4.6-.1-1.2-1.001-2.098-2.702-2v-.2zm-16.099.401.1-.101c-1 0-1.901.4-2.701.7-.798.3-1.8.401-2.198 1.3.399.7 1.4.7 2.398.802 3.4.5 8.302.398 11.701 0 1.799-.201 3.4-.401 4.2-1.201l-.1-.101.1-.1c-3.4.402-7.8 1-11.9.8-1.301-.099-3-.099-3.7-.8.4-.7 1.4-.9 2.1-1.299zm19.9 14.099v-.1c-5.1 2.5-13.201 2.8-20.5 2.201l.1.1-.1.2c2.999.5 6.9.7 10.7.398 3.7-.198 8.199-.698 9.9-2.698zm-14.4-15.398h.1c-.8-1.802-2.3-2.602-2.499-4.7-.2-1.902.7-3.102 1.598-4 1.101-1.201 2.702-2.201 3.901-3.5 1.6-1.803 3.4-4.5 1.9-7.102l-.101.101-.299-.101c.4 2.5-.6 4.101-1.901 5.4-.999 1.201-2.6 2.201-4 3.3-1.6 1.3-3.7 2.901-3.1 5.3.502 2.302 2.8 3.9 4.102 5.4zm8-11.602-.1-.099c-2.7 1-6.701 2.6-7.1 5.698-.1 1.503.399 2.602.9 3.401.4.602 1.1 1 1.3 1.901.2.8 0 1.6-.2 2.2h.1l.1.1c1.099-.8 2.2-1.901 1.899-3.401-.198-1.5-1.9-2.5-2.1-3.899-.1-.802.1-1.5.401-1.9 1.1-1.701 3.5-2.902 4.8-4zm-13.8 17.401-.101-.101c-.5.301-1.5.4-1.4 1.2.1.8 1.5 1 2.2 1.2 3.7.8 9.2.3 11.902-.599l-.1-.101.1-.099c-.301-.101-.7-.7-1.3-.7-.502-.1-1.6.299-2.602.4-1.6.199-3.299.3-4.799.199-1.101-.1-4.5-.1-3.9-1.399zm.9 4.099.1-.1c-.6.201-1.3.4-1.3 1.1 0 .601 1.2 1 1.9 1.3 3.299 1 8.5.4 10.9-.699-.2-.302-.6-.4-.9-.601-.4-.1-.7-.3-1.1-.5-2.001.5-5.1.7-7.5.4-.7-.1-1.701-.1-1.9-.799zm17.699 3.2-.1-.099c-.098 1-1.3 1.1-2.1 1.3-.898.2-1.9.398-2.998.5-4.902.599-11.5.898-16.302 0-.898-.102-2.2-.401-2.499-1.102.4-.698 1.5-.8 2.399-1.198l-.098-.101.098-.1c-1.2.1-2.1.4-2.998.701-.7.3-1.701.698-1.902 1.5.6.8 1.801.8 2.8 1 6.6 1 15.7 1.198 21.402-.7.998-.401 3.098-1 2.1-1.901zm-3.7-5.099c.2 0 .4-.101.702-.2m.898-6.8c-.198 0-.399.1-.7.1m-2.2 1.7c.1 0 .2-.101.401-.101m-12.5-1.6c-.4.1-.8.1-1.3.201m-2.2 15.898c.499.2 1.099.2 1.7.401m20.5-2.2c.1-.1.2-.2.3-.399M19.043.814c0-.099-.098-.3-.098-.399m-4.702 19.7c.1.1.301.4.402.5m2.298 1.199c.1-.1.2-.198.3-.399m5.7-13.2c-.3.099-.499.2-.7.398m-1.198 18.802h.198m-12.6-1.8c0 .1-.2.1-.2.199m.9 4.2.101-.1m-2.8 2.701c-.4 0-.7.1-1 .1m21.399.5c0-.1-.1-.1-.1-.1h-.098" fill="#666" stroke="#666" stroke-miterlimit="10" transform="matrix(1.63519 0 0 1.61722 .336 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/jsx.svg
1
1
<svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><g fill="#72bed3" stroke="#72bed3" stroke-miterlimit="10" stroke-width=".75"><path d="M56.4 65.6c0-2.7-3.4-5.3-8.5-6.9 1.2-5.3.7-9.5-1.7-10.9-.6-.3-1.2-.4-1.9-.4v1.9c.4 0 .7.1.9.201 1.1.7 1.7 3.098 1.2 6.299-.1.8-.2 1.6-.4 2.4-1.7-.401-3.4-.7-5.3-.9-1.099-1.6-2.3-2.9-3.4-4.2 2.8-2.5 5.3-3.9 7-3.9V47.3c-2.3 0-5.3 1.7-8.301 4.5-3.098-2.8-6.098-4.4-8.3-4.4V49.3c1.802 0 4.201 1.3 7 3.9-1.198 1.2-2.3 2.6-3.4 4.2-1.9.2-3.698.399-5.3.9-.098-.9-.298-1.7-.4-2.4-.4-3.1.102-5.7 1.201-6.301.2-.098.6-.198.9-.198v-1.902c-.7 0-1.3.1-1.9.402-2.3 1.3-2.8 5.498-1.7 10.899-5.3 1.6-8.7 4.199-8.7 6.9 0 2.7 3.4 5.3 8.5 6.9-1.2 5.301-.7 9.5 1.7 10.9.6.3 1.2.401 1.9.401 2.3 0 5.3-1.7 8.301-4.5 3.098 2.8 6.098 4.4 8.3 4.4.698 0 1.299-.1 1.9-.401C48.3 82.1 48.8 77.9 47.7 72.5c5.4-1.7 8.7-4.2 8.7-6.9zm-10.8-5.601c-.3 1.1-.7 2.202-1.1 3.3-.3-.699-.701-1.299-1.099-1.998-.4-.7-.8-1.302-1.2-2.002 1.2.2 2.3.401 3.399.7zM41.8 68.9c-.7 1.099-1.3 2.2-1.999 3.098-1.2.1-2.401.2-3.7.2s-2.501-.1-3.8-.1c-.7-.998-1.3-1.999-2.002-3.1-.7-1.099-1.198-2.2-1.799-3.299.6-1.1 1.1-2.2 1.799-3.3.702-1.1 1.302-2.2 2.001-3.101 1.2-.098 2.5-.098 3.8-.098s2.5.098 3.8.098c.7 1 1.3 2.002 1.999 3.1.701 1.101 1.2 2.2 1.801 3.301-.7 1.001-1.3 2.1-1.9 3.2zm2.7-1.101c.4 1.1.9 2.2 1.199 3.3-1.1.2-2.298.4-3.4.7.4-.7.8-1.301 1.2-2 .3-.6.601-1.3 1.001-2zM36 76.7C35.2 75.9 34.4 74.9 33.7 74c.798 0 1.5.1 2.298.1.8 0 1.6 0 2.3-.1-.7.999-1.5 1.9-2.3 2.7zm-6.2-4.9c-1.2-.1-2.3-.4-3.399-.7.3-1.1.7-2.199 1.1-3.3.301.7.699 1.3 1.1 2 .4.699.8 1.3 1.2 2zM36 54.5c.8.8 1.6 1.799 2.3 2.7-.798 0-1.5-.1-2.3-.1-.798 0-1.599 0-2.298.1.7-1 1.5-1.9 2.298-2.7zm-6.2 4.9c-.4.7-.8 1.301-1.2 2-.4.7-.7 1.3-1.1 2-.4-1.101-.899-2.2-1.2-3.3 1.2-.4 2.3-.6 3.5-.7zm-7.5 10.4c-3-1.2-4.9-2.9-4.9-4.2 0-1.3 1.9-3 4.9-4.2.701-.3 1.502-.601 2.3-.9.4 1.701 1.102 3.3 1.9 5.1a46.22 46.22 0 0 0-1.9 5c-.798-.199-1.599-.4-2.3-.8zm4.4 11.899c-1.1-.7-1.7-3.098-1.198-6.299.099-.8.2-1.6.4-2.4 1.7.401 3.4.7 5.3.9 1.1 1.6 2.3 2.9 3.4 4.2-2.8 2.5-5.3 3.9-7 3.9-.2-.1-.6-.1-.902-.301zM46.5 75.4c.4 3.1-.099 5.7-1.198 6.299-.2.1-.6.2-.902.2-1.799 0-4.2-1.299-7-3.9 1.201-1.199 2.3-2.599 3.4-4.2 1.9-.2 3.701-.398 5.3-.9.2.8.301 1.701.4 2.501zm3.2-5.6c-.7.3-1.5.601-2.298.9-.4-1.701-1.102-3.3-1.9-5.1a46.22 46.22 0 0 0 1.9-5c.798.199 1.599.6 2.298.899C52.7 62.7 54.6 64.4 54.6 65.7c-.102 1.2-2.002 2.9-4.902 4.1zm0 0" transform="matrix(1.69822 0 0 1.71352 -24.966 -80.407)"/><path d="M39.801 65.6a3.8 3.8 0 1 1-7.601 0 3.8 3.8 0 0 1 7.601 0zm0 0" transform="matrix(1.69822 0 0 1.71352 -24.966 -80.407)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/key.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/kf8.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/kmk.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#1f385e"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ksh.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/kt.svg
1
1
<svg height="63" width="60" xmlns="http://www.w3.org/2000/svg"><path d="M.125 47.379V28.586c0-1.125.379-1.879 1.523-2.441 14.29-7.707 28.579-15.598 42.868-23.493 2.097-.937 4.004-2.254 6.48-2.254 4 .188 7.43 2.442 8.57 6.204 1.145 3.757 0 8.078-3.238 10.144-3.617 2.258-7.621 4.324-11.43 6.578C30.61 31.031 16.32 38.922 1.84 46.816c-.574.188-.953.375-1.715.563zm0 0" fill="#e88e3d"/><path d="M22.797 40.426c.57-.375.953-.563 1.144-.938 4.762-2.633 9.524-5.074 13.907-7.52.953-.562 1.715-.562 2.668.188 5.336 4.887 10.859 9.399 16.004 14.285 3.046 2.63 3.812 6.012 2.667 9.582-.953 3.57-3.62 5.641-7.43 6.204-2.476.375-4.952-.375-6.859-1.88-7.242-6.577-14.48-13.156-22.101-19.921.191.375.191.187 0 0zM.125 22.008c0-4.695-.383-9.207.191-13.528C.886 3.406 5.84.398 11.367.96c4.57.567 8.383 5.263 8 9.774 0 .563-.379 1.13-.953 1.317-5.906 3.195-11.812 6.578-17.91 9.77.191.187 0 0-.379.187zm19.242 20.297c0 4.324.192 8.082 0 12.027-.379 4.7-4.762 8.27-9.336 8.27-4.57 0-8.953-3.383-9.715-7.891-.191-1.316 0-2.258 1.332-3.008 5.336-3.008 10.86-5.828 16.196-8.832.379 0 .761-.187 1.523-.566zm0 0" fill="#5171a5"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/kts.svg
1
1
<svg height="63" width="60" xmlns="http://www.w3.org/2000/svg"><path d="M.125 47.379V28.586c0-1.125.379-1.879 1.523-2.441 14.29-7.707 28.579-15.598 42.868-23.493 2.097-.937 4.004-2.254 6.48-2.254 4 .188 7.43 2.442 8.57 6.204 1.145 3.757 0 8.078-3.238 10.144-3.617 2.258-7.621 4.324-11.43 6.578C30.61 31.031 16.32 38.922 1.84 46.816c-.574.188-.953.375-1.715.563zm0 0" fill="#e88e3d"/><path d="M22.797 40.426c.57-.375.953-.563 1.144-.938 4.762-2.633 9.524-5.074 13.907-7.52.953-.562 1.715-.562 2.668.188 5.336 4.887 10.859 9.399 16.004 14.285 3.046 2.63 3.812 6.012 2.667 9.582-.953 3.57-3.62 5.641-7.43 6.204-2.476.375-4.952-.375-6.859-1.88-7.242-6.577-14.48-13.156-22.101-19.921.191.375.191.187 0 0zM.125 22.008c0-4.695-.383-9.207.191-13.528C.886 3.406 5.84.398 11.367.96c4.57.567 8.383 5.263 8 9.774 0 .563-.379 1.13-.953 1.317-5.906 3.195-11.812 6.578-17.91 9.77.191.187 0 0-.379.187zm19.242 20.297c0 4.324.192 8.082 0 12.027-.379 4.7-4.762 8.27-9.336 8.27-4.57 0-8.953-3.383-9.715-7.891-.191-1.316 0-2.258 1.332-3.008 5.336-3.008 10.86-5.828 16.196-8.832.379 0 .761-.187 1.523-.566zm0 0" fill="#5171a5"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/kup.svg
1
1
<svg height="64" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M32.707 50.086c-4.18-4.148-9.402.348-13.93 4.324-5.742 5.184-9.746 8.125-13.402 4.668-2.613-2.594-2.438-5.707 0-8.992l2.438 2.25a1.847 1.847 0 0 0 2.261 0L37.406 27.27 26.09 16.03.848 43.344c-.524.691-.524 1.558 0 2.25l2.437 2.418c-4.527 5.36-2.96 10.547 0 13.484 5.918 5.879 12.535.172 17.758-4.496 4.7-4.148 7.66-6.395 9.574-4.492.695.52 1.738.52 2.262-.172.348-.691.348-1.559-.172-2.25zm-8.008-19.188c-.699.692-1.57.692-2.265 0-.696-.691-.696-1.554 0-2.246l2.265-2.246c.696-.691 1.567-.691 2.262 0 .695.692.695 1.555 0 2.246zm14.797-5.875c.348.344.695.516 1.043.516 2.262 0 4.7-.516 6.617-1.727L29.57 6.352c-1.218 2.074-1.738 4.324-1.738 6.57 0 .344.172.863.52 1.035zm10.27-21.261c-5.047-5.016-13.23-5.016-18.278 0L49.766 21.91a12.77 12.77 0 0 0 0-18.148zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/less.svg
1
1
<svg height="64" width="75" xmlns="http://www.w3.org/2000/svg"><path d="M.5 19v-4.1c.9-.1 1.6-.2 2-.4.4-.2.8-.6 1.2-1.001.4-.5.5-1.1.7-1.9.1-.6.2-1.499.2-2.799 0-2.201.1-3.7.4-4.6.2-.8.6-1.6 1.2-2 .5-.5 1.4-.9 2.5-1.2.7-.2 1.9-.4 3.5-.4h.9v3.9c-1.3 0-2.2.1-2.6.3-.4.2-.6.4-.9.6-.2.3-.3.7-.3 1.501 0 .8-.1 2-.2 4.099-.101 1.2-.2 2-.4 2.801-.301.6-.6 1.2-1 1.8-.4.4-1 .9-1.8 1.399.7.4 1.3.8 1.8 1.3s.8 1.2 1.1 1.899c.3.702.4 1.802.4 3.001.1 1.9.1 3.1.1 3.599 0 .702.1 1.202.3 1.602.2.4.5.5.9.6.4.2 1.2.3 2.6.3v4.098h-1c-1.6 0-2.9-.1-3.701-.4-.9-.3-1.6-.6-2.2-1.2-.6-.6-.999-1.2-1.2-1.999-.198-.8-.299-2.1-.299-4 0-2-.1-3.5-.3-4.1-.3-.9-.7-1.601-1.201-2-.698-.5-1.5-.7-2.7-.7zm39.1 0c-.9.1-1.6.2-2 .4s-.8.6-1.2 1.001c-.4.5-.5 1.1-.7 1.9-.099.6-.2 1.499-.2 2.799 0 2.201-.1 3.7-.4 4.6-.2.9-.6 1.6-1.2 2-.5.5-1.4.9-2.5 1.2-.7.2-1.9.4-3.5.4h-.999v-4.1c1.298 0 2.1-.1 2.599-.3s.7-.4.899-.6c.2-.3.301-.7.301-1.501 0-.6.1-2 .2-3.999.099-1.2.3-2.1.5-2.8.3-.7.6-1.3 1.1-1.9.4-.5 1-.9 1.7-1.3-.901-.6-1.6-1.1-2-1.6-.5-.7-1-1.801-1.201-2.8-.199-.8-.299-2.6-.299-5.2 0-.8-.1-1.4-.301-1.8-.199-.3-.4-.5-.799-.6-.2-.3-1-.3-2.5-.3v-4h.999c1.602 0 2.9.1 3.7.4.902.3 1.6.6 2.2 1.2.6.6 1.002 1.2 1.2 2 .201.8.402 2.1.402 4 0 2 .098 3.4.299 4.1.299.9.7 1.601 1.2 1.9.5.4 1.401.6 2.5.6.1.1 0 4.3 0 4.3zm0 0" fill="#7058c6" stroke="#7058c6" stroke-miterlimit="10" transform="matrix(1.86825 0 0 1.87558 0 .209)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/lex.svg
1
1
<svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#c93" stroke="#c93" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/licx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852-.512 1.7-.851 2.72-1.36.51-.171.85 0 1.19.169 3.06 2.379 6.118 4.757 9.348 7.136 2.547 1.872 5.098 3.91 7.645 5.778l.511-.508C31.367 18.453 40.543 9.449 49.891.44c.507-.507.847-.507 1.527-.34 3.91 1.532 7.816 3.231 11.727 4.758.34.172.507.512.68.852.167.168 0 .508 0 .68v51.316c0 1.188 0 1.188-1.192 1.7-3.738 1.527-7.477 2.886-11.215 4.417-.68.34-1.02.168-1.527-.34-9.348-8.496-18.524-17.504-27.868-26.34-.171-.34-.34-.507-.68-.847zm26.676 8.156V19.984L31.707 32.22zM13.867 32.22c-2.719-2.38-5.437-4.758-8.16-7.309v14.613c2.723-2.378 5.441-4.757 8.16-7.304zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/lisp.svg
1
1
<svg height="63" width="45" xmlns="http://www.w3.org/2000/svg"><path d="M22.686 26.4h.8c0 2.401-.4 4.2-1.2 5.3-.801 1.1-1.8 1.7-3 1.7-1.001 0-1.9-.4-2.8-1.1-.9-.699-1.701-2.7-2.4-5.9l-2-8.9-6.902 15.599h-4.4l9.901-21.2c-.5-2.698-1.2-4.799-1.899-6.098-.701-1.3-1.7-2.002-2.7-2.002-.902 0-1.601.301-2.3 1-.6.7-1 1.701-1.1 3.1h-.8c0-2.299.5-4.1 1.4-5.4.899-1.3 1.898-2 3.2-2 .8 0 1.599.302 2.3 1.002.7.699 1.4 1.799 1.9 3.499.599 1.7 1.4 5.1 2.6 10.3l1.599 7.3c.701 3 1.4 5 2.1 6.1.7 1 1.6 1.5 2.6 1.5 1.9-.1 2.901-1.3 3.101-3.8zm0 0" fill="#066" stroke="#066" stroke-miterlimit="10" transform="matrix(1.87615 0 0 1.85407 0 .073)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/lit.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/lnk.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M34.84 4.973 23.66 16.156a19.478 19.478 0 0 1 10.266 0c.55.184.914.364 1.281.364l5.684-5.68c3.3-3.3 8.8-3.3 12.097 0 3.301 3.297 3.301 8.797 0 12.098L41.074 34.852c-1.101 1.101-2.383 1.836-3.851 2.199-2.746.734-6.047 0-8.246-2.2-1.47-1.464-2.383-3.48-2.383-5.316-.735.367-1.285.735-1.836 1.102l-5.317 5.316c.735 1.832 2.02 3.484 3.485 4.95 2.199 2.199 4.765 3.667 7.699 4.398 4.398 1.101 9.164.55 13.016-1.832 1.28-.735 2.382-1.649 3.3-2.567L59.04 28.805c6.598-6.602 6.598-17.418 0-24.016a17.443 17.443 0 0 0-24.2.184zm0 0"/><path d="M40.156 47.867c-3.847 1.102-7.883.918-11.73-.367l-5.5 5.5c-3.301 3.3-8.801 3.3-12.098 0-3.3-3.297-3.3-8.797 0-12.098l12.098-12.097c1.101-1.102 2.383-1.836 3.851-2.2 2.746-.734 6.047 0 8.246 2.2 1.47 1.465 2.383 3.48 2.383 5.5.551-.368 1.285-.735 1.836-1.102l5.317-5.316c-.735-1.832-2.02-3.485-3.485-4.95-2.199-2.199-4.765-3.667-7.699-4.398-4.398-1.102-9.164-.55-13.016 1.832-1.28.734-2.382 1.649-3.3 2.567L4.96 35.035c-6.598 6.602-6.598 17.414 0 24.016 6.598 6.597 17.414 6.597 24.016 0zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/lock.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/log.svg
1
1
<svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/lua.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M27.91 64A27.846 27.846 0 0 1 0 36.09C0 20.62 12.445 8 28.445 8.18c15.11.355 27.2 12.441 27.2 27.91C55.645 51.555 43.199 64 27.91 64zm11.38-47.645c-4.446 0-8.356 3.91-8.356 8.356 0 4.445 3.554 8.355 8.355 8.355 4.445 0 8.356-3.554 8.356-8.355 0-4.621-3.555-8.356-8.356-8.356zm16.355 0c-4.446 0-8.356-3.554-8-8.355 0-4.445 3.554-8 8.355-8 4.445 0 8 3.91 8 8.355 0 4.446-3.91 8-8.355 8zm0 0" fill="navy"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/m.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/m2v.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/m3u.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M4.059 10.39h40.254c2.109 0 3.69-1.613 3.69-3.761 0-2.149-1.581-3.758-3.69-3.758H4.059c-2.11 0-3.692 1.61-3.692 3.758 0 2.152 1.582 3.762 3.692 3.762zm0 19.891h40.254c2.109 0 3.69-1.613 3.69-3.765 0-2.149-1.581-3.762-3.69-3.762H4.059c-2.11 0-3.692 1.613-3.692 3.762 0 2.148 1.582 3.765 3.692 3.765zm19.336 10.57H4.059c-2.11 0-3.692 1.614-3.692 3.762 0 2.149 1.582 3.766 3.692 3.766h19.336c2.109 0 3.69-1.617 3.69-3.766 0-2.148-1.581-3.761-3.69-3.761zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M70.68 9.496c-2.813-1.434-6.504-3.582-7.91-6.629C62.77 1.254 61.54 0 59.957 0c-1.582 0-2.812 1.254-2.812 2.867v38.52c-2.989-1.614-8.614-1.075-12.833 1.433-6.68 3.766-9.492 10.93-6.68 15.766 2.813 4.84 10.723 5.914 17.4 2.152 4.573-2.687 7.738-6.988 7.913-11.289V16.305c9.492 0 15.29 3.941 13.18 13.437-.352 1.793-1.05 3.403-1.754 5.195-.355.54-.355 1.254.176 1.793.527.536 1.402.356 2.11-.359 3.515-3.582 5.796-8.242 5.976-13.437-.18-6.805-6.508-10.75-11.953-13.438zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/m3u8.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M4.059 10.39h40.254c2.109 0 3.69-1.613 3.69-3.761 0-2.149-1.581-3.758-3.69-3.758H4.059c-2.11 0-3.692 1.61-3.692 3.758 0 2.152 1.582 3.762 3.692 3.762zm0 19.891h40.254c2.109 0 3.69-1.613 3.69-3.765 0-2.149-1.581-3.762-3.69-3.762H4.059c-2.11 0-3.692 1.613-3.692 3.762 0 2.148 1.582 3.765 3.692 3.765zm19.336 10.57H4.059c-2.11 0-3.692 1.614-3.692 3.762 0 2.149 1.582 3.766 3.692 3.766h19.336c2.109 0 3.69-1.617 3.69-3.766 0-2.148-1.581-3.761-3.69-3.761zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M70.68 9.496c-2.813-1.434-6.504-3.582-7.91-6.629C62.77 1.254 61.54 0 59.957 0c-1.582 0-2.812 1.254-2.812 2.867v38.52c-2.989-1.614-8.614-1.075-12.833 1.433-6.68 3.766-9.492 10.93-6.68 15.766 2.813 4.84 10.723 5.914 17.4 2.152 4.573-2.687 7.738-6.988 7.913-11.289V16.305c9.492 0 15.29 3.941 13.18 13.437-.352 1.793-1.05 3.403-1.754 5.195-.355.54-.355 1.254.176 1.793.527.536 1.402.356 2.11-.359 3.515-3.582 5.796-8.242 5.976-13.437-.18-6.805-6.508-10.75-11.953-13.438zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/m4.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="m60.137 40.719-.184-.36-.36-.359c0-5.395-3.234-10.066-7.73-12.406-.539-1.977-.18-2.696-.18-2.696.18-.18.18-.539.364-.718h4.672c1.078 0 2.156-.36 2.875-1.258 2.52-2.516 3.777-5.754 3.777-9.348 0-6.652-4.676-12.047-11.144-12.945-.364 0-.723-.18-1.082-.18h-37.57C6.382.45.448 6.383.448 13.574c0 .54 0 1.078.18 1.797.36 12.223 9.527 22.113 14.383 26.606H5.664c-1.437 0-2.879.718-3.418 2.16C1.168 46.113.45 48.27.45 50.426c0 7.191 5.934 13.125 13.125 13.125h37.93c6.832-.719 12.047-6.473 12.047-13.125-.18-3.414-1.438-7.192-3.414-9.707M51.145 4.586c4.675.539 8.449 4.312 8.449 9.348 0 2.695-1.078 4.851-2.875 6.652H22.563c1.437-1.98 2.335-4.137 2.335-6.652 0-3.778-1.976-7.192-4.671-9.348zM4.227 50.426c0-1.617.539-3.235 1.257-4.313h15.82c.72 1.438 1.259 2.875 1.259 4.313 0 5.035-4.137 9.168-9.348 9.168-5.215 0-8.988-4.313-8.988-9.168m46.918 9.168H19.863c3.059-2.156 4.856-5.39 4.856-9.348 0-3.773-1.977-7.191-4.672-9.348h.18S4.766 29.395 4.586 14.832c0-.539-.18-.898-.18-1.437 0-5.036 4.133-9.348 9.348-9.348 5.21 0 9.348 4.133 9.348 9.348v.539c0 .898-.18 1.796-.54 2.515-.359 1.078-.898 1.977-1.617 2.875l-2.34 3.239H48.63c0 .18-.18.359-.36.539-.539 1.078-.718 2.156-.718 3.234-.54 0-1.258-.18-1.977-.18-7.55 0-13.844 6.114-13.844 13.844s6.114 13.844 13.844 13.844c5.57 0 10.426-3.239 12.582-8.27.719 1.617 1.258 3.235 1.258 4.852.18 4.676-3.594 8.808-8.27 9.168M51.685 40l-9.168 6.832V32.988zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".898875"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/m4a.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/m4r.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#039"><path d="M.324 12.07C-.777 14.828 1.43 23.102 1.43 23.102c4.226 11.77 14.156 21.882 14.156 21.882 9.746 10.114 19.492 16.364 28.133 18.387 8.64 2.02 10.297-1.473 10.297-1.473s7.168-6.988 9.191-9.375c2.023-2.574-.55-4.046-.55-4.046s-12.505-7.54-14.524-8.274c-2.024-.918-3.13.55-4.414 1.656-1.29 1.102-3.864 3.493-3.864 3.493-1.468.183-4.226-.918-8.64-4.414C26.8 37.444 21.469 30.823 20 28.434c-1.473-2.204-1.473-4.594-1.473-4.594s1.84-1.473 3.68-3.496c1.836-2.02 1.285-3.86 1.285-3.86l-5.699-10.48C14.301-1.352 13.379.12 13.379.12c-2.39.918-4.41 2.758-5.7 4.043-.917.922-5.882 5.149-7.355 7.906zM49.97 27.7c1.472 0 2.758-1.102 2.758-2.759 0-8.09-6.618-15.078-15.075-15.078-1.472 0-2.761 1.106-2.761 2.758 0 1.473 1.105 2.762 2.761 2.762 5.145 0 9.375 4.226 9.375 9.375 0 1.656 1.473 2.941 2.942 2.941zm0 0"/><path d="M38.938 1.223c-1.473 0-2.758 1.105-2.758 2.757 0 1.473 1.101 2.758 2.757 2.758a16.87 16.87 0 0 1 16.915 16.918c0 1.469 1.105 2.758 2.757 2.758 1.657 0 2.762-1.105 2.762-2.758 0-12.32-10.113-22.433-22.434-22.433zm-3.676 16.363c-1.473 0-2.758 1.105-2.758 2.758 0 1.656 1.101 2.758 2.758 2.758 2.39 0 4.41 2.023 4.41 4.414 0 1.472 1.105 2.757 2.758 2.757 1.472 0 2.757-1.101 2.757-2.757-.183-5.516-4.593-9.93-9.925-9.93zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/m4v.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/map.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M56.797 45.254c-.356-.531-.711-.707-1.242-.707H38.91c-.531 1.238-1.238 2.297-1.77 3.535-1.417 2.828-3.011 5.832-4.425 8.305v.18c-.887 1.413-2.305 2.472-4.074 2.472s-3.188-.883-4.07-2.473c-.532-.886-2.305-4.242-4.43-8.484-.707-1.238-1.239-2.473-1.77-3.71H9.34c-.531 0-1.063.35-1.414.882L.133 61.69c-.176.528-.176 1.059 0 1.414.355.528.707.708 1.238.708h46.215c.531 0 1.062-.356 1.418-.887l7.793-16.258c.351-.352.176-1.059 0-1.414zm0 0"/><path d="M28.465.188c-9.387 0-17.176 7.601-17.176 17.144 0 5.656 6.195 19.086 11.332 29.512 2.48 4.773 4.426 8.308 4.426 8.484.355.531.71.883 1.418.883.707 0 1.062-.352 1.414-.883 0 0 1.95-3.535 4.43-8.484 5.132-10.25 11.332-23.68 11.332-29.512C45.64 7.789 37.848.187 28.465.187zm0 27.57c-4.25 0-7.969-3.356-8.324-7.598v-.883c0-4.597 3.718-8.308 8.324-8.308 4.25 0 7.969 3.36 8.32 7.422v.886c0 4.594-3.719 8.48-8.32 8.48zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mc.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#999"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/md.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="39"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M0 38.824V.176h11.2l11.198 14.183L33.602.176H44.8v38.648H33.6v-22.16l-11.203 14.18-11.195-14.18v22.16zm67.2 0L50.397 20.031h11.204V.176h11.195V20.03H84zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mdb.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M66.824 40.465c-.176 1.59-2.097 2.824-5.945 3.707-3.852.879-8.75 1.41-14.52 1.41h-4.898v9.172c1.574.176 3.324.176 4.898.176 5.77 0 10.668-.528 14.52-1.586 3.848-1.059 5.945-2.293 6.121-3.707-.176-.352-.176-9.172-.176-9.172zm-20.64-6.7c-1.75 0-3.325 0-4.899-.18v9.352h4.899c5.773 0 10.671-.53 14.52-1.59s5.944-2.292 5.944-3.702v-8.997c-.171 1.586-2.097 2.825-6.12 3.704-3.673 1.058-8.571 1.59-14.344 1.414zm0-11.468c-1.75 0-3.325 0-4.899-.176v9.352c1.574.175 3.324.175 4.899.175 5.773 0 10.671-.53 14.695-1.59C64.727 29 66.824 27.767 67 26.356V17.36c-.176 1.59-2.098 2.825-6.121 3.704-4.024.707-8.922 1.234-14.695 1.234zm0-13.05c-1.75 0-3.325 0-4.899.175v10.406c1.574.176 3.324.176 4.899.176 5.773 0 10.671-.527 14.695-1.586 3.848-1.059 5.945-2.293 6.121-3.703-.176-1.59-2.098-2.824-6.121-3.883-4.024-1.055-8.922-1.41-14.695-1.586zM18.02 23.886c-.176.527-.528 2.293-1.227 5.293l-1.223 5.113h5.07l-1.222-5.113c-.7-3-1.227-4.766-1.227-5.293zM0 7.129v49.918l37.785 6.527V.426zm23.09 37.219-1.399-5.645-6.996-.176-1.398 5.29-4.375-.352 6.648-23.813 5.07-.351 7.348 25.222zm0 0" fill="#a03537"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mdf.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/me.svg
1
1
<svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mi.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#999"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mid.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/midi.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mk.svg
1
1
<svg height="64" width="79" xmlns="http://www.w3.org/2000/svg"><path d="m42.852 35.445 4.695 1.344 2.851-10.418-4.695-1.344c0-1.68-.168-3.359-.672-5.039l4.192-2.52-5.364-9.405-4.359 2.519a18.036 18.036 0 0 0-4.023-3.023l1.34-4.704L26.421 0l-1.34 4.703c-1.676 0-3.352.168-5.027.672l-2.516-4.2-9.387 5.376 2.512 4.199a18.053 18.053 0 0 0-3.016 4.031l-4.695-1.343L.105 23.852l4.692 1.343c0 1.68.168 3.36.672 5.04l-4.192 2.523 5.364 9.406 4.191-2.52a18.126 18.126 0 0 0 4.023 3.024l-1.34 4.703 10.395 2.856 1.34-4.704c1.676 0 3.352-.168 5.031-.671l2.512 4.199 9.39-5.375-2.515-4.2c1.172-1.175 2.348-2.519 3.184-4.03zm-25.985-5.547c-2.68-4.535-1.004-10.414 3.52-13.101 4.527-2.688 10.394-1.008 13.078 3.527 2.683 4.535 1.004 10.414-3.52 13.106-4.527 2.687-10.394 1.175-13.078-3.532zm50.63 33.262 6.034-3.527-1.676-2.856c.84-.84 1.508-1.68 2.012-2.687l3.184.84 1.844-6.887-3.184-.84c0-1.176-.168-2.183-.504-3.36l2.852-1.679-3.52-6.047-2.852 1.68c-.84-.84-1.675-1.512-2.683-2.016l.84-3.191-6.875-1.852-.836 3.196c-1.176 0-2.18.168-3.356.504l-1.675-2.86-5.7 3.7 1.676 2.855c-.836.84-1.508 1.68-2.012 2.687l-3.183-1.007-1.844 6.886 3.184.84c0 1.176.168 2.184.504 3.36l-2.852 1.68 3.523 6.046 2.848-1.68c.84.84 1.676 1.512 2.684 2.016l-.84 3.191L61.965 64l.836-3.191c1.176 0 2.18-.168 3.355-.504-.336 0 1.34 2.855 1.34 2.855zM57.101 50.563c-1.676-3.024-.668-6.887 2.347-8.567 3.02-1.68 6.875-.672 8.551 2.352 1.676 3.023.672 6.886-2.348 8.566-3.015 1.68-6.875.672-8.55-2.352zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mkv.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mm.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mng.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.14 63.773H1.86a1.627 1.627 0 0 1-1.633-1.632V1.859C.227.953.953.227 1.859.227h60.282c.906 0 1.632.726 1.632 1.632v60.282c0 .906-.726 1.632-1.632 1.632zM3.314 59.777c0 .547.363.727.726.727h55.559c.543 0 .726-.363.726-.727V45.434c0-.543-.363-.723-.726-.723H4.223c-.547 0-.727.363-.727.723v14.343zm56.464-56.28H4.223c-.547 0-.727.362-.727.726v36.492c0 .18 0 .363.18.363l11.8-14.707 11.985 7.082 13.434-15.976 19.793 15.43V4.222c0-.547-.364-.727-.91-.727zm-48.476 44.3c2.543 0 4.722 2.176 4.722 4.719s-2.18 4.722-4.722 4.722c-2.54 0-4.719-2-4.719-4.539 0-2.543 2.18-4.902 4.719-4.902zm8.715 3.266h36.496c.543 0 .726.363.726.726v1.637c0 .543-.363.722-.726.722H20.016c-.543 0-.727-.359-.727-.722v-1.637c0-.363.363-.726.727-.726zm0 0" fill="#3c3" stroke="#3c3" stroke-miterlimit="10" stroke-width=".4539"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mo.svg
1
1
<svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#a87c2d" stroke="#a87c2d" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mobi.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><path d="M69.723 24.898c-.336-.851-1.012-1.535-1.688-2.222-.168.687-.336 1.37-.844 2.054L46.098 57.723c-.844 1.199-2.532 1.539-3.88 1.199l-33.75-9.574c-2.023-.512-4.386-1.711-4.554-4.106 0-.851 0-1.195.504-1.535.508-.344 1.016-.344 1.52-.172l31.726 8.89c4.555 1.368 5.902.34 9.277-4.788l19.239-30.09a5.83 5.83 0 0 0 .675-4.957c-.507-1.54-1.855-2.735-3.543-3.246L35.47 1.48c-.676-.171-1.352-.171-2.024-.171v-.172c-4.218-2.563-5.906 2.222-8.101 4.101-.844.684-1.856 1.2-2.196 1.883-.336.684-.168 1.367-.336 1.879-.843 1.883-3.207 4.957-4.386 5.813-.676.515-1.688.683-2.196 1.539-.335.511-.335 1.539-.503 2.222-.676 1.711-2.872 4.617-4.387 5.985-.508.511-1.352.855-1.688 1.539-.34.511-.172 1.539-.675 2.05-1.012 1.711-3.04 4.446-4.559 5.985-.844.855-1.856 1.195-2.191 2.05-.168.34 0 1.028-.168 1.54-.34.855-.676 1.539-.844 2.222C.37 41.141-.137 42.852.03 44.56c.34 4.105 3.375 8.207 7.09 9.234l33.746 9.574c3.207.852 7.09-.683 8.778-3.422l19.402-30.258c1.016-1.367 1.183-3.25.676-4.789zm-38.98-10.941 1.35-2.05c.337-.512 1.18-.856 1.856-.684l22.274 6.324c.675.172.843.855.507 1.371l-1.351 2.05c-.336.512-1.18.856-1.856.684L31.25 15.328c-.676-.172-.844-.687-.508-1.371zm-5.567 8.55 1.347-2.054c.34-.512 1.184-.851 1.86-.683l22.273 6.328c.676.172.844.855.504 1.367l-1.347 2.05c-.34.512-1.184.856-1.856.684L25.68 23.875c-.672-.172-1.012-.855-.504-1.367zm0 0" fill="#963"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mod.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mov.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mp2.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mp3.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mp4.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mpa.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mpd.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M0 7.336 37.754.008v63.984L0 57.02zm0 0"/><path d="M64.164 57.734H32.082c-1.594 0-2.836-1.25-2.836-2.859V9.305c0-1.61 1.242-2.864 2.836-2.864h32.082C65.758 6.441 67 7.695 67 9.305v45.57c0 1.61-1.242 2.86-2.836 2.86zM32.082 9.125c-.176 0-.355.18-.355.355v45.575c0 .18.18.36.355.36h32.082c.176 0 .356-.18.356-.36V9.305c0-.18-.18-.36-.356-.36 0 .18-32.082.18-32.082.18zm0 0"/><path d="M59.555 34.324H55.3V19.313H35.629v-4.29h23.926zm0 0"/><path d="m57.25 38.078-7.621-8.402h15.066zM37.719 42.578l8.144-8.215 8.149 8.215-8.149 8.215zm0 0"/></g><path d="M23.574 22.348c-.71-.715-1.418-1.07-2.48-1.43-.887-.355-2.13-.176-3.192-.176-2.129 0-5.847.356-5.847.356l-.18 20.73 3.898.36v-7.329s2.305.36 4.254-.18c1.067-.355 2.13-.89 2.66-1.429.711-.715 1.243-1.43 1.594-2.145.535-1.07.711-2.144.711-3.753.356-1.965-.176-3.75-1.418-5.004zm-3.012 7.148c-.71 1.61-2.66 1.61-2.66 1.61h-2.129v-6.614s1.418-.176 2.485 0c.531.18 1.062.36 1.418.54 1.062.89 1.594 3.034.886 4.464zm0 0" fill="#fff"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mpe.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mpeg.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mpg.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mpga.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mpp.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><g fill="#2d7136"><path d="M0 7.336 37.754.008v63.984L0 57.02zm0 0"/><path d="M64.164 57.734H32.082c-1.594 0-2.836-1.25-2.836-2.859V9.305c0-1.61 1.242-2.864 2.836-2.864h32.082C65.758 6.441 67 7.695 67 9.305v45.57c0 1.61-1.242 2.86-2.836 2.86zM32.082 9.125c-.176 0-.355.18-.355.355v45.575c0 .18.18.36.355.36h32.082c.176 0 .356-.18.356-.36V9.305c0-.18-.18-.36-.356-.36 0 .18-32.082.18-32.082.18zm0 0"/><path d="M59.555 34.324H55.3V19.313H35.629v-4.29h23.926zm0 0"/><path d="m57.25 38.078-7.621-8.402h15.066zM37.719 42.578l8.144-8.215 8.149 8.215-8.149 8.215zm0 0"/></g><path d="M23.574 22.348c-.71-.715-1.418-1.07-2.48-1.43-.887-.355-2.13-.176-3.192-.176-2.129 0-5.847.356-5.847.356l-.18 20.73 3.898.36v-7.329s2.305.36 4.254-.18c1.067-.355 2.13-.89 2.66-1.429.711-.715 1.243-1.43 1.594-2.145.535-1.07.711-2.144.711-3.753.356-1.965-.176-3.75-1.418-5.004zm-3.012 7.148c-.71 1.61-2.66 1.61-2.66 1.61h-2.129v-6.614s1.418-.176 2.485 0c.531.18 1.062.36 1.418.54 1.062.89 1.594 3.034.886 4.464zm0 0" fill="#fff"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/mpt.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><g fill="#2d7136"><path d="M0 7.336 37.754.008v63.984L0 57.02zm0 0"/><path d="M64.164 57.734H32.082c-1.594 0-2.836-1.25-2.836-2.859V9.305c0-1.61 1.242-2.864 2.836-2.864h32.082C65.758 6.441 67 7.695 67 9.305v45.57c0 1.61-1.242 2.86-2.836 2.86zM32.082 9.125c-.176 0-.355.18-.355.355v45.575c0 .18.18.36.355.36h32.082c.176 0 .356-.18.356-.36V9.305c0-.18-.18-.36-.356-.36 0 .18-32.082.18-32.082.18zm0 0"/><path d="M59.555 34.324H55.3V19.313H35.629v-4.29h23.926zm0 0"/><path d="m57.25 38.078-7.621-8.402h15.066zM37.719 42.578l8.144-8.215 8.149 8.215-8.149 8.215zm0 0"/></g><path d="M23.574 22.348c-.71-.715-1.418-1.07-2.48-1.43-.887-.355-2.13-.176-3.192-.176-2.129 0-5.847.356-5.847.356l-.18 20.73 3.898.36v-7.329s2.305.36 4.254-.18c1.067-.355 2.13-.89 2.66-1.429.711-.715 1.243-1.43 1.594-2.145.535-1.07.711-2.144.711-3.753.356-1.965-.176-3.75-1.418-5.004zm-3.012 7.148c-.71 1.61-2.66 1.61-2.66 1.61h-2.129v-6.614s1.418-.176 2.485 0c.531.18 1.062.36 1.418.54 1.062.89 1.594 3.034.886 4.464zm0 0" fill="#fff"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/msg.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="fill-rule:nonzero;fill:#1d6fb5;fill-opacity:1;stroke-width:.75;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1d6fb5;stroke-opacity:1;stroke-miterlimit:10" d="M6.274 25.574h28.3l-9.698-9.3-4.501 3.802-4.5-3.802zm34.1-25.2v28.002H.376V.374zM26.976 14.576l10.7 10.298v-19.3zm-24.2 10.298 10.7-10.298-10.7-9.002zm1.4-21.7 15.9 13.4 15.9-13.4zm0 0" transform="matrix(2.06135 0 0 2.08166 0 .076)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/msi.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#4d1b9b"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/msu.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#55486d"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/nef.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/nes.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#ff0021"><path d="M49.453.188h-14.45V64h14.45c7.883 0 14.453-6.383 14.453-14.453V14.453C63.906 6.57 57.523.188 49.453.188zm0 41.289c-4.129 0-7.508-3.375-7.508-7.508 0-4.13 3.38-7.504 7.508-7.504s7.508 3.375 7.508 7.504c0 4.133-3.379 7.508-7.508 7.508zM31.437 64h-16.89C6.664 64 .094 57.617.094 49.547V14.453C.094 6.383 6.477 0 14.547 0h16.89zM14.547 3.941c-5.82 0-10.7 4.692-10.7 10.7v35.093c0 5.82 4.692 10.7 10.7 10.7H27.87V3.94zm0 0"/><path d="M15.672 26.465c-4.129 0-7.508-3.38-7.508-7.508 0-4.129 3.379-7.508 7.508-7.508s7.508 3.38 7.508 7.508c0 4.129-3.38 7.508-7.508 7.508zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/nfo.svg
1
1
<svg height="64" width="66" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa" stroke-miterlimit="10"><path d="M58 47.3v4.2H14.4v-4.2m43.2 10.1V61.6H14V57.4M57.6 68v4.1H14V68M57.6 78.5v4.2H14v-4.2" stroke="#bababa" transform="matrix(1.46667 0 0 1.48837 -19.8 -64.744)"/><path d="M29.8 60.9v-1.8c.5-.2 1.2-.399 2.2-.7.9-.3 1.8-.4 2.799-.599 1.001-.202 2-.302 2.9-.402.9-.1 1.8-.2 2.602-.2l.898.602-4.8 22.8h3.7v1.9c-.4.298-.999.6-1.598.9-.602.299-1.3.498-2 .8-.7.3-1.401.399-2.102.499-.7.1-1.398.199-2 .199-1.398 0-2.2-.3-2.799-.798-.4-.501-.6-1.102-.6-1.7 0-.701.1-1.402.2-2.1.099-.7.299-1.402.4-2.202L33.2 61.7zm4.5-11.999c0-1.202.4-2.202 1.2-2.8.801-.7 1.8-1 3.1-1 1.4 0 2.4.3 3.2 1 .8.698 1.2 1.598 1.2 2.8 0 1.1-.4 2.1-1.2 2.698-.8.701-1.9 1-3.2 1-1.2 0-2.2-.299-3.1-1-.701-.598-1.2-1.498-1.2-2.698zm0 0" stroke="#fff" stroke-width="3" transform="matrix(1.46667 0 0 1.48837 -19.8 -64.744)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/nix.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#666;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/npmignore.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ocx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M60.887 19.578c2.941-7.27 3.285-13.152-.348-16.789C56.56-1.19 46.867.02 36.656 4.867h-1.21c-7.27 0-14.192 2.598-19.383 7.27-4.325 3.98-7.614 9.172-9 15.054 1.039-1.21 6.578-7.789 12.98-11.421.172 0 1.73-1.04 1.73-1.04-.171 0-3.289 2.942-3.808 3.637C3.949 32.73-4.184 54.535 2.219 60.937c4.152 4.153 11.765 3.29 20.593-1.558 3.81 1.73 7.961 2.598 12.633 2.598 6.059 0 11.594-1.559 16.442-4.848 5.02-3.285 8.652-8.133 10.73-14.016H47.04c-2.074 3.809-6.574 6.403-11.422 6.403-6.746 0-12.457-5.54-12.633-11.942v-.52h40.844v-.863c0-1.039.172-2.25.172-2.941 0-4.848-1.04-9.52-3.113-13.672zM6.719 59.555c-3.29-3.29-2.25-9.348 1.554-16.79 1.735 5.02 4.848 9.348 8.657 12.633 1.21 1.04 2.593 2.079 3.98 2.77-6.406 3.46-11.598 3.98-14.191 1.387zm41.015-30.461H23.332v-.172c.344-6.23 6.23-11.594 12.98-11.594 6.403 0 11.594 5.02 11.938 11.594v.172zM59.848 18.02c-1.211-2.079-2.77-3.98-4.672-5.54a29.6 29.6 0 0 0-9.692-6.054c6.403-2.942 11.766-3.461 14.536-.52 2.421 2.422 2.25 6.75-.172 12.114 0 .171 0 .171 0 0zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/odb.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><g fill="#a03537"><path d="M38.684 63.957c-8.098-.34-15.356-2.875-19.239-6.77-1.183-1.011-2.363-2.535-2.87-3.55l-.337-.676v-7.617c0-7.614 0-7.614.168-6.934.34 1.692 1.352 3.383 2.871 4.735 1.012.847 3.035 2.37 4.723 3.214 2.871 1.524 6.586 2.54 10.465 3.047 2.363.34 3.207.34 6.582.34s4.223 0 6.582-.34c3.883-.508 7.43-1.691 10.465-3.047 1.687-.843 3.715-2.199 4.726-3.214 1.352-1.352 2.532-3.043 2.871-4.735.168-.508.168-.508.168 6.934v7.445l-.34.68c-1.18 2.367-3.207 4.398-5.906 6.09-5.23 3.046-13.164 4.738-20.93 4.398zm0-18.95c-7.086-.339-13.668-2.37-17.887-5.413-1.016-.68-2.363-2.032-2.871-2.707a10.877 10.877 0 0 1-1.352-2.371l-.336-.676v-7.445c0-7.446 0-7.446.168-6.938.34 1.184.844 2.54 1.856 3.555.508.675 1.351 1.523 1.86 1.86.167.171.675.339 1.01.679 3.376 2.367 8.102 4.058 13.5 4.906 2.364.336 3.208.336 6.587.336 3.375 0 4.218 0 6.582-.336 3.879-.508 7.426-1.691 10.46-3.047 1.692-.847 3.716-2.2 4.727-3.215 1.352-1.351 2.364-3.047 2.871-4.738.168-.508.168-.508.168 6.938v7.445l-.507 1.015c-.844 1.524-1.348 2.368-2.364 3.383-1.011 1.016-2.023 1.864-3.375 2.54-5.398 3.046-13.332 4.738-21.097 4.23zm-.504-18.78c-4.727-.34-8.438-1.184-11.985-2.54-4.218-1.69-7.257-3.89-8.777-6.597a5.733 5.733 0 0 1-.844-2.031c-.168-.676-.336-2.368-.168-3.383C17.418 6.262 24.676 1.859 34.465.34 36.828 0 37.672 0 41.047 0s4.223 0 6.582.34c3.883.508 7.43 1.691 10.465 3.043 4.39 2.199 7.09 5.078 7.597 8.12.168.849.168 2.708-.171 3.388-.504 1.691-1.18 2.707-2.532 4.058-3.543 3.723-9.789 6.094-17.55 6.938-1.016.34-6.247.34-7.258.34zm0 0"/><path d="M38.5 55.7h1.7c2.501.2 4.5.8 6.5 1.6 3.699-1.7 9.1-.399 12.399.9-4.298-.4-9.3 0-12.2 1.7-2.9-2.4-8.498-2.999-13.699-2.4 1.4-1 3.1-1.5 5.3-1.8zm-1.3 6.601c-3 .199-5.5 1.198-7.2 2.6-5-2.302-13.7-1.3-17 1.798-.299.201-.6.402-.5.702 2.8-.9 6.3-1.6 9.901-1.302 3.5.3 6.198 1.5 8.2 3.1 3.6-3.299 8.999-5.1 15.898-5-2.4-1.4-5.8-2.3-9.3-1.898zm0 0" stroke="#fff" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.68776 0 0 1.692 -20.28 -79.405)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ods.svg
1
1
<svg height="64" width="83" xmlns="http://www.w3.org/2000/svg"><path d="M62.797 9.066H82.93v13.512H62.797zm0 20.801H82.93V43.38H62.797zm0 20.621H82.93V64H62.797zm-27.371 0h20.308V64H35.426zm-27.196 0h20.13V64H8.23zM43.371 0h2.824c4.239.355 7.594 1.422 10.95 2.668 6.359-2.848 15.187-.711 20.84 1.422-7.063-.711-15.54 0-20.485 2.844-4.945-4.09-14.305-5.157-22.957-4.09A22.506 22.506 0 0 1 43.371 0zM41.43 10.844c-5.121.355-9.36 1.957-12.008 4.265C20.945 11.2 6.465 12.977.988 18.133c-.531.355-1.058.71-.883 1.246 4.77-1.422 10.598-2.668 16.602-2.133 6.004.531 10.418 2.488 13.773 5.152 6.18-5.507 15.188-8.71 26.665-8.53-4.06-1.778-9.887-3.38-15.715-3.024zm-5.828 19.199h20.132v13.512H35.602zm-27.196 0H28.54v13.512H8.406zm0 0" fill="#1f7244"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/odt.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M43.399 59.6h15.802v2.498H43.399zm-30.9 6.902h46.702V69.3H12.499zm0 6.799h46.702v2.798H12.499zm0 0" transform="matrix(1.7529 0 0 1.7867 -21.473 -85.752)"/><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="M46.363.363h2.985c4.554.36 8.058 1.434 11.566 2.864C67.574.187 77.043 2.512 83 4.832c-7.54-.715-16.477 0-21.738 3.04-5.258-4.286-15.075-5.54-24.364-4.286 2.63-1.79 5.786-2.863 9.465-3.223zM44.262 11.98c-5.434.36-9.82 2.145-12.797 4.649-8.942-4.113-24.367-2.324-30.149 3.21-.527.36-1.054.72-.879 1.25 5.086-1.605 11.22-2.855 17.528-2.32 6.312.536 11.047 2.68 14.55 5.54 6.485-5.899 16.13-9.29 28.223-9.114-4.207-1.965-10.343-3.57-16.476-3.215zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M12.5 80h46.7v2.8H12.5zm0 0" transform="matrix(1.7529 0 0 1.7867 -21.473 -85.752)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ogg.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ogv.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ost.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><g fill="#1d6fb5"><path d="M18.754 25.742c-2.668.36-4.8 3.219-4.8 6.258s2.132 6.078 4.8 6.258c2.668.355 4.805-2.504 4.805-6.258s-2.137-6.613-4.805-6.258zm0 0"/><path d="M.074 7.508v49.52L38.504 64V0zm18.68 34.683c-4.27-.539-7.649-5.187-7.649-10.191 0-5.184 3.38-9.652 7.649-10.191 4.27-.536 7.652 4.113 7.652 10.191 0 6.258-3.383 10.727-7.652 10.191zm50.172-27.175L47.754 32.715l-5.691-4.649v-14.66h26.863zm0 0"/><path d="m68.926 18.414-21.172 17.7-5.691-4.65V51.31h26.863zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/otf.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ott.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M43.399 59.6h15.802v2.498H43.399zm-30.9 6.902h46.702V69.3H12.499zm0 6.799h46.702v2.798H12.499zm0 0" transform="matrix(1.7529 0 0 1.7867 -21.473 -85.752)"/><path style="stroke:none;fill-rule:nonzero;fill:#1a75ce;fill-opacity:1" d="M46.363.363h2.985c4.554.36 8.058 1.434 11.566 2.864C67.574.187 77.043 2.512 83 4.832c-7.54-.715-16.477 0-21.738 3.04-5.258-4.286-15.075-5.54-24.364-4.286 2.63-1.79 5.786-2.863 9.465-3.223zM44.262 11.98c-5.434.36-9.82 2.145-12.797 4.649-8.942-4.113-24.367-2.324-30.149 3.21-.527.36-1.054.72-.879 1.25 5.086-1.605 11.22-2.855 17.528-2.32 6.312.536 11.047 2.68 14.55 5.54 6.485-5.899 16.13-9.29 28.223-9.114-4.207-1.965-10.343-3.57-16.476-3.215zm0 0"/><path style="fill-rule:nonzero;fill:#1a75ce;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1a75ce;stroke-opacity:1;stroke-miterlimit:10" d="M12.5 80h46.7v2.8H12.5zm0 0" transform="matrix(1.7529 0 0 1.7867 -21.473 -85.752)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ova.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#4d1b9b"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ovf.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#4d1b9b"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/p12.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/p7b.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/part.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#bababa;fill-opacity:1" d="M78.953 17.746c-.969-.191-1.742-.387-2.707-.191-2.125.191-4.441 1.36-6.566 1.746-1.543.199-2.895-.973-3.09-2.719a102.076 102.076 0 0 1 0-15.379c0-.777-.578-1.168-1.156-1.168C59.64-.156 54.039.425 48.05 1.79c-1.547.387-2.7 1.754-2.7 3.113.188 3.118 2.313 6.618 1.348 9.926-.773 2.918-3.09 5.059-6.18 5.645-2.894.586-5.988-.778-7.53-3.114-1.93-2.726-.966-6.62-1.739-9.925-.383-1.559-1.93-2.34-3.473-2.145a62.499 62.499 0 0 0-16.804 4.09c-.77.195-1.157.973-.77 1.555 2.125 4.671 3.664 9.539 5.02 14.597.386 1.559-.578 3.117-2.125 3.504-2.125.586-4.637.195-6.758.777-.969.196-1.738.586-2.508 1.168-2.707 1.754-3.867 4.672-3.48 7.59.39 2.727 2.507 5.063 5.02 6.23 3.284 1.165 6.183-.972 9.464-1.167 1.543-.192 2.898.972 3.09 2.726.387 5.059.387 10.313 0 15.371 0 .782.578 1.168 1.16 1.168 5.789.391 11.586-.386 17.379-1.75 1.543-.39 2.703-1.75 2.703-3.113-.195-3.308-2.508-6.617-1.543-10.12.77-2.727 3.473-5.06 6.18-5.454 2.699-.387 5.988.781 7.53 3.117 2.122 2.727.966 6.813 1.74 10.121.382 1.555 1.929 2.336 3.472 2.14 5.797-.585 11.59-1.753 16.805-4.089.77-.195 1.156-.973.77-1.555-2.126-4.672-3.669-9.535-5.02-14.597-.387-1.559.578-3.114 2.125-3.5 3.09-.782 6.566.195 9.46-1.95 2.126-1.75 3.477-4.671 2.895-7.394.195-3.695-2.121-6.227-4.629-7.008zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pdb.svg
1
1
<svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#a03537"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pdf.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M27.762.637c-3.899 0-6.95 3.218-6.95 6.949 0 4.746 2.711 10.68 5.422 16.277-2.203 6.782-4.574 14.07-7.797 20.172-6.44 2.547-12.207 4.41-15.597 7.293l-.168.168C1.484 52.852.637 54.546.637 56.414c0 3.898 3.218 6.95 6.949 6.95 1.867 0 3.73-.677 4.918-2.036 0 0 .168 0 .168-.168 2.543-3.05 5.594-8.644 8.308-13.562 6.102-2.375 12.715-4.918 18.817-6.442 4.578 3.73 11.191 6.102 16.617 6.102 3.898 0 6.95-3.223 6.95-6.95 0-3.902-3.22-6.953-6.95-6.953-4.41 0-10.68 1.528-15.43 3.223a56.197 56.197 0 0 1-10.172-13.223c1.868-5.765 4.07-11.359 4.07-15.77-.171-3.898-3.222-6.948-7.12-6.948zm0 4.066c1.527 0 2.71 1.188 2.71 2.715 0 2.035-1.183 5.934-2.37 10-1.696-4.066-3.223-7.965-3.223-10 .172-1.356 1.355-2.715 2.883-2.715zm1.187 23.906c2.035 3.391 4.578 6.442 7.29 9.157a171.201 171.201 0 0 0-12.208 4.066c2.035-4.238 3.563-8.816 4.918-13.223zm27.465 8.985a2.679 2.679 0 0 1 2.711 2.715 2.678 2.678 0 0 1-2.71 2.71c-3.224 0-7.63-1.355-11.192-3.39 4.07-1.016 8.648-2.035 11.191-2.035zM14.875 49.973c-2.031 3.558-3.898 6.78-5.254 8.476-.508.508-1.016.676-1.863.676a2.679 2.679 0 0 1-2.715-2.71c0-.68.34-1.528.68-1.868 1.523-1.356 5.086-2.879 9.152-4.574zm0 0" fill="#c11e07" stroke="#c11e07" stroke-miterlimit="10" stroke-width="1.27152"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pem.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pfx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pgp.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M31.816 6.078c5.094 5.094 6.547 12.184 4.73 18.547l26.907 26.91.547 12-15.09-1.273v-7.637h-7.637v-7.637h-7.457L24 37.172c-6.363 1.816-13.637.363-18.547-4.73-7.27-7.27-7.27-19.27 0-26.544a18.494 18.494 0 0 1 26.363.18zM18 11.172c-2.184-2.184-5.453-2.184-7.637 0-2.18 2.18-2.18 5.453 0 7.637 2.184 2.18 5.453 2.18 7.637 0 2.184-2.184 2.184-5.637 0-7.637zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ph.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#999"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/phar.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M65.66 13.137 33.5.168 20.77 5.391l32.16 12.8zM12.73 8.59 1.34 13.137 33.5 26.105l11.39-4.546zM0 50.695l32.496 13.137V27.789L0 14.652zm54.27-30.82V29.98l-1.34-.843-1.34 1.851-1.34-.672-1.34 1.852-1.34-.84-1.34 1.852V23.074L34.504 27.79v36.043L67 50.695V14.652zm0 0" fill="#6781b2"/><path d="M7.371 36.21c.668.337 1.34.337 1.676.169.332-.168.836-.504 1.172-1.012.332-.504.5-1.008.332-1.176-.164-.171-.5-.675-1.172-.843l-1.004-.676-1.844 3.035zm-5.195 2.528c-.164-.168-.164-.168 0-.34l4.86-8.082.167-.168 3.516 1.348c1.172.504 1.844 1.008 2.18 1.852.335.843.167 1.683-.504 2.695-.168.336-.504.84-.836 1.008-.336.34-.672.508-1.172.676-.504.168-1.008.336-1.508.336-.504 0-1.176-.168-1.844-.504l-1.508-.508-1.34 2.023-.167.168zm14.238 2.864c-.168-.172-.168-.172 0-.34l2.18-3.535c.168-.336.332-.676.168-.676 0-.168-.168-.336-.672-.504l-1.34-.504-2.68 4.379-.168.168-1.843-.676s-.168 0-.168-.168v-.168l4.859-8.082.168-.168 1.844.672s.164 0 .164.168v.168l-1.172 2.023 1.34.504c1.008.336 1.676.844 2.011 1.516.336.504.168 1.348-.335 2.02l-2.344 3.706-.168.168zm7.707 1.007c.668.34 1.34.34 1.84.168.504-.168.84-.504 1.176-1.007.332-.508.5-1.012.332-1.18-.168-.336-.5-.676-1.172-.844l-1.172-.504-1.844 3.031zm-5.36 2.528c-.167-.168-.167-.168-.167-.336l5.023-8.086s.168-.168.336 0l3.684 1.347c1.172.508 1.843 1.012 2.18 1.852.331.844.167 1.688-.504 2.695-.168.34-.504.844-.836 1.012-.336.336-.672.504-1.176.672-.5.172-1.172.34-1.672.34-.504 0-1.176-.168-2.012-.508l-1.34-.336-1.34 2.02s-.167.171-.335 0zm0 0" fill="#fff"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/php.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="52"><path style="stroke:none;fill-rule:nonzero;fill:#6781b2;fill-opacity:1" d="M36.18 38.57c3.308 1.235 8.703 1.063 12.535.528.7 1.593-.348 3.715.347 5.48.352.887 1.395 1.594 3.137 2.477.348.18.867.18 1.219.355l.348.352c.695.531 2.09 1.418 2.437 1.418.524.172 1.047.172 1.39.172h1.747c2.433-.172 4.87-.883 5.57-1.766 1.219-1.59.524-5.125.348-8.309-.176-2.652-.524-6.187 0-8.308.176-.707.87-1.418 1.215-2.301 1.394-3.004 2.789-7.957 2.27-12.73-.352-2.122-1.395-4.063-1.571-5.653 3.308.352 6.441-.355 9.226 0 1.747.172 3.137 1.41 4.704 1.235.351-.883 1.39-1.415 1.39-2.473.176-1.238-.344-2.656-.867-3.54-2.266-.35-4.004 1.77-6.266 1.95-.703 0-1.57-.18-2.441-.355-2.613-.18-6.266.527-8.531 0-1.567-.356-2.961-2.122-4.528-2.829-.347-.18-1.043 0-1.394-.355-.52-.176-.871-.531-1.215-.531-1.742-.703-3.66-1.414-5.402-1.59C48.543.91 44.016.91 39.836 1.266c-1.39.18-2.61.882-4.004.53C34.785 1.618 34.613.91 33.918.56c-2.965-1.414-5.922.175-7.84 1.238-1.39.707-3.129 1.766-4.527 1.945-1.39.352-3.48 0-4.7 0-1.566 0-3.656.352-5.398.531-1.566.352-3.828.528-4.7 1.235-2.437 1.414-3.132 7.957-4.007 11.847-.344 1.415-.867 2.829-1.215 4.243-.523 3.18-.875 6.543-.875 9.547-.172 6.187-.867 14.851 2.27 17.148.695.531 2.957 1.238 3.652.887.176 0 1.047-.887 1.219-1.239.176-.53-.344-1.238-.344-1.945 0-1.418-.351-3.184-.351-4.598 0-3.71.695-7.777 1.566-9.37 0-.176.523-.176.523-.352.176-.352 0-.707.352-.887.695-.703 1.738-1.414 2.434-1.59 2.09-.883 3.308.176 4.18 1.414 1.741 2.301 2.09 6.188 2.265 9.903v2.297c0 .882-.352 1.765-.352 2.296.524 1.414 2.961 2.125 4.008 2.832 0 .528.176 1.239.52 1.59.523.887 1.394 1.414 1.918 1.766 2.609 1.418 9.226.531 10.449-1.234.172-.18.344-.356.344-.708.171-.53.523-1.062.523-1.414 1.047-3.183-.172-6.011.348-9.37zM32.523 6.922c-.171.18-.171.18 0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pid.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M62.887 38.266c-2.684-.84-4.532-3.36-4.532-6.215 0-2.852 1.848-5.371 4.532-6.211.84-.336 1.343-1.172 1.008-2.012-.84-3.023-1.848-5.707-3.524-8.394-.504-.84-1.344-1.008-2.184-.672-1.007.504-2.015.84-3.19.84-3.692 0-6.548-3.024-6.548-6.547 0-1.176.336-2.184.84-3.188.504-.84.168-1.68-.672-2.183a40.47 40.47 0 0 0-8.39-3.528c-.84-.168-1.68.168-2.016 1.008C37.37 3.852 34.855 5.7 32 5.7s-5.371-1.847-6.21-4.535C25.452.324 24.612-.18 23.772.156c-3.02.84-5.707 1.848-8.39 3.528-.84.503-1.008 1.343-.672 2.183.504 1.004.84 2.012.84 3.188 0 3.691-3.024 6.547-6.547 6.547-1.176 0-2.184-.336-3.191-.84-.84-.504-1.68-.168-2.184.672a40.699 40.699 0 0 0-3.524 8.394c-.167.84.168 1.676 1.008 2.012 2.684.84 4.532 3.36 4.532 6.21 0 2.856-1.848 5.376-4.532 6.216-.84.332-1.343 1.172-1.008 2.011.84 3.024 1.848 5.707 3.524 8.395.504.84 1.344 1.008 2.184.672 1.007-.504 2.015-.84 3.19-.84 3.692 0 6.548 3.02 6.548 6.547 0 1.176-.336 2.183-.84 3.187-.504.84-.168 1.68.672 2.184a40.47 40.47 0 0 0 8.39 3.527h.336c.672 0 1.344-.504 1.512-1.176.84-2.687 3.356-4.535 6.211-4.535s5.371 1.848 6.211 4.535c.336.84 1.176 1.344 2.016 1.008 3.02-.84 5.707-1.847 8.39-3.527.84-.504 1.008-1.344.672-2.184-.504-1.004-.84-2.011-.84-3.187 0-3.692 3.024-6.547 6.547-6.547 1.176 0 2.184.336 3.192.84.84.504 1.68.168 2.183-.672a40.698 40.698 0 0 0 3.524-8.395c.503-.672 0-1.511-.84-1.843zm-30.719 3.691c-5.371 0-9.902-4.363-9.902-9.906 0-5.371 4.363-9.903 9.902-9.903 5.371 0 9.902 4.364 9.902 9.903 0 5.375-4.53 9.906-9.902 9.906zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pkg.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#4d1b9b"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pl.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#3a3c5b"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/plist.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="M69.5 0H4.633C2.125 0 .285 2.008.285 4.352V49.39c0 2.511 2.004 4.351 4.348 4.351h64.699c2.508 0 4.348-2.008 4.348-4.351V4.52C74.016 2.008 72.008 0 69.5 0zm0 0" fill="#ced2d8"/><path d="M4.633 10.883H69.5V49.39H4.633zm0 0" fill="#f2f2f2"/><path d="M18.008 5.86c0 .831-.676 1.507-1.508 1.507a1.508 1.508 0 0 1 0-3.015c.832 0 1.508.675 1.508 1.507zm0 0" fill="#54b845"/><path d="M12.824 5.86c0 .831-.676 1.507-1.504 1.507a1.509 1.509 0 0 1 0-3.015c.828 0 1.504.675 1.504 1.507zm0 0" fill="#fbd303"/><path d="M7.977 5.86c0 .831-.676 1.507-1.508 1.507a1.508 1.508 0 0 1 0-3.015c.832 0 1.508.675 1.508 1.507zm0 0" fill="#f0582f"/><path d="M27.703 63.285c-.836-.668-1.672-1.336-2.34-2.176-.668-.836-1.34-1.84-2.008-2.68-1.503-2.175-2.507-4.519-3.343-6.863-1.004-2.843-1.336-5.523-1.336-8.203 0-3.015.668-5.523 1.84-7.703 1-1.672 2.34-3.18 4.011-4.183 1.672-1.004 3.512-1.508 5.348-1.676.672 0 1.34.168 2.176.336.5.168 1.168.336 2.004.668 1.004.336 1.672.672 1.84.672.667.168 1.171.335 1.503.335.336 0 .672-.167 1.172-.167.336-.168.836-.336 1.504-.672.668-.332 1.34-.5 1.84-.668l2.008-.504c.668-.168 1.504-.168 2.172-.168 1.336.168 2.508.336 3.68.84 2.003.836 3.507 2.007 4.68 3.683-.5.332-1.005.668-1.337 1.004-1.004.836-1.672 1.84-2.344 2.844-.836 1.508-1.168 3.184-1.168 4.855 0 2.012.5 3.852 1.672 5.36a7.757 7.757 0 0 0 2.84 2.847c.672.332 1.172.668 1.672.836-.164.668-.5 1.34-.836 2.008-.668 1.508-1.504 3.016-2.34 4.356-.836 1.172-1.504 2.007-2.004 2.675-.836.84-1.507 1.676-2.175 2.012-.836.5-1.84.836-2.676.836-.668 0-1.336 0-2.004-.168-.504-.168-1.172-.336-1.672-.668-.504-.336-1.172-.504-1.672-.672-.668-.168-1.504-.332-2.176-.332-.836 0-1.504.164-2.172.332s-1.171.336-1.671.672c-.836.332-1.336.5-1.672.668-.668.168-1.172.336-1.84.336-1.336.168-2.34-.168-3.176-.672zm13.04-34.992c-1.337.672-2.509.84-3.677.84-.168-1.172 0-2.512.5-3.852.504-1.172 1.004-2.176 1.84-3.18.836-1.007 1.84-1.843 3.008-2.343 1.172-.672 2.508-1.008 3.512-1.008.168 1.34 0 2.512-.5 3.852-.504 1.171-1.004 2.343-1.84 3.347-.836.84-1.84 1.676-2.844 2.344zm0 0" fill="#c6a8e5"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pm.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M64 32c0 17.645-14.355 32-32 32S0 49.645 0 32 14.355 0 32 0s32 14.355 32 32" fill="#999"/><g fill="#fff"><path d="M54.313 34.422c-1.036-6.746-8.13-11.242-13.493-14.531-2.246-1.383-6.054-3.286-6.57-6.399-.176-1.21-.176-2.594-.176-3.805V8.13c0-.692-.691.172-1.035-.344-.867-1.387-.52.344-.52 1.211.172 1.727.52 3.457.52 5.188 0 3.285-.52 6.574-1.387 9.687-1.902 7.438-3.457 15.223-1.554 22.832a24.518 24.518 0 0 0 1.554 4.668c.176.52.52 1.73 1.211 1.906 2.078.516 3.633.692 5.192 2.246 1.039.868 1.73.348 2.941 0 3.633-1.382 6.746-3.285 9.34-6.226 3.285-4.496 4.844-9.512 3.977-14.875m-3.805 6.746c-.344 2.766-2.074 5.363-3.805 7.437-1.383 1.56-3.113 3.461-5.016 4.153-.69.172.172-1.211.172-1.211.52-.867 1.383-1.73 2.075-2.594 1.039-1.21 1.902-2.598 2.421-3.98 1.903-5.016 1.56-10.899-1.382-15.395-1.555-2.422-3.805-4.496-5.88-6.398-1.038-.868-2.077-1.73-2.94-2.77-.176-.172-2.079-2.594-1.387-2.941.175-.172 4.152 3.98 4.5 4.324 1.554 1.21 3.285 2.422 4.843 3.809 2.075 1.902 4.149 3.976 5.36 6.57 1.21 2.77 1.386 6.055 1.039 8.996"/><path d="M30.79.863c.519.348.69 2.77.69 4.844 0 2.078.172 11.246-.52 13.664-.69 2.422-2.245 5.192-3.804 7.613-1.73 2.422-3.633 7.438-3.457 10.551 0 3.113 1.903 8.13 3.285 10.38 1.383 2.073 3.805 5.015 3.286 5.706-.864 1.211-4.668-2.941-6.747-5.363-1.902-2.422-3.976-7.262-3.976-11.07 0-3.805 2.074-7.262 3.633-9.34 1.554-2.075 4.496-5.707 5.36-7.438.866-1.73 1.73-3.457 1.901-5.707.348-2.25 0-10.55 0-10.55S30.27.52 30.79.862"/><path d="M29.234 4.844c.516.343.692 1.039.692 1.73 0 .692-.176 3.633-.348 6.57-.172 2.942-2.594 5.364-4.152 7.094-1.727 1.73-6.746 7.09-8.473 9.688-1.906 2.594-2.77 6.05-2.598 8.992.176 2.941.868 5.883 3.633 8.996 2.77 3.113 4.672 4.496 6.227 5.363 1.387.692 2.941 1.211 2.597 1.903-.347.691-1.73.172-3.289-.348-1.554-.52-6.746-2.594-9.687-6.055-2.938-3.457-4.496-7.957-4.324-12.105.175-4.324 1.386-6.055 3.289-8.824 1.902-2.766 7.437-6.918 9.168-7.957 1.73-1.036 3.976-2.766 5.187-4.325 1.211-1.382 1.73-2.593 1.73-4.668 0-1.902.173-3.804 0-4.5-.171-.515-.171-1.902.348-1.554m.172 51.89c.344 0 .172 1.211-.347 1.73-.52.52-1.211.864-1.383.692s.52-.343 1.039-.863c.52-.691.344-1.559.691-1.559m5.36-.172c-.344 0-.172 1.211.347 1.731s1.211.863 1.383.691c.176-.172-.52-.347-1.035-.867-.52-.515-.348-1.554-.695-1.554m-2.418 1.382c0 1.04 0 1.903-.176 1.903-.344 0-.172-.864-.172-1.903 0-1.039-.172-1.902.172-1.902.348 0 .176.863.176 1.902"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/png.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/po.svg
1
1
<svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#a87c2d" stroke="#a87c2d" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pom.svg
1
1
<svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M55.809.504S31.965.832 16.73 19.992c-.828 1.145-1.656 2.13-2.484 3.274-.496.656-.996 7.207-.996 7.207s-.66-.493-.992-1.309c-.496-.984-.664-2.129-.664-2.129C2.984 40.953 5.469 48.16 5.469 48.16c-.664 1.637-1.989 2.617-3.809 6.387C-.16 58.313.004 61.914.004 61.914c0 .656.164.82.496.164 0 0 1.988-3.766 3.477-6.223.996-1.636 3.976-5.402 3.976-5.402s4.969.16 10.93-1.312c-.496-.164-2.153-.657-3.313-1.145-1.16-.492-1.82-1.312-1.82-1.312l21.36-4.91c2.98-1.801 5.628-3.93 7.785-6.551 11.257-13.266 14.074-33.57 14.074-33.57.164-.657-.332-1.15-1.16-1.15zM34.613 21.629s-9.937 8.68-14.902 13.266C14.742 39.48 8.117 50.453 8.117 50.453L5.633 48.16s1.824-4.422 9.11-13.265c7.12-8.68 19.538-14.083 19.538-14.083 1.492-.656 1.657-.328.332.817zm0 0" fill="#ef712f"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pot.svg
1
1
<svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#c93" stroke="#c93" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/potx.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pps.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ppsx.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ppt.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pptm.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pptx.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#f57e00" stroke="#f57e00" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/prop.svg
1
1
<svg height="64" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M10.867 48.152c0 1.551 1.04 2.582 2.602 2.582h2.773v9.305c0 2.066 1.91 3.961 3.992 3.961s3.989-1.895 3.989-3.96v-9.306h5.379v9.305c0 2.066 1.91 3.961 3.992 3.961s3.988-1.895 3.988-3.96v-9.306h2.602c1.562 0 2.605-1.03 2.605-2.757V21.449H10.867zM4.097 21.45C2.017 21.45.11 23.344.11 25.41v18.606c0 2.066 1.907 3.96 3.989 3.96s3.992-1.894 3.992-3.96V25.41c0-2.066-1.735-3.96-3.992-3.96zm45.805 0c-2.082 0-3.992 1.895-3.992 3.961v18.606c0 2.066 1.91 3.96 3.992 3.96s3.989-1.894 3.989-3.96V25.41c0-2.066-1.907-3.96-3.989-3.96zM36.367 5.945l3.473-3.449c.52-.516.52-1.375 0-1.894a1.373 1.373 0 0 0-1.91 0L33.94 4.566c-1.91-1.379-4.34-1.894-6.941-1.894-2.777 0-5.031.515-7.285 1.55L15.898.259c-.523-.344-1.562-.344-2.082 0-.347.515-.347 1.55 0 2.066l3.47 3.446c-3.817 2.93-6.419 7.41-6.419 12.75h32.266c0-5.168-2.602-9.82-6.766-12.575zm-14.746 7.407h-2.773v-2.586h2.773zm13.531 0H32.38v-2.586h2.773zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ps.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M16.223 21.805.09 55.844l3.012 3.015 20.035-20.035c-.711-1.594-.532-3.543.886-4.96 1.774-1.774 4.43-1.774 6.204 0 1.773 1.769 1.773 4.429 0 6.202-1.243 1.243-3.368 1.594-4.965.887L5.23 60.984 8.242 64l34.04-16.133L49.73 27.48 36.61 14.36zm46.625-4.075L46.184 1.062c-1.418-1.417-3.547-1.417-4.965 0L37.32 4.966c-1.422 1.418-1.422 3.543 0 4.965l16.664 16.664c1.418 1.418 3.543 1.418 4.965 0l3.899-3.903c1.418-1.418 1.418-3.543 0-4.96zm0 0" fill="#fea500"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ps1.svg
1
1
<svg height="63" width="51" xmlns="http://www.w3.org/2000/svg"><g fill="#737373"><path d="M28.21 1.082 6.696 13.367v24.414l21.516 12.285 21.516-12.285V13.367zm0 0"/><path d="m6.695 13.367 21.516 12.285 21.516-12.285L28.21 1.082zM6.695 37.781l21.516 12.285 21.516-12.285L28.21 25.652zm0 0"/></g><path d="M6.055 39.04 27.57 51.167c.16.156.48.156.64.156.157 0 .477-.156.638-.156l21.515-12.285c.477-.156.637-.63.637-1.102V13.367c0-.472-.16-.785-.637-1.101L28.848.136c-.48-.156-.797-.156-1.278 0L6.055 12.423c-.477.16-.637.633-.637 1.101v24.57c0 .16.16.634.637.946zM28.21 2.5l18.965 10.867L28.21 24.395 9.242 13.367zm2.547 23.152 17.691-10.078v21.574L29.484 48.02V26.44M7.97 35.574v-20L25.66 25.652l1.274.79V48.02L7.969 37.148" fill="#fff"/><path d="M11.953 35.102.637 41.559v12.757l11.316 6.457 11.156-6.457V41.56zm0 0" fill="#fff"/><path d="m.637 41.559 11.316 6.3 11.156-6.3-11.156-6.457zm0 12.757 11.316 6.457 11.156-6.457-11.156-6.457zm0 0" fill="#fff"/><path d="m.32 54.95 11.313 6.456c.16.157.16.157.32.157s.16 0 .32-.157l11.313-6.457c.16-.16.32-.316.32-.472V41.559c0-.157-.16-.473-.32-.473l-11.313-6.457a.496.496 0 0 0-.64 0L.32 41.086c-.16.16-.32.316-.32.473v12.757c0 .16.16.473.32.633zm11.633-19.06 9.883 5.669-9.883 5.828-9.883-5.828zm1.274 11.97 9.246-5.196v11.34l-9.883 5.512v-11.34M1.434 53.215v-10.55l9.246 5.194.636.473v11.34l-9.882-5.668" fill="#444"/><path d="m24.383 45.656-7.328 4.094v8.348l7.328 4.254 7.332-4.254V49.75zm0 0" fill="#517889"/><path d="m17.055 49.75 7.328 4.094 7.332-4.094-7.332-4.094zm0 8.348 7.328 4.254 7.332-4.254-7.332-4.254zm0 0" fill="#517889"/><path d="m16.895 58.57 7.332 4.254c.156 0 .156.156.156.156s.16 0 .16-.156l7.332-4.254c.16-.156.16-.156.16-.472V49.75c0-.156-.16-.316-.16-.473l-7.332-4.254c-.16-.156-.316-.156-.477 0l-7.332 4.254c-.16.157-.16.157-.16.473v8.348c.16.156.16.316.32.472zm7.488-12.441 6.535 3.621-6.535 3.621-6.531-3.777zm.957 7.715 5.898-3.465v7.402l-6.535 3.625V54.16m-7.172 3.149v-6.77l5.899 3.465.476.156v7.246l-6.535-3.625" fill="#fff"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/psd.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="M7.225 18.125c-1 0-2-.1-2.8-.3v12.1h-3.8v-27.7v12.1m19.5 27.5m0 0miterlimit="10transform="matrix(2.05225 0 0 2.0612 .316 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/psp.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pst.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><g fill="#1d6fb5"><path d="M18.754 25.742c-2.668.36-4.8 3.219-4.8 6.258s2.132 6.078 4.8 6.258c2.668.355 4.805-2.504 4.805-6.258s-2.137-6.613-4.805-6.258zm0 0"/><path d="M.074 7.508v49.52L38.504 64V0zm18.68 34.683c-4.27-.539-7.649-5.187-7.649-10.191 0-5.184 3.38-9.652 7.649-10.191 4.27-.536 7.652 4.113 7.652 10.191 0 6.258-3.383 10.727-7.652 10.191zm50.172-27.175L47.754 32.715l-5.691-4.649v-14.66h26.863zm0 0"/><path d="m68.926 18.414-21.172 17.7-5.691-4.65V51.31h26.863zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pub.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#077265"><path d="M23.91 25.406c0-4.746-2.285-7.031-5.976-7.031-1.407 0-2.461.176-3.164.527V32.97c.703.351 1.582.527 2.636.527 4.219-.351 6.504-2.992 6.504-8.09zm0 0"/><path d="m0 53.715 38.68 9.844V.44L0 10.285zm11.605-38.508c1.582-.527 3.692-.703 6.153-.703 3.34 0 5.625 1.055 7.035 2.992 1.406 1.758 2.285 4.219 2.285 7.559s-.703 5.976-1.933 7.738c-1.762 2.637-4.575 4.043-7.739 4.043-1.054 0-1.933 0-2.636-.352v14.418h-3.34V15.207zm30.418-6.855v3.867h13.008V23.12H42.023v4.043h13.008v4.746H42.023v3.871h13.008v4.922H42.023v4.219h13.008v4.926H42.023v8.086H64V8.352zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/py.svg
1
1
<svg height="63" width="62" xmlns="http://www.w3.org/2000/svg"><path d="M29.816 62.316c-7.492-.168-12.261-2.523-13.453-6.398l-.172-.504V47.84c0-8.758 0-8.926.512-10.27.852-2.527 2.895-4.21 5.961-4.882l.68-.168h8.687c6.301 0 8.852 0 9.196-.168 1.87-.34 2.894-.676 3.914-1.516 1.363-1.012 2.386-2.695 2.726-4.379.34-1.348.34-1.18.34-5.89v-4.212h6.129l.684.168c3.234 1.012 5.617 3.872 6.64 8.586.34 1.852.34 1.852.34 6.399 0 4.379 0 4.379-.34 5.89-.34 1.348-.683 2.696-1.363 3.875-1.192 2.356-3.067 4.04-5.277 4.715-1.364.504-.172.336-12.774.504h-11.41v2.02h14.988v7.746c-.172.504-.172 1.008-.683 1.683-.508.672-1.36 1.512-1.871 2.02-2.043 1.348-5.11 2.187-9.196 2.355zm10.387-4.714c1.363-.168 2.555-1.684 2.215-3.028-.172-1.18-1.023-2.023-2.215-2.191-1.871-.168-3.406 1.347-3.066 3.031.172 1.348 1.363 2.188 2.726 2.188zM7.848 46.66c-1.703-.336-3.403-1.347-4.598-2.863C1.04 41.105-.152 36.39.016 30.668c.172-3.54.68-6.23 1.875-8.59 1.359-3.027 3.574-4.543 6.468-5.219.684-.168.684-.168 11.75-.168h11.239c.172 0 .172-.168.172-1.007v-1.012H16.535V10.8c0-4.211 0-4.211.34-5.051 1.363-2.695 5.11-4.379 11.066-4.883.512 0 1.536-.168 2.727-.168 6.64-.168 11.578 1.008 14.133 3.367l.851.84c.34.508.852 1.348 1.024 2.192l.168.504v8.082c0 7.406 0 8.078-.168 8.586-.172.671-.512 1.683-.684 2.187-1.02 1.852-2.722 3.031-4.937 3.703-1.364.336-.852.336-10.387.508-9.535 0-9.027 0-10.387.336-2.726.672-4.597 2.691-5.281 5.555-.34 1.347-.34 1.18-.34 5.89v4.38h-2.894c-2.895 0-3.746 0-3.918-.169zm16.007-36.027c1.024-.508 1.875-1.852 1.532-2.863-.34-1.012-1.02-1.852-1.871-2.188-1.532-.508-3.235.504-3.407 2.188-.168 1.347.512 2.695 1.875 3.03.168.169.508.169 1.02.169.34-.168.34-.168.851-.336zm0 0" fill="#fed142"/><path d="M7.848 46.66c-1.703-.336-3.403-1.347-4.598-2.863C1.04 41.105-.152 36.39.016 30.668c.171-3.54.68-6.23 1.875-8.59 1.359-3.027 3.574-4.543 6.468-5.219.684-.168.684-.168 11.75-.168h11.239c.172 0 .172-.168.172-1.007v-1.012H16.535V10.8c0-4.211 0-4.211.34-5.051 1.363-2.695 5.11-4.379 11.066-4.883.512 0 1.536-.168 2.727-.168 6.64-.168 11.578 1.008 14.133 3.367l.851.84c.34.508.852 1.348 1.024 2.192l.168.504v8.082c0 7.406 0 8.078-.168 8.586-.172.671-.512 1.683-.684 2.187-1.02 1.852-2.722 3.031-4.937 3.703-1.364.336-.852.336-10.387.508-9.535 0-9.027 0-10.387.336-2.726.672-4.597 2.691-5.281 5.555-.34 1.347-.34 1.18-.34 5.89v4.38h-2.894c-2.895 0-3.746 0-3.918-.169zm16.007-36.027c1.024-.508 1.875-1.852 1.532-2.863-.34-1.012-1.02-1.852-1.871-2.188-1.532-.508-3.235.504-3.407 2.188-.168 1.347.512 2.695 1.875 3.03.168.169.508.169 1.02.169.34-.168.34-.168.851-.336zm0 0" fill="#3571a3"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/pyc.svg
1
1
<svg height="63" width="62" xmlns="http://www.w3.org/2000/svg"><g fill="#666"><path d="M29.816 62.316c-7.492-.168-12.261-2.523-13.453-6.398l-.172-.504V47.84c0-8.758 0-8.926.512-10.27.852-2.527 2.895-4.21 5.961-4.882l.68-.168h8.687c6.301 0 8.852 0 9.196-.168 1.87-.34 2.894-.676 3.914-1.516 1.363-1.012 2.386-2.695 2.726-4.379.34-1.348.34-1.18.34-5.89v-4.212h6.129l.684.168c3.234 1.012 5.617 3.872 6.64 8.586.34 1.852.34 1.852.34 6.399 0 4.379 0 4.379-.34 5.89-.34 1.348-.683 2.696-1.363 3.875-1.192 2.356-3.067 4.04-5.277 4.715-1.364.504-.172.336-12.774.504h-11.41v2.02h14.988v7.746c-.172.504-.172 1.008-.683 1.683-.508.672-1.36 1.512-1.871 2.02-2.043 1.348-5.11 2.187-9.196 2.355zm10.387-4.714c1.363-.168 2.555-1.684 2.215-3.028-.172-1.18-1.023-2.023-2.215-2.191-1.871-.168-3.406 1.347-3.066 3.031.172 1.348 1.363 2.188 2.726 2.188zM7.848 46.66c-1.703-.336-3.403-1.347-4.598-2.863C1.04 41.105-.152 36.39.016 30.668c.172-3.54.68-6.23 1.875-8.59 1.359-3.027 3.574-4.543 6.468-5.219.684-.168.684-.168 11.75-.168h11.239c.172 0 .172-.168.172-1.007v-1.012H16.535V10.8c0-4.211 0-4.211.34-5.051 1.363-2.695 5.11-4.379 11.066-4.883.512 0 1.536-.168 2.727-.168 6.64-.168 11.578 1.008 14.133 3.367l.851.84c.34.508.852 1.348 1.024 2.192l.168.504v8.082c0 7.406 0 8.078-.168 8.586-.172.671-.512 1.683-.684 2.187-1.02 1.852-2.722 3.031-4.937 3.703-1.364.336-.852.336-10.387.508-9.535 0-9.027 0-10.387.336-2.726.672-4.597 2.691-5.281 5.555-.34 1.347-.34 1.18-.34 5.89v4.38h-2.894c-2.895 0-3.746 0-3.918-.169zm16.007-36.027c1.024-.508 1.875-1.852 1.532-2.863-.34-1.012-1.02-1.852-1.871-2.188-1.532-.508-3.235.504-3.407 2.188-.168 1.347.512 2.695 1.875 3.03.168.169.508.169 1.02.169.34-.168.34-.168.851-.336zm0 0"/><path d="M7.848 46.66c-1.703-.336-3.403-1.347-4.598-2.863C1.04 41.105-.152 36.39.016 30.668c.171-3.54.68-6.23 1.875-8.59 1.359-3.027 3.574-4.543 6.468-5.219.684-.168.684-.168 11.75-.168h11.239c.172 0 .172-.168.172-1.007v-1.012H16.535V10.8c0-4.211 0-4.211.34-5.051 1.363-2.695 5.11-4.379 11.066-4.883.512 0 1.536-.168 2.727-.168 6.64-.168 11.578 1.008 14.133 3.367l.851.84c.34.508.852 1.348 1.024 2.192l.168.504v8.082c0 7.406 0 8.078-.168 8.586-.172.671-.512 1.683-.684 2.187-1.02 1.852-2.722 3.031-4.937 3.703-1.364.336-.852.336-10.387.508-9.535 0-9.027 0-10.387.336-2.726.672-4.597 2.691-5.281 5.555-.34 1.347-.34 1.18-.34 5.89v4.38h-2.894c-2.895 0-3.746 0-3.918-.169zm16.007-36.027c1.024-.508 1.875-1.852 1.532-2.863-.34-1.012-1.02-1.852-1.871-2.188-1.532-.508-3.235.504-3.407 2.188-.168 1.347.512 2.695 1.875 3.03.168.169.508.169 1.02.169.34-.168.34-.168.851-.336zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/qt.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/r.svg
1
1
<svg height="64" width="83" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientTransform="matrix(.14059 0 0 .09419 -.088 -.345)" gradientUnits="userSpaceOnUse" x1=".741" x2="590.86" y1="3.666" y2="593.79"><stop offset="0" stop-color="#cbced0"/><stop offset="1" stop-color="#84838b"/></linearGradient><linearGradient id="b" gradientTransform="matrix(.1139 0 0 .11627 -.088 -.345)" gradientUnits="userSpaceOnUse" x1="301.03" x2="703.07" y1="151.4" y2="553.44"><stop offset="0" stop-color="#276dc3"/><stop offset="1" stop-color="#165caa"/></linearGradient><g fill-rule="evenodd"><path d="M41.5 55.586C18.59 55.586.016 43.14.016 27.793.016 12.441 18.59 0 41.5 0s41.484 12.441 41.484 27.793c0 15.348-18.574 27.793-41.484 27.793zm6.352-44.719c-17.414 0-31.536 8.504-31.536 19 0 10.492 14.122 19 31.536 19s30.265-5.816 30.265-19c0-13.18-12.851-19-30.265-19zm0 0" fill="url(#a)"/><path d="M63.195 43.047s2.508.758 3.97 1.496c.503.254 1.382.766 2.01 1.437.622.657.923 1.325.923 1.325l9.894 16.687L64 64l-7.48-14.047s-1.532-2.633-2.473-3.398c-.785-.637-1.121-.864-1.899-.864h-3.8l.004 18.297-14.153.008V17.258h28.418s12.946.23 12.946 12.55-12.368 13.235-12.368 13.235zM57.04 27.395l-8.566-.004-.008 7.945 8.574-.004s3.969-.012 3.969-4.039c0-4.113-3.969-3.898-3.969-3.898zm0 0" fill="url(#b)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ra.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ram.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="54"><path style="fill-rule:nonzero;fill:#039;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#039;stroke-opacity:1;stroke-miterlimit:10" d="m10.45 23.48-7.7-8.5v-.1l7.7-8.5V.68L.25 12.681v4.5l10.2 11.898zM36.25.679V6.38l7.9 8.5v.1l-7.9 8.5v5.6l10.2-11.7v-4.9zM28.549 5.08c-1.299-.6-3-1.6-3.598-3 0-.7-.602-1.3-1.301-1.3-.7 0-1.299.6-1.299 1.3v17.3c-1.3-.7-3.9-.5-5.9.6-3.002 1.7-4.3 4.899-3.002 7.2 1.301 2.2 5.002 2.7 8 1 2.1-1.2 3.502-3.2 3.601-5.1v-15c4.4 0 7 1.8 6 6-.199.8-.401 1.6-.8 2.299-.2.302-.2.5.1.802.2.198.6.198 1-.2 1.7-1.6 2.7-3.7 2.7-6.001 0-2.9-2.9-4.8-5.501-5.9zm0 0" transform="matrix(1.79872 0 0 1.81157 0 .047)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rar.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/raw.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rb.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M61.988 2.012v59.976L46.996 17.004zM2.012 61.988h59.976L17.004 46.996zm14.992-14.992 44.984 14.992L32 32zM32 32l29.988 29.988-14.992-44.984zM2.012 46.996v14.992l14.992-14.992zM32 32H17.004v14.996zm14.996-14.996H32V32zM61.988 2.012H46.996v14.992zM17.004 32 2.012 46.996h14.992zM32 17.004 17.004 32H32zM46.996 2.012 32 17.004h14.996zm0 0" fill="#992315" stroke="#fff" stroke-width="1.66605"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rdf.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="54"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.242 25.867c-.5 0-1.1-.2-1.498-.6l-8.4-8.401c-.802-.799-.802-2.099 0-3l8.4-8.398c.8-.8 2.098-.8 2.999 0 .8.8.8 2.098 0 2.999l-6.9 6.9 6.9 6.9c.8.801.8 2.1 0 3-.401.4-1 .6-1.5.6zm25.1 0c-.499 0-1.099-.2-1.5-.6-.8-.8-.8-2.099 0-3.002l6.9-6.898-6.9-6.9c-.8-.8-.8-2.101 0-3 .8-.8 2.1-.8 3.001 0l8.398 8.4c.802.8.802 2.1 0 3l-8.398 8.4c-.4.4-1.001.6-1.5.6zm-16.7 4.1c-.199 0-.398 0-.698-.1-1.102-.401-1.702-1.5-1.301-2.6l8.398-25.1c.4-1.1 1.5-1.699 2.6-1.301 1.102.4 1.702 1.5 1.301 2.601l-8.398 25.1c-.202.899-1.102 1.4-1.901 1.4zm0 0" transform="matrix(1.74425 0 0 1.75713 0 .013)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rdl.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="54"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.242 25.867c-.5 0-1.1-.2-1.498-.6l-8.4-8.401c-.802-.799-.802-2.099 0-3l8.4-8.398c.8-.8 2.098-.8 2.999 0 .8.8.8 2.098 0 2.999l-6.9 6.9 6.9 6.9c.8.801.8 2.1 0 3-.401.4-1 .6-1.5.6zm25.1 0c-.499 0-1.099-.2-1.5-.6-.8-.8-.8-2.099 0-3.002l6.9-6.898-6.9-6.9c-.8-.8-.8-2.101 0-3 .8-.8 2.1-.8 3.001 0l8.398 8.4c.802.8.802 2.1 0 3l-8.398 8.4c-.4.4-1.001.6-1.5.6zm-16.7 4.1c-.199 0-.398 0-.698-.1-1.102-.401-1.702-1.5-1.301-2.6l8.398-25.1c.4-1.1 1.5-1.699 2.6-1.301 1.102.4 1.702 1.5 1.301 2.601l-8.398 25.1c-.202.899-1.102 1.4-1.901 1.4zm0 0" transform="matrix(1.74425 0 0 1.75713 0 .013)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/reg.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M0 56.355v-7.644h15.29V64H0zm22.754 0v-7.644h15.289V64h-15.29zm22.933 0v-7.644h15.29V64h-15.29zM48 38.934l-5.332-5.332L48 28.266l5.332-5.332 5.336 5.332L64 33.602l-5.332 5.332a125.106 125.106 0 0 1-5.336 5.332zM0 33.602v-7.645h15.29v15.29H0zm22.754 0v-7.645h15.289v15.29h-15.29zM25.066 16l-5.332-5.332 5.332-5.336L30.398 0l5.336 5.332 5.332 5.336L35.734 16c-2.843 3.023-5.336 5.332-5.336 5.332zM0 10.668V3.023h15.29v15.29H0zm0 0" fill="#3a898d"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/resx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/retry.svg
1
1
<svg height="64" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M54 36.781c0 14.856-12.04 26.813-27 26.813S0 51.637 0 36.78c0-5.12 1.547-9.906 4.129-14.004l8.77 4.953a16.11 16.11 0 0 0-2.75 9.051c0 9.223 7.566 16.735 16.851 16.735s16.852-7.512 16.852-16.735c0-7.683-5.329-14.172-12.551-16.222v8.539L6.19 14.754 31.301.406v9.906C44.199 12.364 54 23.462 54 36.782zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rm.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rmd.svg
1
1
<svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientTransform="matrix(.08235 0 0 .05573 -.06 .108)" gradientUnits="userSpaceOnUse" x1=".741" x2="590.86" y1="3.666" y2="593.79"><stop offset="0" stop-color="#cbced0"/><stop offset="1" stop-color="#84838b"/></linearGradient><linearGradient id="b" gradientTransform="matrix(.06671 0 0 .0688 -.06 .108)" gradientUnits="userSpaceOnUse" x1="301.03" x2="703.07" y1="151.4" y2="553.44"><stop offset="0" stop-color="#276dc3"/><stop offset="1" stop-color="#165caa"/></linearGradient><path d="M27.406 63.688V41.383h6.48l6.477 8.187 6.48-8.187h6.481v22.304h-6.48V50.898l-6.48 8.184-6.477-8.184v12.79zm38.875 0-9.719-10.844h6.481V41.383h6.477v11.46H76zm0 0" fill="#999"/><g fill-rule="evenodd"><path d="M24.297 33.2C10.879 33.2 0 25.84 0 16.757S10.879.312 24.297.312c13.422 0 24.3 7.364 24.3 16.446s-10.878 16.441-24.3 16.441zm3.719-26.458c-10.2 0-18.47 5.031-18.47 11.242 0 6.207 8.27 11.243 18.47 11.243s17.73-3.442 17.73-11.243c0-7.797-7.531-11.242-17.73-11.242zm0 0" fill="url(#a)"/><path d="M37.004 25.781s1.473.45 2.324.887c.297.152.813.453 1.18.852.363.386.539.78.539.78l5.797 9.876-9.367.004-4.38-8.313s-.898-1.555-1.449-2.008c-.46-.375-.66-.511-1.113-.511H28.31l.003 10.828-8.289.004V10.523h16.645s7.582.137 7.582 7.426c0 7.29-7.246 7.832-7.246 7.832zm-3.606-9.258-5.015-.003-.004 4.703 5.02-.004s2.324-.008 2.324-2.39c0-2.434-2.325-2.306-2.325-2.306zm0 0" fill="url(#b)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rom.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M68.633.102H16.39v7.226H5.738v2.274H0v2.062h5.738v2.684h10.653V26.94H5.738v2.477H0v2.066h5.738v2.27h10.653v13.625H5.738v2.48H0v2.063h5.738v2.273h10.653v9.703h52.242v-9.703h9.629v-2.48H84v-2.063h-5.738v-2.476h-9.63v-13.63h9.63v-2.062H84v-2.066h-5.738v-2.684h-9.63V14.141h9.63v-2.684H84V9.395h-5.738V7.12h-9.63zm-10.04 17.136c-2.253 0-4.097-1.86-4.097-4.129S56.34 8.98 58.594 8.98s4.097 1.86 4.097 4.13c0 2.476-1.843 4.128-4.097 4.128zm0 0" fill="#099"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rpm.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#55486d"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rpt.svg
1
1
<svg height="63" width="47" xmlns="http://www.w3.org/2000/svg"><path d="M10.62.6c-.398 0-.8.3-.998.7l-1 1.901h-6.8c-.7 0-1.302.6-1.302 1.2-.098 10.6.2 21.299 0 31.899 0 .7.602 1.3 1.302 1.3H26.02c.7 0 1.302-.6 1.302-1.3V4.4c0-.6-.602-1.302-1.302-1.302h-6.799l-1-1.899c-.2-.4-.7-.7-1.2-.7-2.2.1-4.501.1-6.4.1zm0 0" fill="#666" stroke="#666" stroke-miterlimit="10" transform="matrix(1.67417 0 0 1.65354 .211 0)"/><path d="M5.438 9.426h7.53c0 2.148.169 4.133 2.18 4.133h17.075c2.175 0 2.175-2.149 2.175-4.133h7.536v48.449H5.605c-.168-16.207-.168-32.41-.168-48.45zm0 0" fill="#fff"/><path d="M10.793 21H36.41v4.3H10.793zm0 8.434H36.41v4.296H10.793zm0 8.433H36.41V42H10.793zm0 8.434h17.078v4.297H10.793zm0 0" fill="#666"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rsa.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M48.793 26.879h-4.629V18.05C44.164 7.988 35.973.043 26 .043S7.836 8.164 7.836 18.051v8.828H3.207A3.181 3.181 0 0 0 0 30.059V60.78c0 1.762 1.426 3.176 3.207 3.176h45.586c1.781 0 3.207-1.414 3.207-3.176V29.883c0-1.59-1.426-3.004-3.207-3.004zM29.918 52.305c.355 1.058-.535 1.941-1.602 1.941h-4.808c-1.07 0-1.781-1.059-1.606-1.941l1.426-5.649c-1.781-.883-3.027-2.648-3.027-4.945 0-3 2.492-5.473 5.52-5.473 3.027 0 5.523 2.473 5.523 5.473 0 2.117-1.246 4.062-3.028 4.945zm5.164-25.426H16.918V18.05c0-4.942 4.098-9.004 9.082-9.004s9.082 4.062 9.082 9.004zm0 0" fill="#696"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rss.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M14.54 49.375c1.593 1.594 2.48 3.723 2.48 6.027 0 2.305-.887 4.43-2.48 6.028a8.46 8.46 0 0 1-6.032 2.48 8.451 8.451 0 0 1-6.028-2.48C.887 59.832 0 57.883 0 55.402c0-2.304.887-4.433 2.48-6.027a8.439 8.439 0 0 1 6.028-2.484c2.484 0 4.433 1.066 6.031 2.484zM.175 21.719v12.23c7.98 0 15.426 3.192 21.097 8.688 5.676 5.672 8.688 13.12 8.688 21.097h12.234c0-11.523-4.789-22.16-12.41-29.785C22.16 26.504 11.7 21.72.175 21.72zm0-21.63V12.32c28.367 0 51.59 23.227 51.59 51.59H64c0-17.55-7.09-33.504-18.793-45.027C33.684 7.18 17.73.09.176.09zm0 0" fill="#dd7d36"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rst.svg
1
1
<svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rtf.svg
1
1
<svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#666"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ru.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M61.988 2.012v59.976L46.996 17.004zM2.012 61.988h59.976L17.004 46.996zm14.992-14.992 44.984 14.992L32 32zM32 32l29.988 29.988-14.992-44.984zM2.012 46.996v14.992l14.992-14.992zM32 32H17.004v14.996zm14.996-14.996H32V32zM61.988 2.012H46.996v14.992zM17.004 32 2.012 46.996h14.992zM32 17.004 17.004 32H32zM46.996 2.012 32 17.004h14.996zm0 0" fill="#666" stroke="#fff" stroke-width="1.66605"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rub.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M61.988 2.012v59.976L46.996 17.004zM2.012 61.988h59.976L17.004 46.996zm14.992-14.992 44.984 14.992L32 32zM32 32l29.988 29.988-14.992-44.984zM2.012 46.996v14.992l14.992-14.992zM32 32H17.004v14.996zm14.996-14.996H32V32zM61.988 2.012H46.996v14.992zM17.004 32 2.012 46.996h14.992zM32 17.004 17.004 32H32zM46.996 2.012 32 17.004h14.996zm0 0" fill="#992315" stroke="#fff" stroke-width="1.66605"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/rxml.svg
1
1
<svg height="64" width="71" xmlns="http://www.w3.org/2000/svg"><linearGradient id="a" gradientTransform="matrix(.07693 0 0 .05184 -.057 .13)" gradientUnits="userSpaceOnUse" x1=".741" x2="590.86" y1="3.666" y2="593.79"><stop offset="0" stop-color="#cbced0"/><stop offset="1" stop-color="#84838b"/></linearGradient><linearGradient id="b" gradientTransform="matrix(.06233 0 0 .064 -.057 .13)" gradientUnits="userSpaceOnUse" x1="301.03" x2="703.07" y1="151.4" y2="553.44"><stop offset="0" stop-color="#276dc3"/><stop offset="1" stop-color="#165caa"/></linearGradient><path d="M22.7 30.914C10.163 30.914 0 24.066 0 15.617 0 7.168 10.164.32 22.7.32c12.534 0 22.698 6.848 22.698 15.297 0 8.45-10.16 15.297-22.699 15.297zm3.476-24.613c-9.531 0-17.254 4.683-17.254 10.457 0 5.777 7.723 10.46 17.254 10.46 9.527 0 16.558-3.202 16.558-10.46 0-7.254-7.03-10.457-16.558-10.457zm0 0" fill="url(#a)" fill-rule="evenodd"/><path d="M34.57 24.016s1.375.414 2.172.82c.278.144.758.426 1.102.793.34.363.504.73.504.73l5.414 9.184-8.75.004-4.094-7.73s-.836-1.45-1.352-1.872c-.43-.347-.613-.472-1.039-.472H26.45v10.07l-7.742.004V9.82h15.55s7.083.13 7.083 6.907c0 6.78-6.77 7.285-6.77 7.285zm-3.367-8.618h-4.687l-.004 4.372h4.691s2.172-.008 2.172-2.227c0-2.262-2.172-2.145-2.172-2.145zm0 0" fill="url(#b)" fill-rule="evenodd"/><path d="M-24.46 120.549a1.43 1.43 0 0 1-.97-.386l-5.364-5.43c-.517-.517-.517-1.357 0-1.94l5.43-5.43c.515-.517 1.357-.517 1.938 0 .518.517.518 1.36 0 1.94l-4.46 4.461 4.46 4.46c.518.517.518 1.356 0 1.939-.322.257-.71.386-1.035.386zm16.222 0a1.43 1.43 0 0 1-.97-.386c-.517-.517-.517-1.357 0-1.94l4.461-4.459-4.46-4.461c-.518-.517-.518-1.357 0-1.94.517-.517 1.357-.517 1.94 0l5.428 5.43c.517.517.517 1.357 0 1.94l-5.427 5.43a1.432 1.432 0 0 1-.972.386zm-10.793 2.65c-.13 0-.26 0-.452-.062-.71-.26-1.098-.971-.84-1.683l5.427-16.222c.26-.711.972-1.1 1.683-.84.71.257 1.098.969.84 1.68l-5.43 16.222c-.13.583-.71.906-1.228.906zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".97" transform="matrix(1.47916 0 0 1.48836 72.435 -120.409)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sass.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="fill-rule:nonzero;fill:#cd6799;fill-opacity:1;stroke-width:.25;stroke-linecap:butt;stroke-linejoin:miter;stroke:#cd6799;stroke-opacity:1;stroke-miterlimit:10" d="M40.764 20.63c-1.602 0-3.101.4-4.3 1-.4-.9-.9-1.601-1.001-2.198-.1-.7-.2-1.001-.1-1.8.1-.804.599-1.904.599-2.003 0-.098-.1-.498-1-.498-.998 0-1.797.099-1.797.4-.101.197-.302.799-.4 1.398-.102.9-1.903 3.901-2.903 5.502-.3-.7-.6-1.201-.7-1.6-.1-.701-.2-1.002-.1-1.8.1-.802.6-1.902.6-2.001 0-.101-.1-.498-1-.498s-1.801.098-1.801.4c-.096.3-.197.798-.398 1.4-.2.597-2.5 5.598-3.1 6.999-.302.698-.6 1.2-.7 1.598v.101c-.099.301-.3.5-.3.5-.1.2-.3.4-.4.4-.101 0-.101-.599 0-1.5.4-1.8 1.199-4.5 1.199-4.6 0-.1.1-.5-.5-.8-.7-.2-.9.101-.997.101-.103 0-.103.099-.103.099s.7-3.1-1.399-3.1c-1.3 0-3.2 1.5-4.2 2.801-.6.299-1.8.997-3.2 1.697-.499.301-1 .602-1.5.802l-.1-.101c-2.598-2.8-7.5-4.8-7.3-8.6.099-1.398.5-4.999 9.3-9.3 7.202-3.598 13-2.6 13.9-.4 1.4 3.2-3.1 9.002-10.6 9.799-2.901.303-4.3-.797-4.7-1.199-.4-.4-.4-.4-.6-.4-.2.102-.098.501 0 .701.2.6 1.202 1.6 2.699 2.101 1.4.397 4.7.698 8.702-.9 4.498-1.8 8.099-6.602 6.998-10.7-1-4.102-7.9-5.5-14.4-3.202-3.898 1.401-8.098 3.5-11.1 6.4-3.6 3.4-4.099 6.2-3.9 7.502.801 4.299 6.8 7.1 9.202 9.199-.1.1-.201.1-.302.1-1.2.6-5.7 2.901-6.897 5.5-1.3 2.9.197 4.898 1.2 5.1 3.099.898 6.198-.7 7.897-3.2 1.7-2.501 1.501-5.8.702-7.3v-.101c.3-.1.7-.4.998-.499.6-.4 1.2-.7 1.7-1-.299.8-.5 1.8-.599 3.2-.098 1.6.5 3.7 1.399 4.5.4.4.8.4 1.103.4.998 0 1.497-.801 1.998-1.8.6-1.2 1.2-2.6 1.2-2.6s-.7 3.799 1.2 3.799c.7 0 1.4-.9 1.7-1.3l.1-.099.1-.1c.299-.501.9-1.5 1.8-3.401 1.2-2.3 2.3-5.298 2.3-5.298s.1.698.4 1.899c.2.7.7 1.5 1 2.2-.3.4-.401.6-.401.6-.2.3-.398.599-.698.998-1 1.1-2.101 2.402-2.2 2.802-.1.4-.1.7.197 1 .201.2.703.2 1.202.2.8-.1 1.499-.3 1.698-.4.4-.1 1-.4 1.502-.8 1-.7 1.499-1.601 1.4-2.9 0-.701-.3-1.4-.5-2.1.1-.1.1-.2.2-.4 1.5-2.1 2.6-4.5 2.6-4.5s.1.7.4 1.9c.1.6.5 1.2.8 1.9-1.4 1.1-2.202 2.4-2.5 3.2-.5 1.501-.1 2.3.699 2.399.4.1.9-.101 1.202-.299.499-.1 1-.4 1.6-.801 1-.698 1.799-1.6 1.799-2.898 0-.602-.1-1.203-.4-1.701 1.2-.501 2.602-.7 4.5-.501 4.099.5 4.9 3.001 4.8 4.101-.1 1.1-1 1.7-1.3 1.8-.3.1-.4.2-.4.4s.1.1.4.1c.4-.1 2.1-.9 2.199-2.9.201-2.2-2.2-4.9-6.4-4.9zm-31.5 10.701c-1.3 1.5-3.203 2.099-4.002 1.5-.9-.501-.499-2.7 1.101-4.301.998-1 2.3-1.8 3.2-2.299.2-.1.5-.3.799-.5.1 0 .1-.101.1-.101.101-.101.101-.101.202-.101.6 2.2 0 4.1-1.4 5.802zm9.9-6.801c-.401 1.2-1.5 4.1-2.1 4-.501-.101-.801-2.301-.1-4.5.4-1.1 1.199-2.4 1.6-2.901.7-.8 1.499-1.1 1.799-.7.2.202-.9 3.4-1.2 4.1zm8.1 3.899c-.202.1-.403.1-.403.1l.1-.1s1-1.1 1.402-1.599c.199-.298.5-.7.8-1v.1c-.1 1.199-1.4 2.1-1.9 2.5zm6.2-1.401c-.1-.099-.1-.4.399-1.5.2-.399.7-1.1 1.4-1.8.1.301.1.503.1.802 0 1.6-1.102 2.2-1.9 2.498zm0 0" transform="matrix(1.7456 0 0 1.77926 .217 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/scss.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="fill-rule:nonzero;fill:#cd6799;fill-opacity:1;stroke-width:.25;stroke-linecap:butt;stroke-linejoin:miter;stroke:#cd6799;stroke-opacity:1;stroke-miterlimit:10" d="M40.764 20.63c-1.602 0-3.101.4-4.3 1-.4-.9-.9-1.601-1.001-2.198-.1-.7-.2-1.001-.1-1.8.1-.804.599-1.904.599-2.003 0-.098-.1-.498-1-.498-.998 0-1.797.099-1.797.4-.101.197-.302.799-.4 1.398-.102.9-1.903 3.901-2.903 5.502-.3-.7-.6-1.201-.7-1.6-.1-.701-.2-1.002-.1-1.8.1-.802.6-1.902.6-2.001 0-.101-.1-.498-1-.498s-1.801.098-1.801.4c-.096.3-.197.798-.398 1.4-.2.597-2.5 5.598-3.1 6.999-.302.698-.6 1.2-.7 1.598v.101c-.099.301-.3.5-.3.5-.1.2-.3.4-.4.4-.101 0-.101-.599 0-1.5.4-1.8 1.199-4.5 1.199-4.6 0-.1.1-.5-.5-.8-.7-.2-.9.101-.997.101-.103 0-.103.099-.103.099s.7-3.1-1.399-3.1c-1.3 0-3.2 1.5-4.2 2.801-.6.299-1.8.997-3.2 1.697-.499.301-1 .602-1.5.802l-.1-.101c-2.598-2.8-7.5-4.8-7.3-8.6.099-1.398.5-4.999 9.3-9.3 7.202-3.598 13-2.6 13.9-.4 1.4 3.2-3.1 9.002-10.6 9.799-2.901.303-4.3-.797-4.7-1.199-.4-.4-.4-.4-.6-.4-.2.102-.098.501 0 .701.2.6 1.202 1.6 2.699 2.101 1.4.397 4.7.698 8.702-.9 4.498-1.8 8.099-6.602 6.998-10.7-1-4.102-7.9-5.5-14.4-3.202-3.898 1.401-8.098 3.5-11.1 6.4-3.6 3.4-4.099 6.2-3.9 7.502.801 4.299 6.8 7.1 9.202 9.199-.1.1-.201.1-.302.1-1.2.6-5.7 2.901-6.897 5.5-1.3 2.9.197 4.898 1.2 5.1 3.099.898 6.198-.7 7.897-3.2 1.7-2.501 1.501-5.8.702-7.3v-.101c.3-.1.7-.4.998-.499.6-.4 1.2-.7 1.7-1-.299.8-.5 1.8-.599 3.2-.098 1.6.5 3.7 1.399 4.5.4.4.8.4 1.103.4.998 0 1.497-.801 1.998-1.8.6-1.2 1.2-2.6 1.2-2.6s-.7 3.799 1.2 3.799c.7 0 1.4-.9 1.7-1.3l.1-.099.1-.1c.299-.501.9-1.5 1.8-3.401 1.2-2.3 2.3-5.298 2.3-5.298s.1.698.4 1.899c.2.7.7 1.5 1 2.2-.3.4-.401.6-.401.6-.2.3-.398.599-.698.998-1 1.1-2.101 2.402-2.2 2.802-.1.4-.1.7.197 1 .201.2.703.2 1.202.2.8-.1 1.499-.3 1.698-.4.4-.1 1-.4 1.502-.8 1-.7 1.499-1.601 1.4-2.9 0-.701-.3-1.4-.5-2.1.1-.1.1-.2.2-.4 1.5-2.1 2.6-4.5 2.6-4.5s.1.7.4 1.9c.1.6.5 1.2.8 1.9-1.4 1.1-2.202 2.4-2.5 3.2-.5 1.501-.1 2.3.699 2.399.4.1.9-.101 1.202-.299.499-.1 1-.4 1.6-.801 1-.698 1.799-1.6 1.799-2.898 0-.602-.1-1.203-.4-1.701 1.2-.501 2.602-.7 4.5-.501 4.099.5 4.9 3.001 4.8 4.101-.1 1.1-1 1.7-1.3 1.8-.3.1-.4.2-.4.4s.1.1.4.1c.4-.1 2.1-.9 2.199-2.9.201-2.2-2.2-4.9-6.4-4.9zm-31.5 10.701c-1.3 1.5-3.203 2.099-4.002 1.5-.9-.501-.499-2.7 1.101-4.301.998-1 2.3-1.8 3.2-2.299.2-.1.5-.3.799-.5.1 0 .1-.101.1-.101.101-.101.101-.101.202-.101.6 2.2 0 4.1-1.4 5.802zm9.9-6.801c-.401 1.2-1.5 4.1-2.1 4-.501-.101-.801-2.301-.1-4.5.4-1.1 1.199-2.4 1.6-2.901.7-.8 1.499-1.1 1.799-.7.2.202-.9 3.4-1.2 4.1zm8.1 3.899c-.202.1-.403.1-.403.1l.1-.1s1-1.1 1.402-1.599c.199-.298.5-.7.8-1v.1c-.1 1.199-1.4 2.1-1.9 2.5zm6.2-1.401c-.1-.099-.1-.4.399-1.5.2-.399.7-1.1 1.4-1.8.1.301.1.503.1.802 0 1.6-1.102 2.2-1.9 2.498zm0 0" transform="matrix(1.7456 0 0 1.77926 .217 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sdf.svg
1
1
<svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#a03537"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sed.svg
1
1
<svg height="64" width="79" xmlns="http://www.w3.org/2000/svg"><path d="m42.852 35.445 4.695 1.344 2.851-10.418-4.695-1.344c0-1.68-.168-3.359-.672-5.039l4.192-2.52-5.364-9.405-4.359 2.519a18.036 18.036 0 0 0-4.023-3.023l1.34-4.704L26.421 0l-1.34 4.703c-1.676 0-3.352.168-5.027.672l-2.516-4.2-9.387 5.376 2.512 4.199a18.053 18.053 0 0 0-3.016 4.031l-4.695-1.343L.105 23.852l4.692 1.343c0 1.68.168 3.36.672 5.04l-4.192 2.523 5.364 9.406 4.191-2.52a18.126 18.126 0 0 0 4.023 3.024l-1.34 4.703 10.395 2.856 1.34-4.704c1.676 0 3.352-.168 5.031-.671l2.512 4.199 9.39-5.375-2.515-4.2c1.172-1.175 2.348-2.519 3.184-4.03zm-25.985-5.547c-2.68-4.535-1.004-10.414 3.52-13.101 4.527-2.688 10.394-1.008 13.078 3.527 2.683 4.535 1.004 10.414-3.52 13.106-4.527 2.687-10.394 1.175-13.078-3.532zm50.63 33.262 6.034-3.527-1.676-2.856c.84-.84 1.508-1.68 2.012-2.687l3.184.84 1.844-6.887-3.184-.84c0-1.176-.168-2.183-.504-3.36l2.852-1.679-3.52-6.047-2.852 1.68c-.84-.84-1.675-1.512-2.683-2.016l.84-3.191-6.875-1.852-.836 3.196c-1.176 0-2.18.168-3.356.504l-1.675-2.86-5.7 3.7 1.676 2.855c-.836.84-1.508 1.68-2.012 2.687l-3.183-1.007-1.844 6.886 3.184.84c0 1.176.168 2.184.504 3.36l-2.852 1.68 3.523 6.046 2.848-1.68c.84.84 1.676 1.512 2.684 2.016l-.84 3.191L61.965 64l.836-3.191c1.176 0 2.18-.168 3.355-.504-.336 0 1.34 2.855 1.34 2.855zM57.101 50.563c-1.676-3.024-.668-6.887 2.347-8.567 3.02-1.68 6.875-.672 8.551 2.352 1.676 3.023.672 6.886-2.348 8.566-3.015 1.68-6.875.672-8.55-2.352zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sh.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sit.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sitemap.svg
1
1
<svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M56.645 44.492c-.348-.512-.864-.68-1.38-.68H38.728c-.516 1.188-1.204 2.376-1.723 3.567-1.375 2.715-2.926 5.773-4.305 8.32v.168c-.863 1.528-2.41 2.38-4.133 2.38s-3.273-.852-4.136-2.38c-.516-.847-2.239-4.074-4.477-8.32-.691-1.188-1.207-2.379-1.723-3.567H9.27c-.516 0-1.204.34-1.375.852L.14 60.793a2.327 2.327 0 0 0 0 1.527c.343.512.863.68 1.379.68h46.34c.515 0 1.203-.34 1.374-.848l7.754-15.965c0-.68 0-1.187-.343-1.695zm0 0" fill="#039"/><path d="M28.39 0c-9.472 0-17.222 7.64-17.222 16.98 0 5.606 6.2 18.852 11.367 29.04 2.41 4.753 4.309 8.32 4.48 8.32.344.508.86.847 1.376.847.52 0 1.035-.34 1.379-.847 0 0 1.894-3.567 4.48-8.32 5.168-10.188 11.367-23.434 11.367-29.04C45.617 7.64 37.867 0 28.391 0zm0 27.168c-4.304 0-7.921-3.223-8.265-7.469v-.851c0-4.582 3.79-8.32 8.441-8.32 4.305 0 7.922 3.226 8.266 7.472v.848c0 4.586-3.789 8.32-8.441 8.32zm0 0" fill="#efce4a"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/skin.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sldm.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#c60" stroke="#c60" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sldx.svg
1
1
<svg height="64" width="70" xmlns="http://www.w3.org/2000/svg"><g fill="#c60" stroke="#c60" stroke-miterlimit="10"><path d="M53 47.7H19a3.116 3.116 0 0 0-3.101 3.1v21.8c0 1.7 1.4 3.1 3.101 3.1h14.2L31.8 81h-1.6c-.7 0-1.2.501-1.2 1.2s.5 1.2 1.2 1.2h11.5c.7 0 1.2-.501 1.2-1.2s-.5-1.2-1.2-1.2H40l-1.4-5.4H53c1.7 0 3.101-1.4 3.101-3.1V50.8c0-1.7-1.4-3.1-3.101-3.1zm.3 25.1H18.7V50.6h34.5zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/><path d="M27.6 63.9h2.8v5.6h-2.8zm4.7-1.8h2.801v7.4h-2.8zM36.9 60.2h2.8v9.302h-2.8zm4.6-1.8h2.8v11.102h-2.8zm-.2-4.098L36.4 56.999l-3.6-1.198-6.1 3.3.9.998 5.4-2.798 3.6 1.198 5.6-3.1.9 1.001 2.101-3.5-4.8.3zm0 0" transform="matrix(1.69903 0 0 1.72133 -26.165 -80.833)"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sln.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sol.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M30.871 11.793h2.602c.867 0 1.39-.691 1.39-1.387v-9.02C34.863.52 34.168 0 33.473 0H30.87c-.867 0-1.387.695-1.387 1.387v8.847c-.172.868.52 1.559 1.387 1.559zm21.336 18.906v2.602c0 .867.691 1.386 1.387 1.386h9.02c.866 0 1.386-.69 1.386-1.386v-2.602c0-.867-.695-1.387-1.387-1.387h-9.02c-.695-.175-1.386.52-1.386 1.387zM33.301 64c.867 0 1.386-.695 1.386-1.387v-9.02c0-.866-.69-1.386-1.386-1.386h-2.602c-.867 0-1.387.691-1.387 1.387v9.02c0 .866.692 1.386 1.387 1.386zM1.387 34.687h9.02c.866 0 1.386-.69 1.386-1.386v-2.602c0-.867-.691-1.387-1.387-1.387h-9.02C.52 29.313 0 30.005 0 30.7v2.602c0 .695.695 1.386 1.387 1.386zM47.176 18.56c.52.52 1.562.52 2.082 0l6.418-6.418c.52-.52.52-1.563 0-2.082L53.94 8.324c-.52-.52-1.562-.52-2.082 0l-6.418 6.418c-.52.52-.52 1.563 0 2.082zM53.94 55.5l1.735-1.734c.52-.52.52-1.559 0-2.078l-6.418-6.418c-.52-.524-1.563-.524-2.082 0l-1.735 1.734c-.52.52-.52 1.559 0 2.082L51.86 55.5c.52.523 1.387.523 2.082 0zm-41.629 0 6.418-6.414c.524-.523.524-1.563 0-2.082l-1.734-1.734c-.52-.524-1.558-.524-2.082 0L8.5 51.687c-.523.52-.523 1.56 0 2.079l1.734 1.734c.692.523 1.559.523 2.079 0zm2.602-36.941c.523.52 1.563.52 2.082 0l1.734-1.735c.524-.52.524-1.562 0-2.082l-6.418-6.418c-.519-.52-1.558-.52-2.078 0L8.5 10.06c-.523.52-.523 1.562 0 2.082zm0 0" fill="#fea500"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sphinx.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M30.906 0h3.746v2.496h-3.746zm31.532 26.066c.156-2.027 0-4.058-.465-5.93h-8.899v-3.745h7.18a15.572 15.572 0 0 0-2.031-2.496l-8.895-8.899-1.406 14.047c-.313 3.59-2.027 6.871-4.527 9.367v6.242h4.996c2.808 0 5.308 1.094 7.18 2.657 2.808-1.72 4.84-4.371 6.09-7.336h-8.587v-3.75h9.364zM34.651 2.652V7.18h-3.746V2.652H19.98l1.563 15.922c.312 2.813 1.559 5.465 3.746 7.34 2.027 1.871 4.836 2.965 7.648 2.965 2.81 0 5.461-1.094 7.649-2.965 2.027-1.875 3.434-4.527 3.746-7.34l1.563-15.922zm5.153 12.176h-4.996v6.09h-3.746v-6.09h-4.997v-3.746h13.739zm0 0"/><path d="M48.234 38.242h-8.742v-7.336c-2.027 1.094-4.37 1.563-6.71 1.563-2.344 0-4.684-.625-6.716-1.563v7.336h-8.738a7.33 7.33 0 0 0-7.336 7.34v4.68c5.774.937 10.145 5.933 10.145 11.863V64h23.57v-1.875c0-6.555 5.309-11.863 11.863-12.02v-4.523a7.33 7.33 0 0 0-7.336-7.34zm0 0"/><path d="M55.727 53.855c-4.528 0-8.274 3.747-8.274 8.27V64h6.402v-4.996h3.747V64H64v-1.875c0-4.68-3.746-8.27-8.273-8.27zm-37.93-34.968L16.39 4.84l-8.899 8.898a15.342 15.342 0 0 0-2.027 2.496h7.18v3.746H3.746c-.625 1.875-.781 3.903-.469 5.934h9.524v3.746H4.215c1.094 2.965 3.277 5.617 6.086 7.336 1.875-1.719 4.527-2.656 7.183-2.656h4.993v-6.242a13.914 13.914 0 0 1-4.68-9.211zM8.273 53.855C3.746 53.855 0 57.602 0 62.125V64h6.398v-4.996h3.747V64h6.402v-1.875c0-4.68-3.746-8.27-8.274-8.27zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sql.svg
1
1
<svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#a03537"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sqlite.svg
1
1
<svg height="64" width="51" xmlns="http://www.w3.org/2000/svg"><path d="M23.023 63.957c-8.199-.34-15.543-2.875-19.468-6.77-1.196-1.011-2.39-2.535-2.903-3.55L.31 52.96v-7.617c0-7.614 0-7.614.171-6.934.34 1.692 1.368 3.383 2.903 4.735 1.023.847 3.074 2.37 4.781 3.214 2.906 1.524 6.66 2.54 10.59 3.047 2.39.34 3.246.34 6.66.34 3.418 0 4.27 0 6.66-.34 3.93-.508 7.516-1.691 10.59-3.047 1.707-.843 3.758-2.199 4.781-3.214 1.368-1.352 2.563-3.043 2.903-4.735.172-.508.172-.508.172 6.934v7.445l-.34.68c-1.196 2.367-3.246 4.398-5.98 6.09-5.294 3.046-13.321 4.738-21.177 4.398zm0-18.95c-7.171-.339-13.832-2.37-18.101-5.413-1.027-.68-2.39-2.032-2.906-2.707-.512-.68-1.024-1.524-1.364-2.371L.31 33.84v-7.445c0-7.446 0-7.446.171-6.938.34 1.184.852 2.54 1.88 3.555.511.675 1.367 1.523 1.878 1.86.168.171.684.339 1.024.679 3.414 2.367 8.199 4.058 13.664 4.906 2.39.336 3.242.336 6.66.336 3.414 0 4.27 0 6.66-.336 3.93-.508 7.516-1.691 10.59-3.047 1.707-.847 3.758-2.2 4.781-3.215 1.367-1.351 2.39-3.047 2.903-4.738.171-.508.171-.508.171 6.938v7.445l-.511 1.015c-.856 1.524-1.368 2.368-2.39 3.383-1.028 1.016-2.052 1.864-3.419 2.54-5.465 3.046-13.492 4.738-21.348 4.23zm-.511-18.78c-4.782-.34-8.54-1.184-12.125-2.54-4.27-1.69-7.344-3.89-8.883-6.597a5.594 5.594 0 0 1-.852-2.031C.48 14.383.31 12.69.48 11.676 1.504 6.262 8.848 1.859 18.754.34 21.144 0 22 0 25.414 0c3.418 0 4.27 0 6.66.34 3.93.508 7.516 1.691 10.59 3.043 4.441 2.199 7.172 5.078 7.684 8.12.172.849.172 2.708-.168 3.388-.512 1.691-1.196 2.707-2.563 4.058-3.586 3.723-9.906 6.094-17.762 6.938-1.023.34-6.32.34-7.343.34zm0 0" fill="#369"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/step.svg
1
1
<svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/stl.svg
1
1
<svg height="63" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M33.325 19.55c-.498-.2-1.1 0-1.299.5-1.1 2.5-2.901 4.7-5.1 6.4l-6.7-13.601c1-.8 1.6-1.999 1.6-3.4 0-2.099-1.501-3.899-3.501-4.3v-3.4a1 1 0 0 0-2 0v3.4c-2 .401-3.5 2.201-3.5 4.3 0 1.401.6 2.6 1.601 3.4l-6.7 13.602c-2.201-1.7-4-3.801-5.1-6.401-.201-.5-.8-.7-1.301-.5-.499.199-.7.8-.499 1.3 1.299 3 3.4 5.4 6 7.3l-4 8c-.2.5 0 1.1.4 1.3.098 0 .3.1.4.1.3 0 .7-.2.9-.5l3.8-7.8c2.7 1.5 5.6 2.2 8.7 2.2 3.1 0 6-.8 8.699-2.2l3.8 7.8c.1.3.501.5.9.5.1 0 .3 0 .4-.1.5-.2.7-.8.4-1.3l-3.9-8c2.6-1.8 4.701-4.4 6-7.3.6-.5.401-1.101 0-1.3zM17.326 6.95c1.4 0 2.5 1.1 2.5 2.499 0 1.401-1.1 2.502-2.5 2.502s-2.5-1.1-2.5-2.502c0-1.4 1.199-2.5 2.5-2.5zm0 22.6c-2.8 0-5.4-.7-7.801-2l6.8-13.7c.3.1.701.1 1.1.1.402 0 .701 0 1.1-.1l6.8 13.7c-2.5 1.3-5.199 2-7.999 2zm0 0" fill="#369" stroke="#369" stroke-miterlimit="10" stroke-width="1.5" transform="matrix(1.6544 0 0 1.63607 0 .154)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/svg.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M16.223 21.805.09 55.844l3.012 3.015 20.035-20.035c-.711-1.594-.532-3.543.886-4.96 1.774-1.774 4.43-1.774 6.204 0 1.773 1.769 1.773 4.429 0 6.202-1.243 1.243-3.368 1.594-4.965.887L5.23 60.984 8.242 64l34.04-16.133L49.73 27.48 36.61 14.36zm46.625-4.075L46.184 1.062c-1.418-1.417-3.547-1.417-4.965 0L37.32 4.966c-1.422 1.418-1.422 3.543 0 4.965l16.664 16.664c1.418 1.418 3.543 1.418 4.965 0l3.899-3.903c1.418-1.418 1.418-3.543 0-4.96zm0 0" fill="#fea500"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/swd.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M46.168 13.516c1.793-.711 3.766-.891 5.738-.891V.008c-8.605-.18-16.851 3.554-22.23 10.308-2.153 2.844-4.125 5.864-5.559 9.243l-4.12 10.128c-1.079 3.024-2.333 6.223-3.767 9.067a31.916 999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/swf.svg
1
1
<svg height="64" width="52" xmlns="http://www.w3.org/2000/svg"><path d="M46.168 13.516c1.793-.711 3.766-.891 5.738-.891V.008c-8.605-.18-16.851 3.554-22.23 10.308-2.153 2.844-4.125 5.864-5.559 9.243l-4.12 10.128c-1.079 3.024-2.333 6.223-3.767 9.067a31.916 31.916 0 0 1-3.945 6.754c-1.254 1.777-3.047 3.199-5.02 4.09-2.152 1.066-4.66 1.597-7.171 1.597v12.797c8.605.18 16.851-3.554 22.23-10.308 1.613-2.309 3.227-4.797 4.485-7.286l3.406-8h14.879v-12.62h-9.86c.715-1.954 1.793-3.731 3.047-5.508.895-1.602 2.153-2.844 3.407-3.91 1.613-1.422 3.046-2.313 4.48-2.844zm0 0" fill="#d10407"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/swift.svg
1
1
<svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><path d="M65.75 44.957S76.094 21.352 44.36.051c0 0 12.972 16.152 6.312 34.433 0 0-23.32-16.328-35.067-28.222 0 0 14.727 20.945 19.989 25.207 0 0-8.77-4.438-28.93-21.657 0 0 23.316 30.176 34.188 36.387 0 0-16.657 11.184-40.852-4.613 0 0 12.8 22.363 39.625 22.363 12.098 0 15.605-6.21 21.566-6.21 6.137 0 9.993 6.21 9.993 6.21 3.507-8.875-5.434-18.992-5.434-18.992zm0 0" fill="#fa2a22"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/swp.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/sys.svg
1
1
<svg height="63" width="60" xmlns="http://www.w3.org/2000/svg"><path d="M57.96 53.09 37.532 32.832l-.527-.523 3.7-3.844.526-.524c4.754 1.747 10.391.875 14.266-2.968 2.816-2.793 4.227-6.637 3.875-10.653 0-.523-.355-.875-.707-1.047-.352-.175-.879 0-1.23.348l-6.516 6.461-6.871-1.57-1.758-6.813 6.516-6.46c.351-.348.351-.872.351-1.223-.176-.348-.527-.7-1.058-.7-4.051-.347-7.922 1.051-10.743 3.844-3.87 3.844-4.93 9.43-2.992 14.145l-.527.523-5.285 5.067-9.86-9.782-.355-.347c.176-.176.176-.352.355-.7 2.114.348 5.809-3.668 8.98-6.812L18.337 0c-4.227 4.191-7.219 6.984-6.867 8.906-.88.524-1.762 1.047-2.465 1.57L7.77 11.7c-.88.875-1.407 1.922-1.582 2.969-.176.176-.176.352-.352.523l-.531 1.051v.172l-.528.7c-.351.35-.527.698-.703 1.222a.378.378 0 0 1-.351.352l-.18.171c-.176.524-.527.875-.703 1.399-.176.523-.527 1.222-.703 1.922v.347c0 .176-.176.524-.176.7l-.176.875c-.176.523-.176 1.046-.176 1.57v3.144l.176.696v.351c0 .348.176.524.176.871l.527 1.575c.176.523.703.87 1.235.87.351 0 .527-.171.703-.347s.351-.352.351-.7l.176-1.745c0-.176 0-.348.176-.524 0-.175 0-.351.176-.351l.175-.696s0-.175.176-.175c0 0 0-.176.18-.176 0-.172.176-.348.176-.348.175-.351.175-.523.351-.699 0-.176.176-.176.176-.348.176-.351.352-.527.527-.875l.352-.523c0-.176.176-.176.352-.352l.18-.172c.175-.351.527-.523.878-.875l.176-.171c.176-.176.527-.352.703-.528.176 0 .176-.172.355-.172.176 0 .176-.175.352-.175.176-.176.352-.348.703-.348l.528-.352.53-.175c.177-.172.352-.172.528-.172s.176 0 .352-.176c0 0 .175 0 .175-.176.176 0 .176 0 .352-.172h.176c.176.172.351.524.531.696l9.684 9.605L2.488 51.52c-2.64 2.617-2.992 6.285-.351 8.906 2.64 2.617 6.164 1.918 8.629-.7l18.136-19.035.356.348 20.426 20.258a5.883 5.883 0 0 0 8.277 0 5.763 5.763 0 0 0 0-8.207zM7.95 57.629c-.884.875-2.47.875-3.348 0-.88-.871-.88-2.445 0-3.316.878-.876 2.464-.876 3.347 0a2.134 2.134 0 0 1 0 3.316zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tar.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#4d1b9b"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tax.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M12.726.224c-2.801 0-5.202 2.302-5.202 5.2 0 2.8 2.302 5.2 5.202 5.2.9 0 1.698-.199 2.4-.599l2.099 2.101-6 6c-.999.999-.999 2.7 0 3.698l.2.202L23.524 9.925c.702.4 1.602.601 2.4.601 2.801 0 5.202-2.302 5.202-5.2 0-2.8-2.302-5.2-5.202-5.2-2.798 0-5.199 2.3-5.199 5.2 0 .898.2 1.698.601 2.4l-2.101 2.098-2.1-2.098c.399-.702.6-1.6.6-2.4.2-2.8-2.2-5.102-5-5.102zm0 3.001c1.199 0 2.2 1 2.2 2.2 0 1.2-1.001 2.2-2.2 2.2a2.22 2.22 0 0 1-2.2-2.2c0-1.3.998-2.2 2.2-2.2zm13.3 0c1.199 0 2.2 1 2.2 2.2 0 1.2-1.001 2.2-2.2 2.2a2.22 2.22 0 0 1-2.2-2.2c0-1.3.998-2.2 2.2-2.2zm-22.902 10.3c-.198 0-.398 0-.598.1-.2 0-.4.101-.601.2-.2.1-.3.2-.5.301-.1.098-.301.199-.4.3-.099.1-.2.299-.3.4-.1.1-.2.299-.3.5-.1.199-.1.3-.2.498v.1c0 .202-.1.4-.1.602v17.8c0 .198 0 .4.1.598 0 .201.1.4.2.601.1.2.2.3.3.501.1.098.201.3.3.4.099.098.3.199.4.3.1.1.3.198.5.299.2.1.299.1.5.201h.1c.2 0 .401.098.599.098h32.602c.198 0 .398 0 .598-.098.2 0 .4-.1.601-.201.2-.1.3-.199.5-.3.1-.1.301-.2.4-.299.099-.1.2-.302.3-.4.1-.1.2-.302.3-.5.1-.202.1-.3.2-.501v-.1c0-.2.1-.4.1-.6V16.526c0-.201 0-.4-.1-.601 0-.199-.1-.4-.2-.599-.1-.201-.2-.302-.3-.5-.1-.101-.201-.3-.3-.4-.099-.101-.3-.202-.4-.3-.1-.1-.3-.201-.5-.302-.2-.098-.299-.098-.5-.199h-.1c-.2 0-.401-.1-.599-.1h-11.9l-3.002 3.001h11.802c0 1.6 1.298 2.999 2.999 2.999v11.8c-1.6 0-2.999 1.3-2.999 3h-26.6c0-1.6-1.3-3-3.001-3v-11.9c1.6 0 3.001-1.299 3.001-3h3.4l2.998-3zm16.301 5.9c-3.3 0-5.9 3-5.9 6.699 0 2.1.899 4 2.2 5.2h7.3c1.401-1.2 2.2-3.1 2.2-5.2.1-3.698-2.601-6.7-5.8-6.7zm-11.9 4.5c-.8 0-1.499.7-1.499 1.5s.7 1.499 1.498 1.499c.801 0 1.5-.7 1.5-1.5s-.699-1.499-1.5-1.499zm23.7 0c-.8 0-1.501.7-1.501 1.5s.702 1.499 1.5 1.499c.801 0 1.501-.7 1.501-1.5s-.7-1.499-1.5-1.499zm0 0" fill="#83ad51" stroke="#83ad51" stroke-miterlimit="10" stroke-width=".25" transform="matrix(1.6973 0 0 1.70894 .53 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tcsh.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tfignore.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tga.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tgz.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tiff.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tmp.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><g fill="#bababa"><path d="M51.227 30.453c.687.688 1.89.688 2.574-.172.687-.691.687-1.722.172-2.582l-4.805-5.676a1.33 1.33 0 0 1-.34-.863l-.687-10.664c0-1.035-.86-1.723-1.887-1.723-1.031 0-1.719.688-1.887 1.723v.172l-.687 11.7v.171c0 .688.172 1.375.855 1.89l.344.344zm0 0"/><path d="M46.426 0C36.645 0 28.41 6.367 25.32 15.14c1.887.172 3.774.688 5.664 1.204 2.403-6.192 8.407-10.668 15.442-10.668 9.094 0 16.469 7.398 16.469 16.52 0 8.257-6.004 15.136-13.899 16.343.172 1.031.172 2.234.172 3.266 0 .863 0 1.722-.172 2.582 11.152-1.203 19.734-10.668 19.734-22.02C68.73 10.152 58.777 0 46.426 0zm0 0"/><path d="M42.648 38.71h-2.914c-.515-2.41-1.375-4.644-2.746-6.71l2.059-2.066c.687-.688.687-1.891 0-2.579l-2.059-2.066c-.687-.687-1.886-.687-2.574 0l-2.059 2.066c-2.058-1.378-4.289-2.41-6.69-2.753v-2.926c0-1.031-.86-1.89-1.888-1.89H20.86c-1.027 0-1.886.859-1.886 1.89v2.926c-2.403.515-4.633 1.375-6.692 2.753l-1.886-2.238c-.688-.687-1.887-.687-2.575 0l-2.058 2.067c-.688.687-.688 1.89 0 2.578l1.886 2.066c-1.37 2.063-2.402 4.3-2.746 6.711H2.16c-1.031 0-1.89.86-1.89 1.89v2.926c0 1.032.859 1.891 1.89 1.891h2.742c.516 2.41 1.375 4.645 2.746 6.711l-1.886 1.89c-.688.692-.688 1.895 0 2.583l2.058 2.066c.688.688 1.887.688 2.575 0l2.058-1.894c2.059 1.375 4.29 2.41 6.692 2.753v2.754c0 1.032.859 1.891 1.886 1.891h2.918c1.028 0 1.887-.86 1.887-1.89v-2.755c2.402-.515 4.633-1.378 6.691-2.753l1.887 2.066c.688.687 1.887.687 2.574 0l2.059-2.066c.687-.688.687-1.891 0-2.579l-2.059-2.066c1.371-2.066 2.403-4.3 2.746-6.71h2.914c1.032 0 1.887-.86 1.887-1.892V40.43c0-.86-.855-1.72-1.887-1.72zM29.094 48.86c-3.774 3.785-9.95 3.785-13.723 0-3.777-3.786-3.777-9.977 0-13.762 3.774-3.785 9.95-3.785 13.723 0 3.949 3.785 3.777 9.976 0 13.761zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tmx.svg
1
1
<svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M38.027 37.414c-5.011-4.812-9.425-9.223-12.03-19.25H43.64v-7.219H26.195V1.121h-7.617v10.024H.93v7.421h18.047s-.2 1.403-.399 2.606C15.968 30.996 13.164 37.215.93 43.23l2.61 7.418c11.429-6.015 17.444-13.835 20.05-22.257 2.605 6.418 6.816 11.629 11.629 16.441zM61.29 13.352H51.262L33.617 62.879h7.617l5.016-14.836H66.3l5.013 14.836h7.62zm-12.434 27.27 7.622-19.65 7.617 19.852zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width="1.5039150000000001"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/torrent.svg
1
1
<svg height="64" width="58" xmlns="http://www.w3.org/2000/svg"><path d="M57.938 50.598c-.325 1.437-1.29 2.394-2.415 3.511-14.785 15.325-40.34 12.45-51.109-5.906-2.89-4.95-4.5-10.055-4.34-15.8.324-7.184 2.735-13.407 7.235-18.997C12.613 6.703 19.363 2.234 28.043.796 30.129.48 32.219.32 34.309 0v5.906l-4.34.477C17.754 8.14 7.469 18.355 6.664 30.168c-.48 7.34 1.45 14.047 6.43 19.633 1.93 2.394 4.18 4.469 6.91 5.746.805.32 1.77.476 2.574.637-7.715-4.47-12.215-11.172-13.664-19.793-.965-5.746.16-11.332 3.375-16.278C20.49 7.5 34.793 5.426 44.594 9.578c-.801 1.754-1.606 3.512-2.41 5.106-1.926-.317-3.856-.957-5.786-.957-5.625-.32-10.605.957-14.785 4.949-10.61 9.734-7.875 24.738 2.41 31.445 3.215 2.234 6.75 3.828 10.606 4.625.965.32 1.93 0 3.055-.156-.16-.16-.32-.16-.48-.16-4.825-.957-9.325-2.555-13.18-5.907-3.86-3.351-6.75-7.503-7.235-12.77-.48-7.503 2.414-13.566 8.84-17.398 5.625-3.511 11.574-3.511 17.52-.636 3.374 1.593 5.785 4.148 7.714 7.34-1.765.957-3.375 1.757-4.98 2.554-1.45-1.437-2.735-3.031-4.34-3.992-7.555-5.105-18.164-.316-18.965 9.102-.324 4.629 1.606 8.14 4.82 11.332 3.856 3.511 8.52 4.789 13.66 5.425 4.985.641 9.965.48 14.95-.16.965-.16 1.445.48 1.93 1.278 0-.16 0 0 0 0zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tpl.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#999"><path d="M60.89 0H10.974C10.238 0 9.69.547 9.69 1.281v3.84c0 .73.73 1.277 1.282 1.277h46.629v46.813c0 .55.73 1.281 1.277 1.281h3.84A1.25 1.25 0 0 0 64 53.211V3.109C64 1.281 62.719 0 60.89 0zm0 0"/><path d="M49.922 12.8H1.282C.546 12.8 0 13.349 0 14.079v48.64C0 63.27.73 64 1.281 64h48.64c.548 0 1.278-.547 1.278-1.281v-48.64c0-.731-.547-1.278-1.277-1.278zm-27.43 43.52c0 .547-.73 1.282-1.281 1.282H7.863c-.73 0-1.281-.551-1.281-1.282v-3.84c0-.55.73-1.28 1.281-1.28h13.164c.735 0 1.282.55 1.282 1.28v3.84zm0-12.8c0 .55-.73 1.28-1.281 1.28H7.863c-.73 0-1.281-.55-1.281-1.28v-3.84c0-.73.73-1.282 1.281-1.282h13.164c.735 0 1.282.551 1.282 1.282v3.84zm22.309 12.8c0 .547-.551 1.282-1.281 1.282H30.172c-.73 0-1.281-.551-1.281-1.282v-3.84c0-.55.55-1.28 1.28-1.28H43.52c.55 0 1.28.55 1.28 1.28zm0-12.8c0 .55-.551 1.28-1.281 1.28H30.172c-.73 0-1.281-.55-1.281-1.28v-3.84c0-.73.55-1.282 1.28-1.282H43.52c.55 0 1.28.551 1.28 1.282zm0-12.801c0 .55-.551 1.281-1.281 1.281H7.68a1.25 1.25 0 0 1-1.282-1.281V20.48c0-.73.735-1.28 1.282-1.28h35.84c.73 0 1.28.55 1.28 1.28zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ts.svg
1
1
<svg height="64" width="37" xmlns="http://www.w3.org/2000/svg"><path d="M21.914 13.86V0h-9.039c-.187.547-.371 1.094-.371 1.824-.184.363-.184.547-.184.91-.922 5.106-3.691 8.754-8.3 10.758-1.293.547-2.582.73-3.875.547v11.125h6.64c.184 15.68.184 23.883.184 24.25v.91c.926 6.93 4.43 10.942 10.886 12.582 2.583.73 5.348 1.094 8.301 1.094 3.688-.184 7.196-.73 10.7-1.824v-13.13a101.367 101.367 0 0 0-5.536 1.645c-3.136.91-5.902.364-8.117-1.824-.183-.367-.55-.73-.55-1.094a23.898 23.898 0 0 1-.555-5.105V25.164h14.386V14.04h-14.57zm0 0" fill="#4065aa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/tsv.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="52"><path style="fill:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke:#1f7244;stroke-opacity:1;stroke-miterlimit:10" d="M0 1.5h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 7.4h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 13.3h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 19.2h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44M0 25.1h8.5m3.3 0h8.5m3.4 0h8.5m3.3 0H44" transform="matrix(1.9091 0 0 1.92593 0 .385)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/ttf.svg
1
1
<svg height="64" width="65" xmlns="http://www.w3.org/2000/svg"><path d="M43.223.492V13.66h-1.32c-.825-3-1.65-5.168-2.473-6.5-.825-1.332-2.145-2.336-3.797-3.168-.824-.5-2.473-.668-4.781-.668h-3.63v37.512c0 2.5.165 4 .329 4.668.328.668.824 1.168 1.648 1.668s1.817.664 3.301.664h1.648v1.336H9.074v-1.336h1.649c1.32 0 2.476-.332 3.3-.832.66-.332 1.153-.832 1.485-1.668.328-.5.328-2 .328-4.5V3.324h-3.461c-3.3 0-5.61.668-7.098 2.004-1.976 2-3.297 4.664-3.957 8.332H0V.492zm0 0" fill="#7291a1"/><path d="M65 14.828V28h-1.32c-.825-3-1.649-5.168-2.473-6.504-.828-1.332-2.145-2.332-3.797-3.168-.824-.5-2.472-.664-4.785-.664h-3.629v37.508c0 2.5.168 4.004.332 4.668.328.668.824 1.168 1.649 1.668.824.5 1.816.668 3.3.668h1.649v1.332H30.684v-1.332h1.652c1.32 0 2.473-.336 3.297-.836.66-.332 1.156-.832 1.488-1.664.328-.5.328-2.004.328-4.504V17.664h-3.465c-3.3 0-5.609.664-7.093 2-1.98 2-3.301 4.668-3.961 8.336h-1.317V14.828zm0 0" fill="#36454d"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/twig.svg
1
1
<svg height="64" width="72" xmlns="http://www.w3.org/2000/svg"><path d="M66.387 64c-4.18-36.844 4.875-26.973 4.875-26.973-9.926-11.988-14.973.528-17.414 12.164C52.628 30.504 46.883-.523 23.898.008c12.364 0 13.059 25.383 11.84 43.719-10.797-19.391-35-17.98-35-17.98s18.11-.708 18.11 38.077h47.539zm0 0" fill="#7faf4a"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/txt.svg
1
1
<svg height="64" width="76" xmlns="http://www.w3.org/2000/svg"><path d="M.176 52.977h75.648V64H.176zm0-26.309h75.648v11.02H.176zM.176 0h75.648v11.023H.176zm0 0" fill="#666"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/udf.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vb.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M21.344 36.297c-1.871 1.527-3.739 2.887-5.61 4.246L4.52 49.211c-.512.34-.852.508-1.36.168A15.884 15.884 0 0 0 .781 48.19c-.511-.171-.68-.511-.68-1.02V17.267c0-.34.34-.852.508-1.02.852d5006e"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vbs.svg
1
1
<svg height="64" width="80" xmlns="http://www.w3.org/2000/svg"><path d="M3.121 5.453v2.363L.227 8.91V6.547zm0 23.637v2.363L.227 32.727v-2.364zm0 24v2.547l-2.894 1.09v-2.364zm-.363-39.817v2.18L.406 16.547v-2.184zm0 8v2.18L.406 24.547v-2.184zm0 16v2.18L.406 40.547v-2.184zm0 8v2.18L.406 48.547v-2.184zm7.414-38.546v2.91L6.375 11.09V8.363zm0 24v2.726L6.375 34.91v-2.726zm0 24.183v2.727l-3.797 1.636v-2.726zm-.184-39.82v2.547l-3.07 1.273v-2.547zm0 8v2.547l-3.07 1.273v-2.726zm0 16v2.547l-3.07 1.273v-2.726zm0 8v2.547l-3.07 1.273v-2.547zm7.414-38.543V12l-5.062 2v-3.273zm0 24V36l-5.062 2v-3.273zm0 23.816v3.453l-5.062 2v-3.453zm-.18-39.453V20l-4.16 1.637v-2.91zm0 8v2.906l-4.16 1.637v-2.906zm0 16v2.906l-4.16 1.637v-2.906zm0 7.637v3.09l-4.16 1.636v-2.91zm8.133-40.184v4.547l-6.144 2.543V11.09zm0 24v4.547l-6.144 2.363v-4.546zm0 23.82v4.544l-6.144 2.546V58.91zm-.359-39.456v4.183l-5.242 2.18v-4.18zm0 8v4l-5.242 2.183v-4.183zm0 16v4.183l-5.242 2v-4.183zm0 7.636v4.184l-5.242 2v-4zm8.496-40.726v5.816l-6.87 2.73v-5.82zm0 24v5.816l-6.87 2.73v-5.82zm0 23.816v5.82L26.622 64v-5.816zm-.363-39.09v4.91l-5.785 2.364v-4.91zm0 7.637v4.91l-5.785 2.363v-4.91zm0 16.363v4.91l-5.965 2.18v-4.906zm0 7.637v4.91l-5.785 2.363v-4.91zm8.68-43.273v8l-7.414 3.09V8.362c2.53-1.636 5.062-2.726 7.414-3.636zm0 8.726v6.91l-7.414 3.09v-6.906zm0 7.82v6.91l-7.414 3.09v-6.91zm0 7.817V36l-7.414 3.09v-6.906zm0 7.82V44l-7.414 3.09V40zm0 8v6.906l-7.414 3.274V48zm0 7.817v7.457c-2.895 1.09-5.426 2.18-7.414 3.27V56zM79.773 4.91v56c-4.699-3.094-10.668-4.726-17.535-4.726-5.785 0-12.297 1.27-19.527 3.632v-7.632c3.797-1.457 7.957-2.547 12.656-3.274V30.727c-3.797.546-8.137 1.82-12.656 4v-5.274a48.751 48.751 0 0 1 12.656-3.816V7.817C51.391 8.546 47.051 10 42.711 12V4.184C49.039 1.454 55.367 0 61.51 0c6.512.184 12.657 1.816 18.262 4.91zM72.36 9.816c-3.07-1.632-6.687-2.363-10.847-2.363h-1.446v18.184h1.63c3.613 0 7.23.547 10.663 1.816zm0 22.73c-3.254-1.456-6.867-2.183-10.664-2.183h-1.629v18.184h1.63c3.976 0 7.41.543 10.663 1.453zm4.883 30.727V62H76.7v-.184h1.266V62h-.543v1.273zm.903 0v-1.457h.363l.543 1.094.543-1.094h.18v1.457h-.18V62l-.543 1.09h-.184L78.328 62v1.273zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vcd.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><g fill="#eab41b"><path d="M28.023 32c0 1.04.344 2.074 1.211 2.766 1.555 1.558 4.149 1.558 5.707 0 .692-.692 1.211-1.727 1.211-2.766s-.347-2.074-1.21-2.766c-.692-.695-1.731-1.21-2.77-1.21-1.035 0-2.074.343-2.766 1.21-1.039.692-1.383 1.727-1.383 2.766zm0 0"/><path d="M9.34 9.34c-12.453 12.453-12.453 32.691 0 45.32 12.453 12.453 32.691 12.453 45.32 0 12.453-12.453 12.453-32.691 0-45.32-12.453-12.453-32.867-12.453-45.32 0zm47.394 36.152c-1.21 2.074-2.765 4.153-4.496 5.88-1.73 1.73-3.804 3.288-5.883 4.5l-7.437-14.184s.691-.176 2.078-1.56c1.383-1.382 1.727-2.073 1.727-2.073zM37.707 26.293c1.559 1.555 2.422 3.633 2.422 5.707s-.863 4.152-2.422 5.707a7.933 7.933 0 0 1-11.242 0c-1.559-1.555-2.422-3.633-2.422-5.707s.691-4.152 2.422-5.707c2.941-3.113 8.129-3.113 11.242 0zm-10.895-5.535s-1.558.863-2.769 2.246c-1.211 1.387-1.211 1.558-1.73 2.25l-14.184-7.61c1.21-2.078 2.77-4.152 4.5-5.882 1.902-1.73 3.805-3.285 5.879-4.496zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vcf.svg
1
1
<svg height="64" width="71" xmlns="http://www.w3.org/2000/svg"><path d="M42.602 10.79V7.132C42.602 3.293 39.324 0 35.5 0s-7.102 3.293-7.102 7.133v3.656H.184V64h70.632V10.79zm21.117 46.077H7.28V17.738h21.117v3.473h14.204v-3.473h21.117zm0 0"/><path d="M24.941 32c0 2.02-1.628 3.656-3.64 3.656S17.66 34.02 17.66 32s1.628-3.656 3.64-3.656S24.94 29.98 24.94 32zM21.3 39.133c-3.823 0-7.1 3.289-7.1 7.129v3.66h14.198v-3.66c0-4.024-3.093-7.13-7.097-7.13zm17.84-10.79H56.8v7.13H39.14zm0 14.446H56.8v7.133H39.14zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vcs.svg
1
1
<svg height="64" width="71" xmlns="http://www.w3.org/2000/svg"><path d="M42.602 10.79V7.132C42.602 3.293 39.324 0 35.5 0s-7.102 3.293-7.102 7.133v3.656H.184V64h70.632V10.79zm21.117 46.077H7.28V17.738h21.117v3.473h14.204v-3.473h21.117zm0 0"/><path d="M24.941 32c0 2.02-1.628 3.656-3.64 3.656S17.66 34.02 17.66 32s1.628-3.656 3.64-3.656S24.94 29.98 24.94 32zM21.3 39.133c-3.823 0-7.1 3.289-7.1 7.129v3.66h14.198v-3.66c0-4.024-3.093-7.13-7.097-7.13zm17.84-10.79H56.8v7.13H39.14zm0 14.446H56.8v7.133H39.14zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vdi.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#4d1b9b"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vmdk.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#4d1b9b"><path d="M73.734 51.555c0-2.844-2.289-5.157-5.109-5.157H5.375c-2.82 0-5.11 2.313-5.11 5.157v7.289c0 2.843 2.29 5.156 5.11 5.156h63.25c2.82 0 5.11-2.313 5.11-5.156zm-27.308 6.757a2.985 2.985 0 0 1-2.996-3.023 2.985 2.985 0 0 1 2.996-3.023 2.985 2.985 0 0 1 2.996 3.023c0 1.777-1.234 3.023-2.996 3.023zm8.984 0a2.984 2.984 0 0 1-2.992-3.023c0-1.777 1.23-3.023 2.992-3.023a2.985 2.985 0 0 1 2.996 3.023 2.985 2.985 0 0 1-2.996 3.023zm8.813 0a2.985 2.985 0 0 1-2.996-3.023c0-1.777 1.234-3.023 2.996-3.023a2.981 2.981 0 0 1 2.992 3.023 2.981 2.981 0 0 1-2.992 3.023zM5.375 43.38h63.25c1.41 0 2.82.355 3.879 1.066l-6.168-12.98c-1.762-3.73-4.582-5.153-7.398-5.153h-6.876L42.2 36.623c-.707.71-1.586 1.245-2.469 1.6-.878.356-1.937.532-2.82.532-1.055 0-1.937-.176-2.816-.531h-.352c-.707-.356-1.41-.891-2.117-1.422l-9.867-10.668h-6.871c-2.817 0-5.461 1.601-7.399 5.156L1.32 44.266c1.235-.532 2.47-.887 4.055-.887zm0 0"/><path d="M51.71 21.332c.352-.355.532-.71.884-1.242.176-.535.351-.89.351-1.602 0-.531-.175-1.066-.351-1.422-.176-.53-.532-.886-.883-1.246a5.273 5.273 0 0 0-1.23-.886c-.356-.18-.883-.356-1.41-.356-.532 0-1.06.176-1.41.356-.528.175-.884.53-1.235.886l-5.637 5.692V3.734c0-.535-.176-1.066-.352-1.421-.18-.536-.53-.891-.882-1.247-.352-.355-.703-.71-1.235-.886C37.97 0 37.441 0 36.91 0c-.527 0-1.055 0-1.406.18-.531.175-.883.53-1.234.886-.352.356-.708.711-.883 1.246-.176.532-.352.887-.352 1.422v17.953L27.398 16c-.351-.355-.707-.71-1.234-.89-.352-.176-.879-.356-1.41-.356-.527 0-1.055.18-1.41.355-.352.18-.88.536-1.23.891-.356.355-.708.71-.884 1.246-.175.531-.351.887-.351 1.422 0 .531.176 1.066.351 1.598.176.535.528.89.883 1.246L34.27 33.957c.351.355.703.711 1.234.887.351.18.879.355 1.406.355.531 0 1.059-.176 1.41-.355.532-.176.883-.532 1.235-.887zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vob.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vox.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><g fill="#039"><path d="m31.793 20.445 11.852 8.008V.246L31.793 8.254V4.25c0-2.184-1.797-4.004-3.953-4.004H3.953C1.797.246 0 2.066 0 4.25v20.2c0 2.183 1.797 4.003 3.953 4.003H27.84c2.156 0 3.953-1.82 3.953-4.004zm18.32 7.278v12.011c0 4.368 3.59 8.008 7.903 8.008 4.308 0 7.902-3.64 7.902-8.008V27.723c0-4.368-3.594-8.004-7.902-8.004-4.313 0-7.903 3.636-7.903 8.004zm0 0"/><path d="M70.047 39.734c0 6.73-5.387 12.008-11.852 12.008-6.648 0-11.855-5.457-11.855-12.008h-3.953A15.96 15.96 0 0 0 54.242 55.2v8.555h7.903v-8.555A15.955 15.955 0 0 0 74 39.734zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/vscodeignore.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M54.633 9.367C42.145-3.12 21.855-3.12 9.367 9.367s-12.488 32.778 0 45.266 32.778 12.488 45.266 0 12.488-32.778 0-45.266zM12.176 44.801c-5.934-9.211-4.84-21.543 3.12-29.504s20.294-9.055 29.505-3.121zm7.023 7.023L51.824 19.2c5.934 9.211 4.84 21.543-3.12 29.504s-20.294 9.055-29.505 3.121zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/war.svg
1
1
<svg height="64" width="48" xmlns="http://www.w3.org/2000/svg"><g stroke-miterlimit="10" stroke-width=".5"><path d="M44.2 75.3c7.2-3.701 3.9-7.3 1.5-6.799-.6.099-.801.2-.801.2s.2-.3.601-.5C50.1 66.6 53.6 73 44 75.5zm0 0" fill="#265db4" stroke="#265db4" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M37.8 64.8c1.801 2.1-.5 4-.5 4s4.7-2.4 2.5-5.5c-2-2.8-3.6-4.2 4.8-9.101 0 .101-13.1 3.401-6.8 10.6" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M39.8 46.499s3.999 4-3.8 10.102c-6.2 4.898-1.4 7.7 0 10.899-3.601-3.3-6.3-6.2-4.5-8.8 2.7-4 9.9-5.9 8.3-12.201" fill="#c00" stroke="#c00" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><g fill="#265db4" stroke="#265db4"><path d="M31 76.8s-1.5.9 1 1.1c3 .299 4.6.299 7.9-.3 0 0 .9.599 2.1 1-7.4 3.3-16.901-.1-11-1.8m-.9-4.2s-1.6 1.199.9 1.5c3.2.3 5.8.4 10.2-.5 0 0 .6.6 1.599 1-9.1 2.6-19.199.2-12.698-2" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M47.7 79.9s1.1.9-1.2 1.599c-4.3 1.302-18 1.702-21.8.101-1.4-.6 1.2-1.4 2-1.6.8-.2 1.3-.1 1.3-.1-1.5-1.1-9.8 2.1-4.2 3 15.3 2.4 27.9-1.199 23.9-3M31.7 68.3s-7 1.702-2.499 2.301c1.9.301 5.699.2 9.2-.101 2.9-.2 5.799-.8 5.799-.8s-1 .4-1.8.901c-7.1 1.9-20.7.999-16.8-.9 3.4-1.6 6.1-1.401 6.1-1.401" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/><path d="M32.399 85.4c6.901.4 17.502-.2 17.7-3.5 0 0-.499 1.2-5.699 2.2-5.899 1.1-13.101 1-17.5.3.1 0 1 .7 5.499 1" transform="matrix(1.63687 0 0 1.62288 -34.986 -75.177)"/></g></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/wav.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/wbk.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/webinfo.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M18.785 46.805c3.504-1.43 7.164-2.387 10.985-2.547V34.07H16.398c.16 4.297.954 8.754 2.387 12.735zM11.461 7.64c1.594 1.593 3.504 2.867 5.254 3.98 1.594-3.66 3.664-7.164 6.21-10.348-4.3 1.274-8.12 3.344-11.464 6.368zm33.434 9.554c-3.5 1.43-7.165 2.387-10.985 2.547V29.93h13.375c-.16-4.297-.957-8.754-2.39-12.735zM29.93 19.582c-3.82-.316-7.64-1.113-10.985-2.547A43.883 43.883 0 0 0 16.56 29.93h13.37zm-9.551-6.207c3.023 1.273 6.207 1.91 9.55 2.227V0h-.316L27.86 1.91c-3.343 3.344-5.73 7.324-7.48 11.465zM47.285 33.91H33.91v10.188c3.82.32 7.64 1.117 10.985 2.55 1.433-3.824 2.23-8.28 2.39-12.738zM33.75 15.762c3.344-.32 6.527-.957 9.555-2.23-1.91-4.298-4.457-8.118-7.485-11.622L34.547.16h-.637zm18.629-8.278c-3.344-2.867-7.324-5.097-11.621-6.21a45.734 45.734 0 0 1 6.207 10.347c1.91-1.113 3.66-2.387 5.414-4.137zm-22.45 40.754c-3.343.32-6.527.957-9.55 2.23 1.91 4.137 4.297 8.118 7.324 11.462l1.59 1.75h.477zM12.419 30.09c.16-5.094 1.273-9.871 2.707-14.649-2.39-1.273-4.457-2.863-6.687-4.933l-.16-.16C3.503 15.602.32 22.449 0 30.09zm38.848 3.82c-.16 5.094-1.278 9.871-2.707 14.649 2.386 1.273 4.457 2.863 6.683 4.933l.32.32C60.34 48.56 63.523 41.712 64 34.07c-.32-.16-12.734-.16-12.734-.16zm3.82-23.402c-1.91 1.91-4.3 3.504-6.688 4.933 1.75 4.618 2.707 9.555 2.707 14.649H63.68c-.477-7.64-3.5-14.488-8.438-19.742zm-2.867 45.851c-1.594-1.593-3.504-2.867-5.254-3.98a45.734 45.734 0 0 1-6.207 10.348c4.297-1.274 8.277-3.344 11.46-6.368zM8.598 53.492c1.91-1.91 4.297-3.504 6.527-4.933-1.75-4.618-2.707-9.555-2.867-14.649H0c.477 7.64 3.504 14.488 8.277 19.742zm8.117-1.113c-1.91 1.113-3.66 2.547-5.254 3.98 3.344 2.864 7.324 5.094 11.625 6.207-2.707-3.023-4.777-6.367-6.371-10.187zm26.59-1.754c-3.028-1.273-6.211-1.91-9.555-2.227V64h.48l1.75-1.91c3.184-3.344 5.57-7.485 7.325-11.465zm0 0" fill="#bababa"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/webm.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/webp.svg
1
1
<svg height="64" width="82" xmlns="http://www.w3.org/2000/svg"><g fill="#3c3"><path d="M0 .094v63.812h82V.094zM77.34 4.8v27.86L57.773 17.6 36.34 39.247l-15.094-8.469L4.844 43.953V4.801zm0 0"/><path d="M22.55 17.223c0 3.222-2.585 5.836-5.777 5.836-3.191 0-5.777-2.614-5.777-5.836s2.586-5.836 5.777-5.836c3.192 0 5.778 2.613 5.778 5.836zm0 0"/></g></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/wma.svg
1
1
<svg height="64" width="57" xmlns="http://www.w3.org/2000/svg"><path d="M49.766 32.992c-5.68-2.187-12.278.547-14.66 6.196-2.2 5.652.55 12.21 6.23 14.582 1.102.546 2.383.73 3.668.73 6.414.18 11.726-4.742 11.726-10.938V0c-.363 0-.546.184-.914.184-12.464 3.46-24.742 7.105-37.207 10.57-1.097.363-1.097.91-1.097 1.824V43.2c-.918-.367-1.285-.547-2.016-.73-4.582-1.64-8.617-.73-11.914 2.55-3.3 3.098-4.215 7.84-2.383 12.032 2.383 5.648 8.98 8.383 14.66 6.195 4.22-1.82 6.965-5.832 6.965-10.387V24.426c0-1.278.367-1.64 1.653-2.008 6.046-1.64 12.093-3.461 18.328-5.102l8.797-2.55v18.953c-.918-.364-1.286-.547-1.836-.727zm0 0" fill="#039"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/wmf.svg
1
1
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg"><path d="M16.223 21.805.09 55.844l3.012 3.015 20.035-20.035c-.711-1.594-.532-3.543.886-4.96 1.774-1.774 4.43-1.774 6.204 0 1.773 1.769 1.773 4.429 0 6.202-1.243 1.243-3.368 1.594-4.965.887L5.23 60.984 8.242 64l34.04-16.133L49.73 27.48 36.61 14.36zm46.625-4.075L46.184 1.062c-1.418-1.417-3.547-1.417-4.965 0L37.32 4.966c-1.422 1.418-1.422 3.543 0 4.965l16.664 16.664c1.418 1.418 3.543 1.418 4.965 0l3.899-3.903c1.418-1.418 1.418-3.543 0-4.96zm0 0" fill="#fea500"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/wmv.svg
1
1
<svg height="64" width="84" xmlns="http://www.w3.org/2000/svg"><path d="M.238 64h83.524V0H.238zm16.16-4.91H6.41V52h9.988zm30.688 0h-9.988V52h9.988zM67.602 4.363h9.988v7.274h-9.988zm0 47.453h9.988v7.094h-9.988zM52.168 4.363h9.805v7.09h-9.985v-7.09zm0 47.453h9.805v7.094h-9.985zM36.914 4.363h9.988v7.09h-9.988zM35.464 22l13.071 7.453c2.363 1.457 2.363 3.637 0 5.094L35.465 42c-2.363 1.453-4.36.184-4.36-2.547v-15.09c0-2.547 1.997-3.816 4.36-2.363zM21.485 4.363h9.805v7.09h-9.805zm0 47.453h9.805v7.094h-9.805zM6.41 4.363h9.988v7.274H6.41zm0 0" fill="#f60"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/woff.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/woff2.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="48"><path style="stroke:none;fill-rule:nonzero;fill:#7291a1;fill-opacity:1" d="M28.621 33.172h-16.32l-2.012 4.45c-.55 1.483-.918 2.593-.918 3.706 0 1.297.547 2.223 1.649 2.781.55.371 2.203.555 4.582.743v1.293H.203v-1.293c1.652-.188 2.934-.93 4.035-2.04 1.098-1.113 2.383-3.34 3.848-6.859L24.586 0h.73L42 36.879c1.648 3.52 2.934 5.746 3.852 6.672.73.742 1.832 1.113 3.296 1.113v1.297h-22.18v-1.297h.919c1.832 0 3.113-.184 3.847-.742.551-.371.735-.926.735-1.48 0-.372 0-.743-.184-1.301 0-.184-.367-1.11-1.101-2.407zm-1.101-2.406-6.786-15.57-7.148 15.57zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#36454d;fill-opacity:1" d="m83.797 16.309-6.602 22.054-.734 2.778c0 .375-.184.558-.184.742 0 .187.184.558.371.742.184.188.368.371.547.371.551 0 1.102-.371 2.016-1.113.371-.367 1.102-1.297 2.387-2.965l1.097.559c-1.648 2.964-3.3 5.003-5.132 6.3-1.833 1.297-3.852 2.04-5.864 2.04-1.285 0-2.203-.372-2.933-.93-.735-.742-1.102-1.485-1.102-2.407 0-.93.367-2.41 1.102-4.82l.73-2.781c-2.562 4.45-5.133 7.601-7.516 9.453C60.516 47.442 59.05 48 57.582 48c-2.016 0-3.668-.926-4.582-2.594-.918-1.668-1.465-3.523-1.465-5.746 0-3.152.914-6.672 2.934-10.75 2.011-4.074 4.582-7.226 7.695-9.82 2.566-2.04 5.133-2.965 7.332-2.965 1.285 0 2.203.367 3.121 1.11.73.742 1.281 2.038 1.649 3.89l1.28-4.074zM72.98 22.797c0-1.856-.367-3.152-.918-3.895-.367-.554-.914-.742-1.648-.742-.734 0-1.469.375-2.2.93-1.464 1.297-3.116 4.074-4.948 8.336-1.832 4.265-2.57 7.785-2.57 10.937 0 1.11.183 2.035.554 2.594.363.559.914.742 1.281.742 1.098 0 2.016-.558 3.117-1.668 1.465-1.668 2.934-3.707 4.032-5.93 2.199-4.449 3.3-8.156 3.3-11.304zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/wps.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="60"><path style="stroke:none;fill-rule:nonzero;fill:#6190aa;fill-opacity:1" d="m12.762 33.262-8.39-26.09c-.349-1.059-.524-1.41-.7-1.41-.176-.176-.352-.176-.527-.352l-2.97-.883L0 .824h15.734l.348 3.703-2.973.883v.352c0 .351.176 1.058.528 1.761L16.78 17.57 22.38.824 26.57.648l5.07 16.747 3.497-10.051c.175-.703.527-1.41.527-1.762V5.41l-2.621-.707-.176-3.879h12.235l.351 3.703-3.32 1.059c-.176 0-.352.176-.528.176 0 .176-.347.351-.523 1.234l-9.266 25.91-4.37.356-4.716-16.043-5.597 15.687zm0 0"/><path style="fill-rule:nonzero;fill:#6190aa;fill-opacity:1;stroke-width:.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#6190aa;stroke-opacity:1;stroke-miterlimit:10" d="M42.4 48.6H60v2.8H42.4zm0 7.401H60V58.8H42.4zm0 7.7H60V66.5H42.4zm-29.4 7.8h47v2.798H13zm0 7.598h47v2.8H13zm0 0" transform="matrix(1.74818 0 0 1.76287 -21.328 -85.027)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/wsf.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xaml.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.143 25.867c-.5 0-1.1-.2-1.502-.6l-8.21.1.6-1.6.6zm25.101 0c-.503 0-1.102-.2-1.502999.6-1.5.6zm-16.7 4.1c-.202 0-.403 0-.7-.1-1.1028.
A src/main/resources/com/keenwrite/ui/fonts/icons/xcf.svg
1
1
<svg height="64" width="78" xmlns="http://www.w3.org/2000/svg"><path d="M60.387 42.926c.734.367 1.648.738 2.383 1.293 2.382 1.48 4.585 2.957 6.785 4.437.367.184.734.371 1.101.184 2.567-.555 5.317.926 6.051 3.512.55 1.664.734 3.328.918 5.18.184 2.03.184 4.25.367 6.468-3.668-.926-7.152-2.035-9.719-4.805-.734-.742-.918-1.851-1.468-2.773-.184-.188-.368-.555-.551-.555A61.383 61.383 0 0 1 52.5 50.875c-.184-.188-.734-.188-.918 0-4.398 2.219-9.168 3.14-14.121 2.773-6.602-.37-12.652-2.59-18.52-5.363-.918-.555-2.02-1.11-2.937-1.664 4.586-2.219 6.605-5.914 6.605-10.906-1.101-.184-2.203-.184-3.12-.371-.184.926-.184 2.035-.368 3.144-1.098 4.067-4.95 6.285-9.168 5.176C4.453 42.184-.5 35.714.051 29.43c.367-4.621 4.402-7.578 8.8-6.47.735.185 1.47.368 2.204.74.734.366 1.28.738 2.015 1.109.551-.926.918-2.036 1.47-2.957.183-.188 0-.555 0-.743V6.32h.183c.183.184.367.371.55.739 1.649 2.406 3.118 4.808 5.133 6.843 2.383 2.215 4.77 4.067 7.887 4.805.918.184 1.648.184 2.566-.371 3.485-2.219 6.97-2.402 10.637-.555.184.188.55.188.918.188 5.133-1.297 9.902-3.512 13.754-7.395 1.652-1.664 2.934-3.699 3.852-6.101.367-1.11.917-2.035 1.468-3.145.547-.922 1.282-1.476 2.2-1.293 1.101.184 1.648.926 1.832 1.848.367 1.851.734 3.699.917 5.547.184 3.328.184 6.656.184 9.984-.184 6.84-.918 13.68-3.3 20.149-1.102 1.296-2.016 3.328-2.934 5.363zm-9.172-.371c.734-1.11.734-2.22.183-3.328-.914-1.664-2.382-2.957-4.214-3.696H47l.547 1.11c.183.367.367.738.367 1.293.367 1.48.184 2.218-1.281 2.773-.918.371-1.836.555-2.57.738-4.399.739-8.985.739-13.387.555h-1.281c0 .184.183.184.183.184 4.399 1.48 8.8 2.59 13.387 2.96 1.648.184 3.484.184 5.133-.37.55-.184.918 0 1.285.183 4.953 3.328 10.27 5.73 16.137 7.211 1.101.371 1.101.371 1.652-.738.183-.188.183-.371.367-.555-4.953-3.7-10.453-6.285-16.324-8.32zm-15.586-17.38c-.184 2.22.184 3.145 1.465 3.516 1.101.368 2.386 0 2.754-.925.367-.555.367-1.293.367-2.036-.184-.554-.735-1.109-1.102-1.664 2.2.188 3.485 1.48 4.219 3.516.547-1.852-.367-5.547-3.117-7.395-2.938-1.851-6.973-1.48-9.356 1.11-2.382 2.586-2.566 6.469-.734 9.426 1.836 2.773 5.504 3.699 7.152 2.957-2.015-.739-3.3-2.215-3.484-4.434 0-1.664.55-2.96 1.836-4.07zm-13.387 7.028c-3.3-1.848-4.035-5.363-1.652-7.027-.367 1.48 0 2.406 1.101 2.96.735.368 2.016.184 2.383-.741.367-.555.551-1.293.367-1.848s-.734-.926-1.101-1.664c.918.37 1.652.555 2.387.926.546-1.664.546-1.664 0-2.403-1.653-1.48-3.485-2.035-5.504-1.48-2.2.554-3.485 2.035-4.035 4.25-.184.926-.368 1.851.183 2.773l1.652 2.774c.184.187.184.554.551.554.914.743 2.2.926 3.668.926zM2.984 29.984c0 .926.368 2.036 1.102 2.957 1.098 1.293 2.934 1.293 4.219 0 1.832-1.847 1.648-4.992-.367-6.656-.551-.555-1.286-.738-2.02-.555-1.832.188-3.117 1.852-2.934 4.254zm0 0" fill="#3c3"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xfl.svg
1
1
<svg height="63" width="49" xmlns="http://www.w3.org/2000/svg"><path d="M4.524 3.224v10.102h8.5v2.598h-8.5v13.7h-3.9V.626h13.301v2.598zm14.402 26.3V.826h3.7v28.7zm0 0" fill="#d10407" stroke="#d10407" stroke-miterlimit="10" stroke-width="1.25" transform="matrix(2.10753 0 0 2.07742 0 .079)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xlm.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xls.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xlsm.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xlsx.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xlt.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xltm.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xltx.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="84" height="62"><path style="stroke:none;fill-rule:nonzero;fill:#1f7244;fill-opacity:1" d="M61.328.137H84v15.152H61.328zm0 23.383H84v15.152H61.328zm0 23.19H84v15.153H61.328zm-30.664 0h22.672v15.153H30.664zM0 46.71h22.672v15.153H0zm50.363-8.98L35.496 18.84 49.06 1.633H35.684l-7.067 9.351-6.5-9.347H8.18l13.746 17.578L7.434 37.73H21l7.617-10.285 7.621 10.285zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xml.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#666;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#666;stroke-opacity:1;stroke-miterlimit:10" d="M11.241 25.867c-.498 0-1.1-.2-1.5-.6l-8.398-8.4c-.8-.799-.8-2.1 0-3.001l8.398-8.4c.8-.798 2.101-.798 3.002 0 .8.801.8 2.1 0 3.001l-6.901 6.9 6.9 6.9c.8.8.8 2.098 0 3-.5.4-.998.6-1.499.6zm25 0c-.5 0-1.099-.2-1.499-.6-.8-.8-.8-2.1 0-3l6.901-6.9-6.9-6.9c-.8-.8-.8-2.1 0-3 .8-.8 2.1-.8 2.998 0l8.4 8.399c.801.8.801 2.102 0 3l-8.4 8.4c-.4.4-.898.6-1.5.6zm-16.698 4.1c-.2 0-.402 0-.7-.1-1.1-.399-1.7-1.5-1.3-2.599l8.399-25.1c.402-1.101 1.5-1.702 2.6-1.302 1.1.4 1.7 1.502 1.3 2.6l-8.4 25.101c-.198.901-1 1.4-1.899 1.4zm0 0" transform="matrix(1.74792 0 0 1.75607 0 .53)"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xpi.svg
1
1
<svg height="64" width="67" xmlns="http://www.w3.org/2000/svg"><path d="M46.406 0c6.367 4.457 10.078 16.043 10.078 24.781 0 2.317-.351 4.457-.882 6.418-.532-5.172-3.008-9.629-6.895-12.48 2.121 2.851 3.36 6.418 3.36 10.34 0 9.625-7.782 17.468-17.329 17.468-3.89 0-5.836-.71-8.664-2.851 5.836 0 9.547-5.883 14.5-5.883 0 0-.71-2.852-4.422-2.852-3.715 0-1.945 2.852-8.664 2.852S17.41 33.695 17.41 30.484c0-3.207 4.774-5.527 5.836-4.457 1.059-1.07 0-2.851 0-2.851l8.664-5.883h-2.832c-12.906 0-5.48-9.094-2.828-11.59-4.598 0-7.426 4.281-8.664 5.883-.707-.356-5.832-.356-7.246 0-.707.18-1.594-1.066-2.3-2.492-1.06-1.961-1.946-4.637-1.946-6.242-3.711 3.746-3.004 9.27-1.59 11.41l-.176.18C1.676 19.253.262 24.601.262 30.483.262 49.024 15.113 64 33.5 64s33.238-13.547 33.238-32.09V29.06C66.563 11.766 55.602 2.852 46.406 0zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xps.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.143 25.867c-.5 0-1.1-.2-1.502-.6l-8.21.1.6-1.6.6zm25.101 0c-.503 0-1.102-.2-1.502999.6-1.5.6zm-16.7 4.1c-.202 0-.403 0-.7-.1-1.1028.
A src/main/resources/com/keenwrite/ui/fonts/icons/xrb.svg
1
1
<svg height="64" width="74" xmlns="http://www.w3.org/2000/svg"><path d="m49.332 34.941-12.25-5.714L61.75 17.633 74 23.348l-12.25 5.879zM61.75 6.207 49.5.492 37.25 6.207l24.5 11.594L74 12.086zm-37.082 17.14-12.25-5.714-12.25 5.715L24.836 34.94l12.246-5.714zm0-11.429 12.25-5.711L24.668.492 0 12.086 12.25 17.8zM61.75 32.59l-11.074 5.039-1.344.672-1.34-.672-11.074-5.04-11.078 5.04-1.34.672-1.344-.672-11.074-5.04v17.977L36.75 63.508l25-12.942zm0 0" fill="#55486d"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xsd.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M26.555 0h15.89v16h-15.89zm0 48h15.89v16h-15.89zM.015 48h15.887v16H.016zm53.083 0h15.886v16H53.098zM37.207 29.273V18.727h-5.414v10.546H5.25v16h5.418V34.727h21.125v10.546h5.414V34.727h21.125v10.546h5.418v-16zm0 0" fill="#999"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xsl.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="55"><path style="fill-rule:nonzero;fill:#999;fill-opacity:1;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke:#999;stroke-opacity:1;stroke-miterlimit:10" d="M11.143 25.867c-.5 0-1.1-.2-1.502-.6l-8.21.1.6-1.6.6zm25.101 0c-.503 0-1.102-.2-1.502999.6-1.5.6zm-16.7 4.1c-.202 0-.403 0-.7-.1-1.1028.
A src/main/resources/com/keenwrite/ui/fonts/icons/xspf.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M4.059 10.39h40.254c2.109 0 3.69-1.613 3.69-3.761 0-2.149-1.581-3.758-3.69-3.758H4.059c-2.11 0-3.692 1.61-3.692 3.758 0 2.152 1.582 3.762 3.692 3.762zm0 19.891h40.254c2.109 0 3.69-1.613 3.69-3.765 0-2.149-1.581-3.762-3.69-3.762H4.059c-2.11 0-3.692 1.613-3.692 3.762 0 2.148 1.582 3.765 3.692 3.765zm19.336 10.57H4.059c-2.11 0-3.692 1.614-3.692 3.762 0 2.149 1.582 3.766 3.692 3.766h19.336c2.109 0 3.69-1.617 3.69-3.766 0-2.148-1.581-3.761-3.69-3.761zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#039;fill-opacity:1" d="M70.68 9.496c-2.813-1.434-6.504-3.582-7.91-6.629C62.77 1.254 61.54 0 59.957 0c-1.582 0-2.812 1.254-2.812 2.867v38.52c-2.989-1.614-8.614-1.075-12.833 1.433-6.68 3.766-9.492 10.93-6.68 15.766 2.813 4.84 10.723 5.914 17.4 2.152 4.573-2.687 7.738-6.988 7.913-11.289V16.305c9.492 0 15.29 3.941 13.18 13.437-.352 1.793-1.05 3.403-1.754 5.195-.355.54-.355 1.254.176 1.793.527.536 1.402.356 2.11-.359 3.515-3.582 5.796-8.242 5.976-13.437-.18-6.805-6.508-10.75-11.953-13.438zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/xz.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/yaml.svg
1
1
<svg xmlns="http://www.w3.org/2000/svg" width="83" height="63"><path style="stroke:none;fill-rule:nonzero;fill:#999;fill-opacity:1" d="M.125 0h69.586v8.184H.125zm13.164 18.273h69.586v8.18H13.289zM.125 36.543h69.586v8.184H.125zm13.164 18.273h69.586V63H13.289zm0 0"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/z.svg
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
12
A src/main/resources/com/keenwrite/ui/fonts/icons/zip.svg
1
1
<svg height="63" width="54" xmlns="http://www.w3.org/2000/svg"><path d="M53.344 18.172H44.02V8.965zM28.309 8.965v33.437h25.199V20.434H41.727V8.964zm3.93-8.723H4.417v6.461h10.965l-6.875 5.332v5.652l10.148-7.753V6.867H54V4.281zM18.655 14.297 8.508 22.05v5.652l10.148-7.754zM8.344 37.559l10.148-7.754v-5.657L8.344 31.902zm10.312 2.261v-5.656L8.508 41.918v2.91h-4.09v6.461h6.219v4.523H7.035c-.652-1.132-1.797-1.937-3.273-1.937C1.637 53.875 0 55.488 0 57.59c0 2.097 1.637 3.715 3.762 3.715 1.476 0 2.62-.809 3.273-1.938h3.602v3.39h5.562v-3.39h3.602c.652 1.13 1.8 1.938 3.273 1.938 2.125 0 3.762-1.618 3.762-3.715 0-2.102-1.637-3.715-3.762-3.715-1.472 0-2.62.805-3.273 1.938h-3.602v-4.524h15.875l21.762-3.879v-2.582H11.78zm0 0" fill="#90c"/></svg>
A src/main/resources/com/keenwrite/ui/fonts/icons/zsh.svg
1
1
<svg height="64" width="69" xmlns="http://www.w3.org/2000/svg"><path d="M13.875 13.874h10.9v2.701h-10.9zm0 5.4h10.9v2.701h-10.9zm0 5.5h10.9v2.702h-10.9zm19-24.399H11.177c-3 0-5.402 2.4-5.402 5.4v24.4h-5.4c0 3 2.402 5.4 5.4 5.4h21.7c3 0 5.402-2.4 5.402-5.4v-21.7h5.4v-2.7c0-3-2.402-5.4-5.4-5.4zm-2.7 29.3c0 1.801-1.4 3.2-3.2 3.2h-19.9c1.3-.9 1.3-2.7 1.3-2.7v-24.4c0-1.5 1.2-2.7 2.7-2.7 1.501 0 2.7 1.2 2.7 2.7v2.7h16.3zm-13.6-23.9v-2.7h16.3c2.501 0 2.7 1.6 2.7 2.7zm0 0" fill="#999" stroke="#999" stroke-miterlimit="10" stroke-width=".75" transform="matrix(1.7717 0 0 1.78025 .262 0)"/></svg>
A src/main/resources/com/keenwrite/xml.css
1
.tagmark {
2
    -fx-fill: gray;
3
}
4
.anytag {
5
    -fx-fill: crimson;
6
}
7
.paren {
8
    -fx-fill: firebrick;
9
    -fx-font-weight: bold;
10
}
11
.attribute {
12
    -fx-fill: darkviolet;
13
}
14
.avalue {
15
    -fx-fill: black;
16
}
117
18
.comment {
19
	-fx-fill: teal;
20
}
A src/main/resources/fonts/emoji/OpenSansEmoji-Regular.ttf
Binary file
A src/main/resources/fonts/noto-sans/NotoSans-Bold.ttf
Binary file
A src/main/resources/fonts/noto-sans/NotoSans-BoldItalic.ttf
Binary file
A src/main/resources/fonts/noto-sans/NotoSans-Italic.ttf
Binary file
A src/main/resources/fonts/noto-sans/NotoSans-Regular.ttf
Binary file
A src/main/resources/fonts/source-code-pro/SourceCodePro-Bold.ttf
Binary file
A src/main/resources/fonts/source-code-pro/SourceCodePro-BoldItalic.ttf
Binary file
A src/main/resources/fonts/source-code-pro/SourceCodePro-Italic.ttf
Binary file
A src/main/resources/fonts/source-code-pro/SourceCodePro-Regular.ttf
Binary file
A src/main/resources/fonts/source-serif-4/SourceSerif4-Bold.otf
Binary file
A src/main/resources/fonts/source-serif-4/SourceSerif4-BoldItalic.otf
Binary file
A src/main/resources/fonts/source-serif-4/SourceSerif4-Italic.otf
Binary file
A src/main/resources/fonts/source-serif-4/SourceSerif4-Regular.otf
Binary file
A src/main/resources/lexicons/README.md
1
# Lexicons
2
3
This directory contains lexicons used for spell checking. Each lexicon
4
file contains tab-delimited word-frequency pairs.
5
6
Compiling a high-quality list of correctly spelled words requires the
7
following steps:
18
9
1. Download a unigram frequency lResource
A src/main/resources/lexicons/de.txt
Binary file
A src/main/resources/lexicons/en.txt
Binary file
A src/main/resources/lexicons/es.txt
Binary file
A src/main/resources/lexicons/ext/README.md
1
# Overview
2
3
Lexicons in this directory are meant to relate to a particular subject
4
(medicine, chemistry, math, sports, and such), extend the main lexicon,
5
or not be in common use.
6
17
A src/main/resources/lexicons/ext/contractions.txt
1
1
'aight
2
ain't
3
amn't
4
aren't
5
can't
6
'cause
7
couldn't
8
couldn't've
9
could've
10
daren't
11
daresn't
12
dasn't
13
didn't
14
doesn't
15
don't
16
dunno
17
d'ye
18
e'er
19
everybody's
20
everyone's
21
g'day
22
gimme
23
giv'n
24
gonna
25
gon't
26
gotta
27
hadn't
28
had've
29
hasn't
30
haven't
31
he'd
32
he'll
33
he's
34
he've
35
how'd
36
howdy
37
ho
A src/main/resources/lexicons/ext/tech.txt
1
analytics	130337
2
hotspot	130022
3
instantiation	130000
4
onboarding	129953
5
biometric	129795
6
anamorphic	129777
7
benchmarking	129772
8
cybersecurity	129769
9
barcode	129757
10
splitter	129755
11
keychain	129719
12
crowdfunding	129696
13
polymorphism	129688
14
automata	129666
15
shockwave	129658
16
profiler	129648
17
kerning	129646
18
nanometer	129630
19
meridiem	129624
20
influencer	129618
21
passcode	129617
22
sexting	129607
23
cryptology	129606
24
biometrics	129606
25
bitcoin	129599
26
specular	129598
27
accelerometer	129588
28
googolplex	129583
29
grayscale	129576
30
ascender	129571
31
pixelated	129569
32
rockstar	129565
33
ragdoll	129564
34
cyberattack	129564
35
cryptanalysis	129562
36
ransomware	129553
37
crowdsourcing	129552
38
hackathon	129551
39
audiobook	129544
40
degauss	129543
41
attenuator	129540
42
jetpack	129538
43
packrat	129536
44
backlight	129535
45
bootable	129530
46
octothorpe	129529
47
newsfeed	129525
48
extranet	129523
49
failover	129516
50
cyberbullying	129516
51
neumann	129515
52
capacitive	129514
53
backlit	129511
54
millimicron	129507
55
inductor	129505
56
workgroup	129502
57
journaling	129500
58
middleware	129499
59
spooler	129497
60
clamshell	129495
61
wireframe	129494
62
modularity	129493
63
strikethrough	129489
64
petabyte	129487
65
jughead	129482
66
acyclic	129482
67
gearhead	129478
68
stateful	129473
69
submenu	129467
70
pseudorandom	129463
71
earbuds	129461
72
narrowband	129460
73
recordable	129457
74
unallocated	129455
75
mappable	129455
76
chipset	129454
77
multicast	129447
78
loopback	129444
79
pixelate	129441
80
cryptographic	129441
81
pixelation	129438
82
autocorrect	129438
83
teraflop	129437
84
digitizer	129436
85
tunnelling	129434
86
deduplication	129434
87
subwoofer	129433
88
touchpad	129429
89
namespace	129428
90
microcontroller	129428
91
geolocation	129428
92
telepresence	129427
93
driverless	129426
94
photolithography	129425
95
multiphase	129425
96
verifier	129424
97
robocall	129424
98
autofocus	129424
99
kilobit	129422
100
hacktivist	129419
101
geocache	129415
102
rasterize	129412
103
plaintext	129411
104
pipelining	129411
105
technobabble	129409
106
defragment	129409
107
connectionless	129409
108
homomorphic	129407
109
demodulator	129406
110
datagram	129406
111
activex	129406
112
normalisation	129404
113
blackhole	129402
114
cyberstalker	129401
115
multifunction	129400
116
undirected	129397
117
ciphertext	129397
118
superspeed	129396
119
spacebar	129395
120
cyberwar	129395
121
borderless	129395
122
transcode	129393
123
cyberbully	129393
124
multimeter	129392
125
dropship	129391
126
yottabyte	129390
127
infector	129390
128
superclass	129389
129
tooltip	129388
130
dereference	129387
131
combinator	129386
132
milliwatt	129385
133
cyberstalking	129384
134
subfolder	129383
135
wideband	129382
136
noncontiguous	129382
137
ferroelectric	129382
138
cybersquatting	129378
139
autofill	129378
140
trackpad	129376
141
associatively	129376
142
luggable	129374
143
seamonkey	129373
144
defragmentation	129373
145
starcraft	129371
146
obliquing	129371
147
leadless	129371
148
greeking	129371
149
upgradeable	129370
150
radiosity	129370
151
transcoding	129369
152
quintillionth	129369
153
bitmapped	129369
154
subdirectory	129368
155
degausser	129368
156
curtiss	129368
157
scunthorpe	129367
158
undelete	129365
159
gigaflops	129365
160
darknet	129365
161
zettabyte	129364
162
topologies	129363
163
spidering	129363
164
photorealism	129363
165
multithreading	129363
166
deallocate	129363
167
mersenne	129362
168
machinima	129361
169
satisfiable	129360
170
laserjet	129360
171
multicore	129359
172
microblog	129359
173
megaflops	129359
174
homeomorphic	129359
175
microblogging	129358
176
kilobaud	129358
177
cyberwarfare	129358
178
microarchitecture	129357
179
autosave	129357
180
wirelessly	129356
181
sneakernet	129355
182
textbox	129354
183
obfuscator	129354
184
microkernel	129353
185
substring	129352
186
macroinstruction	129352
187
endianness	129352
188
indexable	129351
189
backtick	129351
190
unshielded	129350
191
cleartext	129350
192
autocomplete	129349
193
abandonware	129349
194
hacktivism	129348
195
antikythera	129348
196
stereolithography	129347
197
photorealistic	129347
198
macrovision	129347
199
greasemonkey	129347
200
geotagging	129347
201
disassembler	129346
202
spacewar	129345
203
pluggable	129345
204
kilobits	129345
205
webcomic	129344
206
unfollow	129344
207
photosensor	129344
208
petaflop	129344
209
garageband	129344
210
truetype	129343
211
subnetwork	129342
212
backpropagation	129342
213
supercomputing	129340
214
smartwatch	129340
215
unbundled	129339
216
smilies	129339
217
milliamp	129339
218
bytecode	129339
219
trackpoint	129337
220
slipstreaming	129337
221
monospace	129337
222
memoization	129337
223
scaleable	129336
224
respawn	129335
225
multicasting	129335
226
geocacher	129335
227
workgroups	129334
228
ferrofluid	129334
229
smartdrive	129333
230
subsampling	129332
231
rasterization	129332
232
guiltware	129332
233
defragger	129332
234
satisfiability	129331
235
activision	129331
236
subdirectories	129330
237
segfault	129330
238
flamebait	129330
239
framebuffer	129329
240
defragging	129329
241
decompiler	129329
242
unshift	129328
243
memristor	129328
244
zebibyte	129327
245
semiprime	129327
246
rotoscoping	129327
247
hypertransport	129327
248
smartmedia	129326
249
grayware	129326
250
defragmenting	129326
251
defragmenter	129326
252
repagination	129325
253
subnetting	129324
254
skeuomorphism	129324
255
screencast	129324
256
stylesheet	129323
257
superintelligence	129322
258
multitenancy	129322
259
datastore	129322
260
autoplay	129322
261
repaginate	129321
262
macbook	129321
263
geotagged	129321
264
baudrate	129321
265
transmeta	129320
266
screwless	129320
267
nameserver	129320
268
interexchange	129320
269
geocoding	129319
270
downloader	129319
271
autodiscovery	129319
272
extortion	65752
273
emoji	65684
274
googol	65618
275
backside	65388
276
fibre	65387
277
metre	65333
278
royale	65173
279
radix	65093
280
hotdog	65091
281
lecher	65062
282
uptime	65009
283
unbound	64979
284
eniac	64975
285
synaptic	64966
286
voxel	64926
287
selfie	64917
288
uplink	64887
289
fanboy	64857
290
defrag	64849
291
nondisclosure	64839
292
qubit	64828
293
yippie	64821
294
gearhead	64819
295
subnet	64818
296
endian	64798
297
bezier	64797
298
reallocation	64796
299
telephonic	64789
300
mosfet	64777
301
mutex	64775
302
inkjet	64772
303
gobbing	64768
304
shader	64766
305
ultralight	64755
306
hackers	64746
307
pacman	64742
308
unlink	64741
309
undock	64740
310
understroke	64738
311
beginners	64736
312
photoscope	64731
313
gantt	64725
314
programmers	64722
315
todays	64720
316
moores	64716
317
fullscreen	64715
318
moveless	64708
319
reformatted	64704
320
deallocate	64704
321
laserdisc	64702
322
macos	64700
323
nonactive	64697
324
nonadjacent	64696
325
hotfix	64695
326
keylogger	64694
327
geotag	64691
328
oreilly	64681
329
exabit	64678
330
jailbroken	64677
331
fuzzer	64676
332
noninteractive	64673
333
multifactor	64672
334
letterspacing	64671
335
preinstall	64669
336
multiboot	64666
337
runescape	64665
338
micropayment	64664
339
numpad	64663
340
preinstalled	64661
341
jailbreaking	64660
342
attend	2158
343
withstand	1809
344
transpire	1116
345
reading	1110
346
texture	1065
347
capitalize	832
348
calling	779
349
unfold	767
350
starboard	679
351
commode	625
352
doing	594
353
textbook	499
354
unease	378
355
unpack	358
356
keycard	231
357
mainspring	207
358
grr	180
359
geocaching	167
360
microbus	160
361
mp3	147
362
svg	139
363
shifted	128
364
texted	127
365
towheaded	118
366
mineshaft	115
367
nonparty	95
368
crossbite	80
369
resignedness	69
370
msrp	61
371
inbreak	53
372
nanocomposite	44
373
md5	44
374
neomorphic	41
375
superstrain	28
376
lifers	27
377
multination	26
378
smartwatch	22
379
antilibration	22
380
zapf	20
381
mp4	20
1382
A src/main/resources/lexicons/fr.txt
Binary file
A src/main/resources/lexicons/he.txt
Binary file
A src/main/resources/lexicons/it.txt
Binary file
A src/main/resources/lexicons/ru.txt
Binary file
A src/main/resources/lexicons/zh.txt
Binary file
A src/test/java/com/keenwrite/AwaitFxExtension.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite;
3
4
import javafx.embed.swing.JFXPanel;
5
import org.junit.jupiter.api.extension.BeforeAllCallback;
6
import org.junit.jupiter.api.extension.ExtensionContext;
7
import org.testfx.osgi.service.TestFx;
8
9
import java.util.concurrent.Semaphore;
10
11
import static javafx.application.Platform.runLater;
12
import static javax.swing.SwingUtilities.invokeLater;
13
14
/**
15
 * Blocks all unit tests until JavaFX is ready.
16
 */
17
public class AwaitFxExtension implements BeforeAllCallback {
18
  /**
19
   * Prevent {@link RuntimeException} for internal graphics not initialized yet.
20
   *
21
   * @param context Provided by the {@link TestFx} framework.
22
   * @throws InterruptedException Could not acquire semaphore.
23
   */
24
  @Override
25
  public void beforeAll( final ExtensionContext context )
26
    throws InterruptedException {
27
    final var semaphore = new Semaphore( 0 );
28
29
    invokeLater( () -> {
30
      // Prepare JavaFX toolkit and environment.
31
      new JFXPanel();
32
      runLater( semaphore::release );
33
    } );
34
35
    semaphore.acquire();
36
  }
37
}
138
A src/test/java/com/keenwrite/definition/TreeViewTest.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.definition;
3
4
import com.keenwrite.editors.definition.DefinitionEditor;
5
import com.keenwrite.editors.definition.yaml.YamlTreeTransformer;
6
import com.keenwrite.editors.markdown.MarkdownEditor;
7
import com.keenwrite.preferences.Workspace;
8
import com.keenwrite.preview.HtmlPreview;
9
import com.panemu.tiwulfx.control.dock.DetachableTabPane;
10
import javafx.application.Application;
11
import javafx.beans.property.SimpleObjectProperty;
12
import javafx.event.Event;
13
import javafx.event.EventHandler;
14
import javafx.scene.Node;
15
import javafx.scene.Scene;
16
import javafx.scene.control.ColorPicker;
17
import javafx.scene.control.SplitPane;
18
import javafx.scene.control.Tooltip;
19
import javafx.scene.control.TreeItem;
20
import javafx.stage.Stage;
21
import org.assertj.core.util.Files;
22
import org.testfx.framework.junit5.Start;
23
24
import static com.keenwrite.util.FontLoader.initFonts;
25
26
public class TreeViewTest extends Application {
27
  private final SimpleObjectProperty<Node> mTextEditor =
28
    new SimpleObjectProperty<>();
29
30
  private final EventHandler<TreeItem.TreeModificationEvent<Event>> mTreeHandler =
31
    event -> refresh( mTextEditor.get() );
32
33
  private void refresh( final Node node ) {
34
    throw new RuntimeException( "Derp: " + node );
35
  }
36
37
  public static void main( final String[] args ) {
38
    initFonts();
39
    launch( args );
40
  }
41
42
  public void start( final Stage stage ) {
43
    onStart( stage );
44
  }
45
46
  @Start
47
  private void onStart( final Stage stage ) {
48
    final var workspace = new Workspace();
49
    final var mainPane = new SplitPane();
50
    final var transformer = new YamlTreeTransformer();
51
    final var editor = new DefinitionEditor( transformer );
52
    final var file = Files.newTemporaryFile();
53
54
    final var tabPane1 = new DetachableTabPane();
55
    tabPane1.addTab( "Editor", editor );
56
57
    final var tabPane2 = new DetachableTabPane();
58
    final var tab21 =
59
      tabPane2.addTab( "Picker", new ColorPicker() );
60
    final var tab22 =
61
      tabPane2.addTab( "Editor", new MarkdownEditor( file, workspace ) );
62
    tab21.setTooltip( new Tooltip( "Colour Picker" ) );
63
    tab22.setTooltip( new Tooltip( "Text Editor" ) );
64
65
    final var tabPane3 = new DetachableTabPane();
66
    tabPane3.addTab( "Preview", new HtmlPreview( workspace ) );
67
68
    editor.addTreeChangeHandler( mTreeHandler );
69
70
    mainPane.getItems().addAll( tabPane1, tabPane2, tabPane3 );
71
72
    stage.setScene( new Scene( mainPane ) );
73
    stage.show();
74
  }
75
}
176
A src/test/java/com/keenwrite/editors/markdown/MarkdownEditorTest.java
1
package com.keenwrite.editors.markdown;
2
3
import com.keenwrite.AwaitFxExtension;
4
import com.keenwrite.preferences.Workspace;
5
import org.assertj.core.util.Files;
6
import org.junit.jupiter.api.Test;
7
import org.junit.jupiter.api.extension.ExtendWith;
8
import org.testfx.framework.junit5.ApplicationExtension;
9
10
import java.io.File;
11
import java.util.regex.Pattern;
12
13
import static java.util.regex.Pattern.compile;
14
import static javafx.application.Platform.runLater;
15
import static org.junit.jupiter.api.Assertions.assertEquals;
16
import static org.junit.jupiter.api.Assertions.assertTrue;
17
18
@ExtendWith( {ApplicationExtension.class, AwaitFxExtension.class} )
19
public class MarkdownEditorTest {
20
  private static final File TEMP_FILE = Files.newTemporaryFile();
21
22
  private static final String[] WORDS = new String[]{
23
    "Italicize",
24
    "English's",
25
    "foreign",
26
    "words",
27
    "based",
28
    "on",
29
    "popularity,",
30
    "like",
31
    "_bête_",
32
    "_noire_",
33
    "and",
34
    "_Weltanschauung_",
35
    "but",
36
    "not",
37
    "résumé.",
38
    "Don't",
39
    "omit",
40
    "accented",
41
    "characters!",
42
    "Cœlacanthe",
43
    "L'Haÿ-les-Roses",
44
    "Mühlfeldstraße",
45
    "Da̱nx̱a̱laga̱litła̱n",
46
  };
47
48
  private static final String TEXT = String.join( " ", WORDS );
49
50
  private static final Pattern REGEX = compile(
51
    "[^\\p{Mn}\\p{Me}\\p{L}\\p{N}'-]+" );
52
53
  /**
54
   * Test that the {@link MarkdownEditor} can retrieve a word at the caret
55
   * position, regardless of whether the caret is at the beginning, middle, or
56
   * end of the word.
57
   */
58
  @Test
59
  public void test_CaretWord_GetISO88591Word_WordSelected() {
60
    runLater( () -> {
61
      final var editor = createMarkdownEditor();
62
63
      for( int i = 0; i < WORDS.length; i++ ) {
64
        final var word = WORDS[ i ];
65
        final var len = word.length();
66
        final var expected = REGEX.matcher( word ).replaceAll( "" );
67
68
        for( int j = 0; j < len; j++ ) {
69
          editor.moveTo( offset( i ) + j );
70
          final var actual = editor.getCaretWordText();
71
          assertEquals( expected, actual );
72
        }
73
      }
74
    } );
75
  }
76
77
  /**
78
   * Test that the {@link MarkdownEditor} can make a word bold.
79
   */
80
  @Test
81
  public void test_CaretWord_SetWordBold_WordIsBold() {
82
    final var index = 20;
83
    final var editor = createMarkdownEditor();
84
85
    editor.moveTo( offset( index ) );
86
    editor.bold();
87
    assertTrue( editor.getText().contains( "**" + WORDS[ index ] + "**" ) );
88
  }
89
90
  /**
91
   * Returns the document offset for a string at the given index.
92
   */
93
  private static int offset( final int index ) {
94
    assert 0 <= index && index < WORDS.length;
95
    int offset = 0;
96
97
    for( int i = 0; i < index; i++ ) {
98
      offset += WORDS[ i ].length();
99
    }
100
101
    // Add the index to compensate for one space between words.
102
    return offset + index;
103
  }
104
105
  /**
106
   * Returns an instance of {@link MarkdownEditor} pre-populated with
107
   * {@link #TEXT}.
108
   *
109
   * @return A new {@link MarkdownEditor} instance, ready for unit tests.
110
   */
111
  private MarkdownEditor createMarkdownEditor() {
112
    final var workspace = new Workspace();
113
    final var editor = new MarkdownEditor( TEMP_FILE, workspace );
114
    editor.setText( TEXT );
115
    return editor;
116
  }
117
}
1118
A src/test/java/com/keenwrite/encoding/EncodingTest.java
1
package com.keenwrite.encoding;
2
3
import com.keenwrite.util.EncodingDetector;
4
import org.junit.jupiter.api.Test;
5
6
import java.nio.charset.Charset;
7
import java.nio.charset.StandardCharsets;
8
9
import static java.nio.charset.StandardCharsets.*;
10
import static org.junit.jupiter.api.Assertions.assertEq@SuppressWarnings( "UnnecessaryLocalVariable" )or = new EncodingDetector();UTF_8;detector.detect( bytes UTF_8 );
11
  }
12
}
113
A src/test/java/com/keenwrite/flexmark/ParserTest.java
1
/* Copyright 2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.flexmark;
3
4
import com.keenwrite.processors.markdown.extensions.fences.FencedDivExtension;
5
import com.keenwrite.processors.markdown.extensions.references.CrossReferenceExtension;
6
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
7
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension;
8
import com.vladsch.flexmark.ext.superscript.SuperscriptExtension;
9
import com.vladsch.flexmark.ext.tables.TablesExtension;
10
import com.vladsch.flexmark.html.HtmlRenderer;
11
import com.vladsch.flexmark.parser.Parser;
12
import com.vladsch.flexmark.util.data.MutableDataSet;
13
import com.vladsch.flexmark.util.misc.Extension;
14
import org.junit.jupiter.params.ParameterizedTest;
15
import org.junit.jupiter.params.provider.Arguments;
16
import org.junit.jupiter.params.provider.MethodSource;
17
18
import java.util.ArrayList;
19
import java.util.List;
20
import java.util.stream.Stream;
21
22
import static org.junit.jupiter.api.Assertions.assertEquals;
23
24
/**
25
 * Test that basic styles for conversion exports as expected.
26
 */
27
public class ParserTest {
28
29
  @ParameterizedTest
30
  @MethodSource( "markdownParameters" )
31
  void test_Conversion_Markdown_Html( final String md, final String expected ) {
32
    final var extensions = createExtensions();
33
    final var options = new MutableDataSet();
34
    final var parser = Parser
35
      .builder( options )
36
      .extensions( extensions )
37
      .build();
38
    final var renderer = HtmlRenderer
39
      .builder( options )
40
      .extensions( extensions )
41
      .build();
42
43
    final var document = parser.parse( md );
44
    final var actual = renderer.render( document );
45
46
    assertEquals( expected, actual );
47
  }
48
49
  private List<Extension> createExtensions() {
50
    final var extensions = new ArrayList<Extension>();
51
52
    extensions.add( DefinitionExtension.create() );
53
    extensions.add( StrikethroughSubscriptExtension.create() );
54
    extensions.add( SuperscriptExtension.create() );
55
    extensions.add( TablesExtension.create() );
56
    extensions.add( FencedDivExtension.create() );
57
    extensions.add( CrossReferenceExtension.create() );
58
59
    return extensions;
60
  }
61
62
  private static Stream<Arguments> markdownParameters() {
63
    return Stream.of(
64
      Arguments.of(
65
        "*emphasis* _emphasis_ **strong**",
66
        "<p><em>emphasis</em> <em>emphasis</em> <strong>strong</strong></p>\n"
67
      ),
68
      Arguments.of(
69
        "the \uD83D\uDC4D emoji",
70
        "<p>the \uD83D\uDC4D emoji</p>\n"
71
      )
72
    );
73
  }
74
}
175
A src/test/java/com/keenwrite/io/FileObjectTest.java
1
package com.keenwrite.io;
2
3
import org.apache.commons.vfs2.FileSystemException;
4
import org.junit.jupiter.api.Disabled;
5
import org.renjin.eval.SessionBuilder;
6
7
import java.io.IOException;
8
import java.io.OutputStreamWriter;
9
10
import static com.keenwrite.constants.Constants.TEMPORARY_DIRECTORY;
11
import static java.io.File.separator;
12
import static java.lang.String.format;
13
import static java.nio.charset.StandardCharsets.UTF_8;
14
15
/**
16
 * Tests file resource allocation.
17
 */
18
public class FileObjectTest {
19
  /**
20
   * Test that resources are not exhausted.
21
   * <p>
22
   * Disabled because no issue was found and this test thrashes the I/O.
23
   * </p>
24
   */
25
  @Disabled
26
  void test_Open_MultipleFiles_NoResourcesExpire() throws FileSystemException {
27
    final var builder = new SessionBuilder();
28
    final var session = builder.build();
29
30
    for( int i = 0; i < 10000; i++ ) {
31
      final var filename = format(
32
        "%s%s%d.txt", TEMPORARY_DIRECTORY, separator, i
33
      );
34
      final var fileObject = session
35
        .getFileSystemManager()
36
        .resolveFile( filename );
37
38
      try(
39
        final var stream = fileObject.getContent().getOutputStream();
40
        final var writer = new OutputStreamWriter( stream, UTF_8 ) ) {
41
        writer.write( "contents" );
42
      } catch( final IOException e ) {
43
        throw new FileSystemException( e );
44
      }
45
46
      fileObject.delete();
47
    }
48
  }
49
}
150
A src/test/java/com/keenwrite/io/FileWatchServiceTest.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import org.junit.jupiter.api.Test;
5
import org.junit.jupiter.api.Timeout;
6
7
import java.io.File;
8
import java.io.IOException;
9
import java.nio.file.Files;
10
import java.util.concurrent.Semaphore;
11
import java.util.function.Consumer;
12
13
import static java.io.File.createTempFile;
14
import static java.nio.charset.StandardCharsets.UTF_8;
15
import static java.nio.file.StandardOpenOption.APPEND;
16
import static java.nio.file.StandardOpenOption.CREATE;
17
import static java.util.concurrent.TimeUnit.SECONDS;
18
import static org.junit.jupiter.api.Assertions.assertEquals;
19
20
/**
21
 * Responsible for testing that the {@link FileWatchService} fires the
22
 * expected {@link FileEvent} when the system raises state changes.
23
 */
24
class FileWatchServiceTest {
25
  /**
26
   * Test that modifying a file produces a {@link FileEvent}.
27
   *
28
   * @throws IOException          Could not create watcher service.
29
   * @throws InterruptedException Could not join on watcher service thread.
30
   */
31
  @Test
32
  @Timeout( value = 5, unit = SECONDS )
33
  void test_SingleFile_Write_Notified() throws
34
    IOException, InterruptedException {
35
    final var text = "arbitrary text to write";
36
    final var file = createTemporaryFile();
37
    final var service = new FileWatchService( file );
38
    final var thread = new Thread( service );
39
    final var semaphor = new Semaphore( 0 );
40
    final var listener = createListener( f -> {
41
      semaphor.release();
42
      assertEquals( file, f );
43
    } );
44
45
    thread.start();
46
    service.addListener( listener );
47
    Files.writeString( file.toPath(), text, UTF_8, CREATE, APPEND );
48
    semaphor.acquire();
49
    service.stop();
50
    thread.join();
51
  }
52
53
  private FileModifiedListener createListener( final Consumer<File> action ) {
54
    return fileEvent -> action.accept( fileEvent.getFile() );
55
  }
56
57
  private File createTemporaryFile() throws IOException {
58
    final var prefix = getClass().getPackageName();
59
    final var file = createTempFile( prefix, null, null );
60
    file.deleteOnExit();
61
    return file;
62
  }
63
}
164
A src/test/java/com/keenwrite/io/MediaTypeSnifferTest.java
1
package com.keenwrite.io;
2
3
import org.junit.jupiter.api.Test;
4
5
import java.io.File;
16
7
import static com.keenwrite.io.MediaTypeExtension.valueFrom;
8
import static org.apache.commons.io.FilenameUtils.getExtension;
9
import static org.junit.jupiter.api.AssertionTR."\{dir}/images"TR."\{image} -> \{media}"
A src/test/java/com/keenwrite/io/MediaTypeTest.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import org.junit.jupiter.api.Test;
5
6
import java.util.Map;
7
8
import static com.keenwrite.io.MediaType.*;
9
import static com.keenwrite.io.downloads.DownloadManager.open;
10
import static org.junit.jupiter.api.Assertions.*;
11
12
/**
13
 * Test that {@link MediaType} instances can be queried and return reliable
14
 * results.
15
 */
16
public class MediaTypeTest {
17
  /**
18
   * Test that {@link MediaType#equals(String, String)} is case-insensitive.
19
   */
20
  @Test
21
  public void test_Equality_IgnoreCase_Success() {
22
    final var mediaType = TEXT_PLAIN;
23
    assertTrue( mediaType.equals( "TeXt", "Plain" ) );
24
    assertEquals( "text/plain", mediaType.toString() );
25
  }
26
27
  /**
28
   * Test that {@link MediaType#fromFilename(String)} can lookup by file name.
29
   */
30
  @Test
31
  public void test_FilenameExtensions_Supported_Success() {
32
    final var map = Map.of(
33
      "jpeg", IMAGE_JPEG,
34
      "png", IMAGE_PNG,
35
      "svg", IMAGE_SVG_XML,
36
      "md", TEXT_MARKDOWN,
37
      "Rmd", TEXT_R_MARKDOWN,
38
      "txt", TEXT_PLAIN,
39
      "yml", TEXT_YAML
40
    );
41
42
    map.forEach( ( k, v ) -> assertEquals( v, fromFilename( "f." + k ) ) );
43
  }
44
45
  /**
46
   * Test that remote fetches will pull and identify the type of resource
47
   * based on the HTTP Content-Type header (or shallow decoding).
48
   */
49
  @Test
50
  public void test_HttpRequest_Supported_Success() {
51
    //@formatter:off
52
    final var map = Map.of(
53
       "https://kroki.io/robots.txt", TEXT_PLAIN,
54
       "https://place-hold.it/300x500", IMAGE_GIF,
55
       "https://loremflickr.com/200/300", IMAGE_JPEG,
56
       "https://upload.wikimedia.org/wikipedia/commons/9/9f/Vimlogo.svg", IMAGE_SVG_XML,
57
       "https://kroki.io//graphviz/svg/eNpLyUwvSizIUHBXqPZIzcnJ17ULzy_KSanlAgB1EAjQ", IMAGE_SVG_XML
58
    );
59
    //@formatter:on
60
61
    map.forEach( ( k, v ) -> {
62
      try( var response = open( k ) ) {
63
        System.out.printf( "%s => %s%n", k, v );
64
        assertEquals( v, response.getMediaType() );
65
      } catch( final Exception e ) {
66
        throw new RuntimeException( e );
67
      }
68
    } );
69
  }
70
}
171
A src/test/java/com/keenwrite/io/SysFileTest.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.io;
3
4
import org.junit.jupiter.api.Test;
5
6
import java.io.IOException;
7
import java.nio.file.Path;
8
import java.util.Optional;
9
import java.util.concurrent.atomic.AtomicBoolean;
10
import java.util.function.Function;
11
12
import static org.junit.jupiter.api.Assertions.*;
13
14
class SysFileTest {
15
16
  @Test
17
  void test_Locate_ExistingExecutable_PathFound() {
18
    testFunction( SysFile::locate, "env", "/usr/bin/env" );
19
  }
20
21
  @Test
22
  void test_Where_ExistingExecutable_PathFound() {
23
    testFunction( sysFile -> {
24
      try {
25
        return sysFile.where();
26
      } catch( final IOException e ) {
27
        throw new RuntimeException( e );
28
      }
29
    }, "which", "/usr/bin/which" );
30
  }
31
32
  void testFunction( final Function<SysFile, Optional<Path>> consumer,
33
                     final String command,
34
                     final String expected ) {
35
    final var file = new SysFile( command );
36
    final var path = consumer.apply( file );
37
    final var failed = new AtomicBoolean( false );
38
39
    assertTrue( file.canRun() );
40
41
    path.ifPresentOrElse(
42
      location -> {
43
        final var actual = location.toAbsolutePath().toString();
44
45
        assertEquals( expected, actual );
46
      },
47
      () -> failed.set( true )
48
    );
49
50
    assertFalse( failed.get() );
51
  }
52
}
153
A src/test/java/com/keenwrite/io/UserDataDirTest.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io;
6
7
import org.junit.jupiter.api.Test;
8
9
import static org.junit.jupiter.api.Assertions.assertFalse;
10
import static org.junit.jupiter.api.Assertions.assertTrue;
11
112
A src/test/java/com/keenwrite/io/WindowsRegistryTest.java
1
package com.keenwrite.io;
2
3
import org.junit.jupiter.api.Test;
4
5
import static com.keenwrite.io.WindowsRegistry.*;
6
import static org.junit.jupiter.api.Assertions.*;
7
8
class WindowsRegistryTest {
9
  private static final String REG_PATH_PREFIX =
10
    "%USERPROFILE%";
11
  private static final String REG_PATH_SUFFIX =
12
    "\\AppData\\Local\\Microsoft\\WindowsApps;";
13
  private static final String REG_PATH = REG_PATH_PREFIX + REG_PATH_SUFFIX;
14
15
  @Test
16
  void test_Parse_RegistryEntry_ValueObtained() {
17
    final var expected = REG_PATH;
18
    final var actual = parseRegEntry(
19
      "    path    REG_EXPAND_SZ    " + expected
20
    );
21
22
    assertEquals( expected, actual );
23
  }
24
25
  @Test
26
  void test_Expand_RegistryEntry_VariablesExpanded() {
27
    final var value = "UserProfile";
28
    final var expected = value + REG_PATH_SUFFIX;
29
    final var actual = expand( REG_PATH, s -> value );
30
31
    assertEquals( expected, actual );
32
  }
33
}
134
A src/test/java/com/keenwrite/io/downloads/DownloadManagerTest.java
1
/* Copyright 2023 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.io.downloads;
6
7
import org.junit.jupiter.api.Test;
18
9
import java.io.File;
10
import java.io.IOException;
11
import java.net.URISyntaxException;
12
import java.util.URL_BINARY =
13
    "https://keenwrite.com/downloads/KeenWfile = File.createTempFile( "kw-", "test" );
14
    file.deleteOnExit()_BINARYfileassertNull( future
A src/test/java/com/keenwrite/preferences/KeyTest.java
1
package com.keenwrite.preferences;
2
3
import org.junit.jupiter.api.Test;
4
5
import static com.keenwrite.preferences.Key.key;
6
import static org.junit.jupiter.api.Assertions.assertEquals;
7
8
/**
9
 * Test that {@link Key} hierarchies can be transformed into alternate data
10
 * models.
11
 */
12
class KeyTest {
13
  @Test
14
  public void test_String_ParentHierarchy_DotNotation() {
15
    final var keyRoot = key( "root" );
16
    final var keyMeta = key( keyRoot, "meta" );
17
    final var keyDate = key( keyMeta, "date" );
18
19
    final var expected = "root.meta.date";
20
    final var actual = keyDate.toString();
21
22
    assertEquals( expected, actual );
23
  }
24
}
125
A src/test/java/com/keenwrite/preview/DiagramUrlGeneratorTest.java
1
package com.keenwrite.preview;
2
3
import org.junit.jupiter.api.Test;
4
5
import static com.keenwrite.constants.Constants.DIAGRAM_SERVER_NAME;
6
import static com.keenwrite.preview.DiagramUrlGenerator.toUrl;
7
import static org.junit.jupiter.api.Assertions.assertEquals;
8
9
/**
10
 * Responsible for testing that images sent to the diagram server will render.
11
 */
12
class DiagramUrlGeneratorTest {
13
  // @formatter:off
14
  private static final String[] DIAGRAMS = new String[]{
15
    "graphviz",
16
    "digraph G {Hello->World; World->Hello;}",
17
    "https://kroki.io/graphviz/svg/eJxLyUwvSizIUHBXqPZIzcnJ17ULzy_KSbFWAFO6dmBB61oAE9kNww==",
18
19
    "blockdiag",
20
    """
21
      blockdiag {
22
        Kroki -> generates -> "Block diagrams";
23
        Kroki -> is -> "very easy!";
24
25
        Kroki [color = "greenyellow"];
26
        "Block diagrams" [color = "pink"];
27
        "very easy!" [color = "orange"];
28
      }
29
      """,
30
    "https://kroki.io/blockdiag/svg/eJxdzDEKQjEQhOHeU4zpPYFoYesRxGJ9bwghMSsbUYJ4d10UCZbDfPynolOek0Q8FsDeNCestoisNLmy-Qg7R3Blcm5hPcr0ITdaB6X15fv-_YdJixo2CNHI2lmK3sPRA__RwV5SzV80ZAegJjXSyfMFptc71w=="
31
  };
32
  // @formatter:on
33
34
  /**
35
   * Test that URL encoding works with Kroki's server.
36
   */
37
  @Test
38
  public void test_Generation_TextDiagram_UrlEncoded() {
39
    // Use a map of pairs if this test needs more complexity.
40
    for( int i = 0; i < DIAGRAMS.length / 3; i += 3 ) {
41
      final var name = DIAGRAMS[ i ];
42
      final var text = DIAGRAMS[ i + 1 ];
43
      final var expected = DIAGRAMS[ i + 2 ];
44
      final var actual = toUrl( DIAGRAM_SERVER_NAME, name, text );
45
46
      assertEquals( expected, actual );
47
    }
48
  }
49
}
150
A src/test/java/com/keenwrite/processors/html/XhtmlProcessorTest.java
1
package com.keenwrite.processors.html;
2
3
import com.keenwrite.ExportFormat;
4
import com.keenwrite.editors.common.Caret;
5
import com.keenwrite.processors.ProcessorContext;
6
import org.junit.jupiter.params.ParameterizedTest;
7
import org.junit.jupiter.params.provider.Arguments;
8
import org.junit.jupiter.params.provider.MethodSource;
9
10
import java.io.File;
11
import java.nio.file.Path;
12
import java.util.HashMap;
13
import java.util.stream.Stream;
14
15
import static com.keenwrite.ExportFormat.HTML_TEX_DELIMITED;
16
import static com.keenwrite.ExportFormat.XHTML_TEX;
17
import static com.keenwrite.processors.ProcessorContext.builder;
18
import static com.keenwrite.processors.ProcessorFactory.createProcessors;
19
import static java.util.Locale.ENGLISH;
20
import static org.junit.jupiter.api.Assertions.assertEquals;
21
22
public class XhtmlProcessorTest {
23
24
  /**
25
   * Contains the thumbs up emoji.
26
   */
27
  private static final String EMOJI_MARKDOWN = "the \uD83D\uDC4D emoji";
28
29
  @ParameterizedTest
30
  @MethodSource( "formatParameters" )
31
  void test_Conversion_EmojiInput_EncodedEmoji(
32
    final ExportFormat format, final String expected ) {
33
    final var context = createProcessorContext( format );
34
    final var processor = createProcessors( context );
35
    final var actual = processor.apply( EMOJI_MARKDOWN );
36
37
    assertEquals( expected, actual );
38
  }
39
40
  private static ProcessorContext createProcessorContext(
41
    final ExportFormat format ) {
42
    final var caret = Caret.builder().build();
43
    return builder()
44
      .with( ProcessorContext.Mutator::setExportFormat, format )
45
      .with( ProcessorContext.Mutator::setSourcePath, Path.of( "f.md" ) )
46
      .with( ProcessorContext.Mutator::setDefinitions, HashMap::new )
47
      .with( ProcessorContext.Mutator::setLocale, () -> ENGLISH )
48
      .with( ProcessorContext.Mutator::setMetadata, HashMap::new )
49
      .with( ProcessorContext.Mutator::setThemeDir, () -> Path.of( "b" ) )
50
      .with( ProcessorContext.Mutator::setCaret, () -> caret )
51
      .with( ProcessorContext.Mutator::setImageDir, () -> new File( "i" ) )
52
      .with( ProcessorContext.Mutator::setImageOrder, () -> "" )
53
      .with( ProcessorContext.Mutator::setImageServer, () -> "" )
54
      .with( ProcessorContext.Mutator::setSigilBegan, () -> "" )
55
      .with( ProcessorContext.Mutator::setSigilEnded, () -> "" )
56
      .with( ProcessorContext.Mutator::setRScript, () -> "" )
57
      .with( ProcessorContext.Mutator::setRWorkingDir, () -> Path.of( "r" ) )
58
      .with( ProcessorContext.Mutator::setCurlQuotes, () -> true )
59
      .with( ProcessorContext.Mutator::setAutoRemove, () -> true )
60
      .build();
61
  }
62
63
  private static Stream<Arguments> formatParameters() {
64
    return Stream.of(
65
      Arguments.of(
66
        HTML_TEX_DELIMITED,
67
        """
68
          <html><head></head><body><p>the 👍 emoji</p>
69
          </body></html>"""
70
      ),
71
      Arguments.of(
72
        XHTML_TEX,
73
        """
74
          <html><head><title/><meta charset="UTF-8"/><meta content="2" name="count"/></head><body><p>the 👍 emoji</p>
75
          </body></html>"""
76
      )
77
    );
78
  }
79
}
180
A src/test/java/com/keenwrite/processors/markdown/extensions/ExtensionTest.java
1
package com.keenwrite.processors.markdown.extensions;
2
api.TestInstanceTestInstance( TestInstance.Lifecycle.PER_CLASS )
3
public abstract class ExtensionTest {
4
  private final List<ParserExtension> mE>();
5
geExtensions_MarkdownmEmEotected void addExtension( final ParserExtension extension ) {
6
    mExtensions.add( extension );
7
  }
8
9
  protected Arguments argotected abstract Stream<Arguments> getDocuments();
10
}
111
A src/test/java/com/keenwrite/processors/markdown/extensions/fences/FencedDivExtensionTest.java
1
/* Copyright 2024 White Magic Software, Ltd. -- All rights reserved.
2
 *
3
 * SPDX-License-Identifier: MIT
4
 */
5
package com.keenwrite.processors.markdown.extensions.fences;
6
7
import com.keeExtensionTest;
8
import public class FencedDiv}
9
10
  @Override::: {.concurrent title="3:58"}
11
          > Line 1
12
          > :::::: {.concurrent title="3:59"}
13
          > Line 2
14
          > :::::: {.concurrent title="4:00"}
15
          > Line 3
16
          > :::div class="concurrent" data-title="3:58">
17
          <p>Line 1</p>
18
          </div><div class="concurrent" data-title="3:59">
19
          <p>Line 2</p>
20
          </div><div class="concurrent" data-title="4:00">
21
          <p>Line 3> Hello::: world
22
          > Adventuresin **absolute**nesting.
23
          > :::GoodbyeHello</p>
24
          <div class="world">
25
          <p>Adventures</p>
26
          <p>in <strong>absolute</strong></p>
27
          <p>nesting<p>Goodbye
28
    );
29
  }
30
}
131
A src/test/java/com/keenwrite/processors/markdown/extensions/images/ImageLinkExtensionTest.java
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.processorTR."\{URI_PATH}.png"JPG = STR."\{URI_PATH}.jpg";
17
8
  /*STR."//\{PLACEHOLDER}/200/200", STR."//\{PLACEHOLDER}/200/200" );
9
    add( STR."ftp://\{PLACEHOLDER}/200/200",  STR."ftp://\{PLACEHOLDER}/200/200" );
10
    add( STR."http://\{PLACEHOLDER}/200/200", STR."http://\{PLACEHOLDER}/200/200" );
11
    add( STR."https://\{PLACEHOLDER}/200/200", STR."https://\{PLACEHOLDER}/200/200"
A src/test/java/com/keenwrite/processors/markdown/extensions/references/CaptionsAndCrossReferencesExtensionTest.java
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.ExtensionTest;
8
import com.keenwrite.processors.markdown.extensions.captions.CaptionExtension;
9
imapi.BeforeAllextends ExtensionTest {
10
  protected Stream<Arguments> ge                                                                                                              @BeforeAll
11
  protected void setup() {addExtensionaddExtension( Defini    addExtensionaddExtension( addExtensionaddExtensionaddExtensionaddExtension  }
12
}
113
A src/test/java/com/keenwrite/r/PluralizeTest.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.r;
3
4
import org.junit.jupiter.api.BeforeAll;
5
import org.junit.jupiter.api.Test;
16
7
import javax.script.ScriptEngine;
8
import javax.script.ScriptEngineManager;
9
iod
A src/test/java/com/keenwrite/richtext/StyleClassedTextAreaTest.java
1
package com.keenwrite.richtext;
12
3
import javafx.application.Application;
4
import javafx.scene.Scene;
5
import javafx.scene.layout.StackPane;
6
import javafx.stage.Stage;
7
import org.fxmisc.flowless.VirtualizedScrollPane;
8
import org.fxmisc.richtext.StyleClassedTestatic final
A src/test/java/com/keenwrite/sigils/RKeyOperatorTest.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.sigils;
3
4
import org.junit.jupiter.api.Test;
5
6
import static oKeyOperatorTest {Process_KeyName_ProcessmOperator = new RKeyOperator();apply( "a.b.c.d" );
7
}
18
A src/test/java/com/keenwrite/util/AlphanumComparatorTest.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
3
4
import org.junit.jupiter.api.Test;
5
6
import java.util.ArrayList;
7
import java.util.Arrays;
8
import java.util.Collections;
9
10
import static org.junit.jupiter.api.Assertions.assertEquals;
11
12
/**
13
 * Responsible for testing the http://www.davekoelle.com/alphanum.html
14
 * implementation.
15
 */
16
class AlphanumComparatorTest {
17
18
  /**
19
   * Test that a randomly sorted list containing a mix of alphanumeric
20
   * characters ("chunks") will be sorted according to numeric and alphabetic
21
   * order.
22
   */
23
  @Test
24
  public void test_Sort_UnsortedList_SortedAlphanumerically() {
25
    final var expected = Arrays.asList(
26
      "10X Radonius",
27
      "20X Radonius",
28
      "20X Radonius Prime",
29
      "30X Radonius",
30
      "40X Radonius",
31
      "200X Radonius",
32
      "1000X Radonius Maximus",
33
      "Allegia 6R Clasteron",
34
      "Allegia 50 Clasteron",
35
      "Allegia 50B Clasteron",
36
      "Allegia 51 Clasteron",
37
      "Allegia 500 Clasteron",
38
      "Alpha 2",
39
      "Alpha 2A",
40
      "Alpha 2A-900",
41
      "Alpha 2A-8000",
42
      "Alpha 100",
43
      "Alpha 200",
44
      "Callisto Morphamax",
45
      "Callisto Morphamax 500",
46
      "Callisto Morphamax 600",
47
      "Callisto Morphamax 700",
48
      "Callisto Morphamax 5000",
49
      "Callisto Morphamax 6000 SE",
50
      "Callisto Morphamax 6000 SE2",
51
      "Callisto Morphamax 7000",
52
      "Xiph Xlater 5",
53
      "Xiph Xlater 40",
54
      "Xiph Xlater 50",
55
      "Xiph Xlater 58",
56
      "Xiph Xlater 300",
57
      "Xiph Xlater 500",
58
      "Xiph Xlater 2000",
59
      "Xiph Xlater 5000",
60
      "Xiph Xlater 10000"
61
    );
62
    final var actual = new ArrayList<>( expected );
63
64
    Collections.shuffle( actual );
65
    actual.sort( new AlphanumComparator<>() );
66
    assertEquals( expected, actual );
67
  }
68
}
169
A src/test/java/com/keenwrite/util/CircularQueueTest.java
1
package com.keenwrite.util;
2
3
import com.keenwrite.collections.CircularQueue;
4
import org.junit.jupiter.api.Test;
5
6
import static org.junit.jupiter.api.Assertions.assertEquals;
7
8
/**
9
 * Tests the {@link CircularQueue} class.
10
 */
11
public class CircularQueueTest {
112
13
  /**
14
   * Exercises the circularity aspect 
A src/test/java/com/keenwrite/util/CyclicIteratorTest.java
1
/* Copyright 2020-2021 White Magic Software, Ltd. -- All rights reserved. */
2
package com.keenwrite.util;
3
4
import org.junit.jupiter.api.Test;
5
6
import java.util.List;
7
import java.util.ListIterator;
8
import java.util.NoSuchElementException;
19
10
import static org.juni
A src/test/java/com/keenwrite/util/RangeValidatorTest.java
1
package com.keenwrite.util;
2
3
import org.junit.jupiter.api.Test;
4
5
import static org.junit.jupiter.api.Assertions.*;
6
7
/**
8
 * Tests that the range format specifiers correctly identify integer values
9
 * inside and outside the range.
10
 */
11
class RangeValidatorTeEmptyRange_AllValid() {"2Integer.MAX_VALUE ) );
12
  }
13
}
114
A src/test/resources/com/keenwrite/io/images/example.bmp
Binary file
A src/test/resources/com/keenwrite/io/images/example.eps
Binary file
A src/test/resources/com/keenwrite/io/images/example.gif
Binary file
A src/test/resources/com/keenwrite/io/images/example.jpg
Binary file
A src/test/resources/com/keenwrite/io/images/example.png
Binary file
A src/test/resources/com/keenwrite/io/images/example.svg
1
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 34" width="68px" height="34px" fill="#005a9c">
2
<desc>W3C</desc>
3
<path d="m16.117 1.006 5.759 19.58 5.759-19.58h4.17 11.444v1.946l-5.879 10.128c2.065.663 3.627 1.868 4.686 3.615 1.059 1.748 1.589 3.799 1.589 6.155 0 2.914-.775 5.363-2.324 7.348s-3.555 2.978-6.017 2.978c-1.854 0-3.469-.589-4.845-1.767-1.377-1.178-2.396-2.773-3.058-4.786l3.256-1.35c.477 1.218 1.106 2.178 1.887 2.879.781.702 1.701 1.052 2.76 1.052 1.112 0 2.052-.622 2.82-1.866.768-1.245 1.152-2.74 1.152-4.489 0-1.933-.411-3.429-1.231-4.488-.954-1.244-2.45-1.867-4.489-1.867h-1.588v-1.906l5.56-9.612h-6.712l-.382.65-8.163 27.548h-.397l-5.958-19.937-5.957 19.937h-.397l-9.53-32.168h4.17l5.759 19.58 3.892-13.185-1.906-6.395z"/>
4
<path d="m64.92 1.006c-.819 0-1.554.295-2.111.861-.591.6-.92 1.376-.92 2.178s.313 1.545.887 2.128c.583.591 1.334.912 2.145.912.793 0 1.562-.321 2.161-.903.574-.557.887-1.3.887-2.136 0-.811-.321-1.57-.878-2.136-.584-.592-1.344-.904-2.171-.904zm2.643 3.065c0 .701-.271 1.351-.768 1.832-.524.507-1.174.777-1.892.777-.675 0-1.342-.278-1.84-.785s-.777-1.157-.777-1.849.287-1.368.802-1.891c.481-.49 1.131-.751 1.84-.751.726 0 1.376.271 1.883.785.49.489.752 1.147.752 1.882zm-2.559-1.807h-1.3v3.445h.65v-1.469h.642l.701 1.469h.726l-.769-1.57c.498-.102.785-.439.785-.929 0-.625-.472-.946-1.435-.946zm-.118.422c.608 0 .886.169.886.591 0 .405-.278.549-.87.549h-.549v-1.14z"/>
5
<path d="m59.807.825.676 4.107-2.391 4.575s-.918-1.941-2.443-3.015c-1.285-.905-2.122-1.102-3.431-.832-1.681.347-3.587 2.357-4.419 4.835-.995 2.965-1.005 4.4-1.04 5.718-.056 2.113.277 3.362.277 3.362s-1.452-2.686-1.438-6.62c.009-2.808.451-5.354 1.75-7.867 1.143-2.209 2.842-3.535 4.35-3.691 1.559-.161 2.791.59 3.743 1.403 1 .854 2.01 2.721 2.01 2.721z"/>
6
<path d="m60.102 24.063s-1.057 1.889-1.715 2.617c-.659.728-1.837 2.01-3.292 2.651s-2.218.762-3.656.624c-1.437-.138-2.772-.97-3.24-1.317s-1.664-1.369-2.34-2.322-1.733-2.859-1.733-2.859.589 1.91.958 2.721c.212.467.864 1.894 1.789 3.136.863 1.159 2.539 3.154 5.086 3.604 2.547.451 4.297-.693 4.73-.97s1.346-1.042 1.924-1.66c.603-.645 1.174-1.468 1.49-1.962.231-.36.607-1.092.607-1.092z"/>
7
</svg>
18
A src/test/resources/com/keenwrite/io/images/example.xbm
1
#define 1617524430813_width 72
2
#define 1617524430813_height 48
3
static char 1617524430813_bits[] = {
4
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 
5
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
6
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
7
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 
8
  0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
9
  0x00, 0x00, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 
10
  0x10, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0xAA, 0x78, 0x80, 0x07, 
11
  0xC0, 0xFF, 0x3F, 0xE0, 0x41, 0x44, 0x78, 0x80, 0x0F, 0xC0, 0xFF, 0x3F, 
12
  0xF8, 0x67, 0x18, 0xF0, 0x80, 0x07, 0xE0, 0xFF, 0x3F, 0xF8, 0xE7, 0x00, 
13
  0xE0, 0x00, 0x0F, 0xE0, 0xFF, 0x1F, 0xFC, 0xFF, 0x00, 0xF0, 0x01, 0x0F, 
14
  0xE0, 0x01, 0x1D, 0xFC, 0x7F, 0x00, 0xF0, 0x01, 0x1F, 0xE0, 0x81, 0x0F, 
15
  0x3E, 0x7C, 0x00, 0xE0, 0x01, 0x1E, 0xE0, 0x81, 0x07, 0x0E, 0x38, 0x00, 
16
  0xE0, 0x01, 0x17, 0xF0, 0xC0, 0x07, 0x0F, 0x30, 0x00, 0xE0, 0x03, 0x3F, 
17
  0xF0, 0xC0, 0x03, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x3F, 0xF8, 0xE0, 0x03, 
18
  0x07, 0x00, 0x00, 0xC0, 0x83, 0x3F, 0x78, 0xF0, 0x01, 0x07, 0x00, 0x00, 
19
  0xC0, 0x83, 0x3F, 0x78, 0xB0, 0x00, 0x03, 0x00, 0x00, 0x80, 0x87, 0x7F, 
20
  0x78, 0xF8, 0x03, 0x03, 0x00, 0x00, 0x80, 0x87, 0x7B, 0x78, 0xFC, 0x07, 
21
  0x03, 0x00, 0x00, 0x80, 0xCF, 0x7B, 0x3C, 0xF8, 0x0F, 0x03, 0x00, 0x00, 
22
  0x80, 0xC7, 0xE3, 0x3E, 0xC8, 0x1F, 0x02, 0x00, 0x00, 0x00, 0xEF, 0xF2, 
23
  0x3C, 0x00, 0x1F, 0x02, 0x00, 0x00, 0x00, 0xEF, 0xF1, 0x1C, 0x00, 0x3E, 
24
  0x02, 0x00, 0x00, 0x00, 0xEF, 0xB1, 0x1F, 0x00, 0x3C, 0x00, 0x00, 0x00, 
25
  0x00, 0xFE, 0xE1, 0x1F, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0xF6, 0xE0, 
26
  0x17, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xE0, 0x0F, 0x00, 0x3C, 
27
  0x00, 0x00, 0x00, 0x00, 0x7E, 0xC0, 0x0F, 0x00, 0x7C, 0x00, 0x00, 0x00, 
28
  0x00, 0xFC, 0xC0, 0x0F, 0x00, 0xFC, 0x00, 0x40, 0x00, 0x00, 0x7C, 0x80, 
29
  0x07, 0x03, 0xBC, 0x00, 0x60, 0x00, 0x00, 0x7C, 0xC0, 0xC7, 0x01, 0x3E, 
30
  0x03, 0xE0, 0x00, 0x00, 0x18, 0x80, 0xC7, 0x07, 0x1E, 0x03, 0x70, 0x00, 
31
  0x00, 0x38, 0x80, 0x82, 0x87, 0x1F, 0x0E, 0x7C, 0x00, 0x00, 0x38, 0x00, 
32
  0x03, 0xFF, 0x1D, 0xBC, 0x3F, 0x00, 0x00, 0x30, 0x00, 0x03, 0xFF, 0x0F, 
33
  0xFC, 0x1F, 0x00, 0x00, 0x10, 0x00, 0x03, 0xFC, 0x07, 0xF0, 0x1F, 0x00, 
34
  0x00, 0x10, 0x00, 0x01, 0xF8, 0x01, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x00, 
35
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
36
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
37
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
38
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
39
  0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
40
  };
141
A src/test/resources/com/keenwrite/io/images/example_2.bmp
Binary file
A src/test/resources/com/keenwrite/io/images/example_2.gif
Binary file
A src/test/resources/com/keenwrite/io/images/example_2.jpg
Binary file
A src/test/resources/com/keenwrite/io/images/example_2.png
Binary file
A src/test/resources/com/keenwrite/io/images/example_256.bmp
Binary file
A src/test/resources/com/keenwrite/io/images/example_256.gif
Binary file
A src/test/resources/com/keenwrite/io/images/example_256.jpg
Binary file
A src/test/resources/com/keenwrite/io/images/example_256.png
Binary file
A src/test/resources/com/keenwrite/io/images/example_animation.gif
Binary file
A src/test/resources/com/keenwrite/io/images/example_animation.mng
Binary file
A src/test/resources/com/keenwrite/io/images/example_gray.bmp
Binary file
A src/test/resources/com/keenwrite/io/images/example_gray.gif
Binary file
A src/test/resources/com/keenwrite/io/images/example_gray.jpg
Binary file
A src/test/resources/com/keenwrite/io/images/example_gray.png
Binary file
A src/test/resources/com/keenwrite/processors/markdown/images/kitten.jpg
Binary file
A src/test/resources/com/keenwrite/processors/markdown/images/kitten.png
Binary file
A src/test/resources/com/keenwrite/processors/markdown/workspace.xml
1
<?xml version="1.0" encoding="UTF-8"?>
2
<keenwrite>
3
   <workspace>
4
      <images>
5
         <order>svg pdf png jpg tiff</order>
6
         <dir></dir>
7
      </images>
8
   </workspace>
9
</keenwrite>
110
A src/test/sikuli/.gitignore
1
*.class
12
A src/test/sikuli/README.md
1
Sikuli is used for the following purposes:
2
3
* Create application videos.
4
* Create integration tests.
5
16
A src/test/sikuli/demo.sikuli/1594187265140.png
Binary file
A src/test/sikuli/demo.sikuli/1594592396134.png
Binary file
A src/test/sikuli/demo.sikuli/1594593710440.png
Binary file
A src/test/sikuli/demo.sikuli/1594593794335.png
Binary file
A src/test/sikuli/demo.sikuli/1594594984108.png
Binary file
A src/test/sikuli/demo.sikuli/1594689573764.png
Binary file
A src/test/sikuli/demo.sikuli/demo.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation fiRuns all scripts
6
import s01
7
import s02
8
import s03
9
import s04
110
A src/test/sikuli/demo.sikuli/s01.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation fiintroduces the editor and its purpose.
6
# Fresh start
7
rm( app_home + "/variables.yaml" )
8
rm( app_home + "/untitled.md" )
9
rm( dir_home + "/.scrivenvar
10
# Wait for application to launch
11
openApp( "java -jar " + app_bin )
12
13
wait("1594187265140.png", 30)
14
15
# Breathing room for video recording.
16
wait( 4 )
17
18
# ---------
19
# Introduc240 )
20
21
heading( "What is this application?" )
22
typer( "Well, this application is a text editor that supports interpolated definitions, ")
23
typer( "a few different text formats, real-time preview, spell check ") 
24
typer( "as you tipe" ) 
25
wait( 0.5 )
26
recur( 3ype, and R statements." )
27
paragraph()
28
wait( 1finition demo
29
heading( "What are definitions?" )
30
typer( "Watch. " )
31
wait( .5 )
32
33
# Focus the definition editor.
34
click_create()
35
recur( 4, tab )
36
37
wait( .5application" )
38
title" )
39
Scrivenvar" )
40
41
# Set focus to the text editor.
42
tab()
43
44
typer( "The left-hand pane contains a nested, folder-like structure of names " )
45
typer( "and values that are called *definitions*. " )
46
wait( .5 )
47
typer( "Such definitions can simplify updating documents. " )
48
wait( 1 )
49
50
edit_find( "this application" )
51
typer( "$application.title$" )
52
53
edit_find_next()
54
typer( "$application.title$" )
55
typer( "Tresult after having substituted definition " )
56
typer( "values into the document." ) 
57
Now nobody wants to type definition names all the time. Instead, type any " )
58
typer( "partial definition value followed by `Ctrl+Space`, such as: scr" )
59
wait( 0.5 )
60
autoinsert()
61
wait( 1 )
62
typer( ". *Much* better!" )
63
paragraph()
64
65
heading( "What is interpolation?" )
66
typer( "Definition values can reference definition names. " )
67
wait( .5 )
68
typer( "The definition names act as placeholders. Substituting placeholders with " )
69
typer( "their definition value is called *interpolation*. Let's see how it works." )
70
wait( 2 )
171
A src/test/sikuli/demo.sikuli/s02.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation fihow to use interpolated strings.Open sample chapter.1 )
6
typer( Key.END1O1Edit the sample document.80 )
7
2, down )
8
9
# Grey
10
recur( 3, skip_right )
11
autoinsert()
12
13
# 34
14
recur( 4, skip_right )
15
autoinsert()
16
17
# Central
18
recur( 10, skip_right )
19
autoinsert()
20
21
# London
22
skip_right()
23
autoinsert()
24
25
# Hatchery
26
skip_right()
27
autoinsert()
28
29
# and Conditioning
30
recur( 2, select_word_right )
31
delete()
32
33
# Centre
34
skip_right()
35
autoinsert(
36
typer( " Let's interpolate those four definitions instead!" )
37
wait( 4 )
38
recur( 13, type, Key.BACKSPACE9, backspace60 )
39
40
typer( "name$" )
41
wait( 2 )
42
43
# Collapse all definitions
44
tab()
45
recur( 8, typer, Key.LEFT )
46
47
# Expand to city
48
recur( 4, typer, Key.RIGHT )
49
50
# Jump to name
51
recur( 2, down )
52
recur( 2, typer, Key.RIGHT )
53
54
# Open the text field to show the full value
55
typer( Key.F2 )
56
57
# Traverse the text field
58
home()
59
recur( 16, type, Key.RIGHT, Key.CTRL )
60
esc()
61
62
restore_typing_speed()
63
64
tab()
65
type(_next()
66
typertyper( "'s" )
67
68
edit_find( "Hatcheries" )
69
autoinsert()
70
71
# and Conditioning
72
recur( 2, select_word_right )
73
delete()
74
75
edit_find( "Central" )
76
autoinsert()
77
78
skip_right()
79
autoinsert()
80
81
typer( " How about a different city?" )
82
wait( 2 )
83
recur( 5, type, Key.BACKSPACE, Key.CTRL )
84
wait( 1 )
85
tab()
86
typer( Key.F2 )
87
typer( "Seattle" )
88
enter()
89
tab()
90
wait( 2 )
91
No?" )
92
paragraph()
93
94
tab()
95
typer( Key.F2 )
96
typer( "London" )
97
enter()
98
99
tab()
100
typer( "Organizing definitions is left to your ")
101
typer( "doub" )
102
autoinsert()
103
typer( " Good imagination." )
104
tab()
105
106
# Jump to "char" definition
107
home()
108
109
# Jump to "char.a.primary.name" definition
110
recur( 6, typer, Key.RIGHT )
111
112
# Jump to "char.a.primary.caste" definition
113
down()
114
typer( Key.RIGHT )
115
116
# Jump to root-level "caste" definition
117
recur( 7, down )
118
119
# Reselect "super"
120
recur( 5, typer, Key.RIGHT )
121
wait( 2 )
122
123
# Close the window, no save
124
type( "w0.5 )
125
tab()
126
wait( 0.5 )
127
typer( Key.SPACE )
128
wait( 1 )
1129
A src/test/sikuli/demo.sikuli/s03.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script introduces images and R.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
set_typing_speed( 80 )
36
37
file_open()
38
type( Key.UP, Key.ALT )
39
wait( 0.5 )
40
home()
41
wait( 0.25 )
42
enter()
43
wait( 1 )
44
end()
45
wait( 0.25 )
46
enter()
47
wait( 1 )
48
49
set_typing_speed( 200 )
50
51
paragraph()
52
heading( "What text formats are supported?" )
53
54
typer( "Scr" )
55
autoinsert()
56
typer( " supports Markdown, R Markdown, XML, and R XML; however, the software " )
57
typer( "architecture enables it to easily add new formats. The following figure " )
58
typer( "depicts the overall architecture: " )
59
paragraph()
60
typer( "![](../writing/images/architecture)" )
61
paragraph()
62
typer( "Many text editors can only open one type of plain text markup format that is " )
63
typer( "only output as HTML. With a little more effort, text editors could support " )
64
typer( "multiple input and output formats. Scr" )
65
autoinsert()
66
typer( " does so and goes one step further by introducing interpolated definitions." )
67
paragraph()
68
typer( "Kitten interlude:" )
69
paragraph()
70
typer( "![](https://i.imgur.com/jboueQH.jpg)" )
71
paragraph()
72
73
heading( "What is R?" )
74
typer( "R is a programming language. You might have noticed a few potential grammar " )
75
typer( "problems with direct substitution. Rules for possessive forms, numbers, and " )
76
typer( "other quirks can be tackled using R." )
77
78
# -----------------------------------------------------------------------------
79
# Demo bootstrapping
80
# -----------------------------------------------------------------------------
81
82
# Jump to the end
83
type( Key.END, Key.CTRL )
84
paragraph()
85
86
set_typing_speed( 300 )
87
heading( "How is R used?" )
88
typer( "R must be instructed where to find script files and what ones to load. The " )
89
typer( "*working directory* is the full path to those R files; the *startup script* " )
90
typer( "defines what R files to load. Both preferences must be changed before prose " )
91
typer( "may be processed. Preferences can be opened using either the " )
92
typeln( "**Edit > Preferences** menu or by pressing `Ctrl+Alt+s`. Here goes!" ) 
93
wait( 2 )
94
95
# -----------------------------------------------------------------------------
96
# Select the R script directory
97
# -----------------------------------------------------------------------------
98
99
# Change the working directory by clicking "Browse"
100
type( "s", Key.CTRL + Key.ALT )
101
wait("1594592396134.png", 1)
102
click("1594592396134.png")
103
wait( 0.5 )
104
105
# Navigate to and select the "r" directory
106
type( Key.UP, Key.ALT )
107
wait( 0.5 )
108
end()
109
wait( 0.5 )
110
enter()
111
wait( 0.5 )
112
end()
113
wait( 0.5 )
114
type( Key.UP )
115
wait( 0.5 )
116
recur( 2, tab )
117
wait( 0.5 )
118
enter()
119
wait( 1 )
120
121
# -----------------------------------------------------------------------------
122
# Set the R startup script instructions
123
# -----------------------------------------------------------------------------
124
125
wait("1594593710440.png", 5)
126
click("1594593710440.png")
127
128
set_typing_speed( 440 )
129
130
typeln( "setwd( '$application.r.working.directory$' )" )
131
typeln( "assign( 'anchor', '$date.anchor$', envir = .GlobalEnv )" )
132
typeln( "source( 'pluralize.R' )" )
133
typeln( "source( 'possessive.R' )" )
134
typeln( "source( 'conversion.R' )" )
135
typeln( "source( 'csv.R' )" )
136
137
wait("1594593794335.png", 3)
138
click("1594593794335.png")
139
140
paragraph()
141
set_typing_speed( 220 )
142
143
typer( "R is now configured. The startup script and other R " )
144
typer( "files can be found in the " )
145
typer( "[repository](https://github.com/DaveJarvis/scrivenvar/tree/master/R). " )
146
wait( 1.5 )
147
148
# Wait for the browser to appear.
149
wait("1594594984108.png", 5)
150
click("1594594984108.png")
151
152
wait( 5 )
153
click("1594689573764.png")
154
155
paragraph()
156
typer( "Next, we'll see how definitions and R can work together." )
157
wait( 2 )
1158
A src/test/sikuli/demo.sikuli/s04.py
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
#
12
# The above copyright notice and this permission notice shall be included
13
# in all copies or substantial portions of the Software.
14
#
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# This script demonstrates using R.
26
# -----------------------------------------------------------------------------
27
from sikuli import *
28
import sys
29
30
if not "../editor.sikuli" in sys.path:
31
    sys.path.append( "../editor.sikuli" )
32
33
from editor import *
34
35
set_typing_speed( 220 )
36
37
# -----------------------------------------------------------------------------
38
# Open the demo text.
39
# -----------------------------------------------------------------------------
40
file_open()
41
type( Key.UP, Key.ALT )
42
wait( 0.5 )
43
end()
44
wait( 0.5 )
45
enter()
46
wait( 0.5 )
47
down()
48
wait( 0.5 )
49
enter()
50
wait( 2 )
51
52
# -----------------------------------------------------------------------------
53
# Re-open the corresponding definition file.
54
# -----------------------------------------------------------------------------
55
file_open()
56
recur( 2, down )
57
wait( 1 )
58
enter()
59
wait( 2 )
60
61
# -----------------------------------------------------------------------------
62
# Brief introduction to R
63
# -----------------------------------------------------------------------------
64
type( Key.HOME, Key.CTRL )
65
end()
66
paragraph()
67
68
typer( "## Using R" )
69
paragraph()
70
typer( "Insert R code into documents as follows: `r# 1+1`. " )
71
wait( 1.5 )
72
typer( "Notice how the right-hand pane shows the computed result. I'll wait. " )
73
wait( 3 )
74
typer( "The syntax is: open backtick, r#, *computable expression*, close " )
75
typer( "backtick. That expression can be any valid R statement. The status bar " ) 
76
typer( "will provide clues when an R expression cannot be computed by the " )
77
typer( "editor. `r# glitch`" )
78
wait( 4 )
79
recur( 11, backspace )
80
typer( "Let's swap 34 storeys for a definition value and replace the number " )
81
typer( "according to the Chicago Manual of Style (cms) rules." )
82
83
# -----------------------------------------------------------------------------
84
# Demo pluralization
85
# -----------------------------------------------------------------------------
86
set_typing_speed( 80 )
87
88
edit_find( "34" )
89
autoinsert()
90
91
edit_find( "x(" )
92
typer( "cms(" )
93
94
edit_find( "storeys." )
95
typer( "34." )
96
autoinsert()
97
edit_find( "x(" )
98
typer( "pl( 'storey'," )
99
wait( 4 )
100
101
tab()
102
rename_definition( "1" )
103
wait( 4 )
104
rename_definition( "142" )
105
wait( 4 )
106
rename_definition( "34" )
107
wait( 4 )
108
tab()
109
110
# -----------------------------------------------------------------------------
111
# Demo possessives (it, her, his, Director)
112
# -----------------------------------------------------------------------------
113
type( Key.HOME, Key.CTRL )
114
edit_find( "Director" )
115
autoinsert()
116
edit_find_next()
117
autoinsert()
118
edit_find_next()
119
autoinsert()
120
type( Key.RIGHT )
121
recur( 2, delete )
122
autoinsert()
123
home()
124
edit_find( "x(" )
125
typer( "pos(" )
126
wait( 2 )
127
128
tab()
129
rename_definition( "Headmistress" )
130
wait( 4 )
131
rename_definition( "Director" )
132
wait( 2 )
133
tab()
134
135
type( Key.END, Key.CTRL )
136
paragraph()
137
typer( "Other possessives: `r# pos( 'it' )`, `r# pos( 'her' )`, `r# pos( 'his' )`, " )
138
typer( "and `r# pos( 'my' )`." )
139
140
# -----------------------------------------------------------------------------
141
# Demo conversion, including ordinal numbers
142
# -----------------------------------------------------------------------------
143
set_typing_speed( 160 )
144
145
paragraph()
146
heading( "Date Conversions" )
147
typer( "Mixing R code with definitions invites endless possibilities. " )
148
typer( "Imagine someone racing to the " ) 
149
typer( "`r#cms( v$location$breeder$storeys, ordinal=TRUE )` floor, whereby that " )
150
typer( "ordinal stems from the Hatchery's storeys' definition. Or how about " )
151
typer( "a complex timeline where dates are expressed in days relative to one " )
152
typer( "point in time. Let's call this the *anchor date* and define it." )
153
154
tab()
155
home()
156
typer( Key.SPACE )
157
insert()
158
rename_definition( "date" )
159
insert()
160
rename_definition( "anchor" )
161
insert()
162
rename_definition( "1969-10-29" )
163
tab()
164
165
paragraph()
166
typer( "Next, set an R variable named `now` to the current date" )
167
typer( "`r# now = format( Sys.time(), '%Y-%m-%d' ); ''`--- the empty single quotes " )
168
typer( "prevent the date from appearing in the output document. " )
169
170
paragraph()
171
typer( "We set the anchor date to `r# annal()`, which was " )
172
typer( "`r# elapsed( 0, days( v$date$anchor, format( Sys.time(), '%Y-%m-%d' ) ) )` " )
173
typer( "ago from `r# format( as.Date( now ), '%B %d, %Y' )`. " )
174
175
# -----------------------------------------------------------------------------
176
# Demo CSV file import
177
# -----------------------------------------------------------------------------
178
paragraph()
179
heading( "Tabular Data" )
180
typer( "The following table shows average Canadian lifespans by birth " )
181
typer( "year and sex:" )
182
paragraph()
183
typer( "`r# csv2md( '../data.csv', total=FALSE )`" )
184
paragraph()
185
typer( "Calling `csv2md` converts the comma-separated values in the spreadsheet " )
186
typer( "to a table formatted using Markdown. The HTML preview pane changes the " )
187
typer( "appearance of the resulting table. Using `../data.csv` instructs R to " )
188
typer( "open `data.csv` from one directory above the *working directory*." )
189
190
# -----------------------------------------------------------------------------
191
# Demo HTML export
192
# -----------------------------------------------------------------------------
193
paragraph()
194
heading( "Export" )
195
typer( "Retrieve the output HTML by using the **Edit > Copy HTML** menu. Let's " )
196
typer( "peek at the output." )
197
wait( 2 )
198
199
type( "e", Key.ALT )
200
wait( 0.5 )
201
down()
202
wait( 0.25 )
203
enter()
204
wait( 0.25 )
205
206
type( "a", Key.CTRL )
207
wait( 0.25 )
208
type( "v", Key.CTRL )
209
wait( 5 )
210
211
set_typing_speed( 40 )
212
213
# Jump to page bottom (should already be there, but just in case)
214
type( Key.END, Key.CTRL )
215
recur( 3, typer, Key.PAGE_UP )
216
type( Key.HOME, Key.CTRL )
217
wait( 3 )
218
219
set_typing_speed( 220 )
220
type( "z", Key.CTRL )
221
type( Key.END, Key.CTRL )
222
223
paragraph()
224
typer( "That's all for now, thank you!" )
225
wait( 5 )
226
227
# Delete the anchor date.
228
tab()
229
end()
230
recur( 2, type, Key.UP )
231
delete()
232
tab()
1233
A src/test/sikuli/demo.sikuli/test.py
1
from sikuli import *
2
3
import sys
4
import os
5
6
def set_class_path():
7
    path_script = getBundlePath()
8
    dir_script = os.path.dirname( path_script )
9
    path_lib = dir_script + "/keycast/build/libs/keycast.jar"
10
    
11
    sys.path.append( path_lib )
12
13
def launch():
14
    from com.whitemagicsoftware.keycast import KeyCast
15
    kc = KeyCast()
16
    kc.show()
17
18
def main():
19
    set_class_path()
20
    launch()
21
   
22
23
if __name__ == "__main__":
24
    main()
125
A src/test/sikuli/editor.sikuli/1594187923258.png
Binary file
A src/test/sikuli/editor.sikuli/editor.py
1
1
# -----------------------------------------------------------------------------
2
# Copyright 2020 White Magic Software, Ltd.
3
#
4
# Permission is hereby granted, free of charge, to any person obtaining a
5
# copy of this software and associated documentation fityping_speed = 80
6
 *args 
A www/LICENSE
1
MIT License
2
3
Copyright (c) 2022 KeenWrite
4
5
Permission is hereby granted, free of charge, to any person obtaining a copy
6
of this software and associated documentation files "Software"), to deal
7
8
9
10
11
 included in all
12
13
 EXPRESS OR
14
 MERCHANTABILITY,
15
16
17
LIABILITY, WHETHER IN AN ACTION
18
OUT OF OR IN CONNECTION
19
SOFTWARE.
120
A www/README.md
11
2
![KeenWrite logo](images/logo/icon.png)
3
4
A www/count.sh
1
#!/usr/bin/env bash
2
3
awk '{s+=$1} END {print s}' downloads/*-count.txt 2> /dev/null || echo 0
4
15
A www/downloads/.gitignore
1
version.txt
12
A www/downloads/.htaccess
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
18
9
# Rewrite requests for file extensions t
A www/downloads/counter.php
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' );
110
11
  // 
A www/favicon.ico
11
A www/images/icons/apple.svg
1
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>
A www/images/icons/java.svg
1
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>
A www/images/icons/linux.svg
1
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>
A www/images/icons/windows.svg
1
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>
A www/images/logo/icon.png
Binary file
A www/images/logo/icon.svg
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<svg
3
   height="493.14542"
4
   viewBox="0 0 500.05118 493.14542"
5
   width="500.05118"
6
   version="1.1"
7
   id="svg37"
8
   sodipodi:docname="logo-icon.svg"
9
   inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
10
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12
   xmlns="http://www.w3.org/2000/svg"
13
   xmlns:svg="http://www.w3.org/2000/svg"
14
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
15
   xmlns:cc="http://creativecommons.org/ns#"
16
   xmlns:dc="http://purl.org/dc/elements/1.1/">
17
  <sodipodi:namedview
18
     id="namedview18"
19
     pagecolor="#ffffff"
20
     bordercolor="#666666"
21
     borderopacity="1.0"
22
     inkscape:showpageshadow="2"
23
     inkscape:pageopacity="0.0"
24
     inkscape:pagecheckerboard="0"
25
     inkscape:deskcolor="#d1d1d1"
26
     showgrid="false"
27
     inkscape:zoom="2.1352728"
28
     inkscape:cx="250.3193"
29
     inkscape:cy="246.57271"
30
     inkscape:current-layer="svg37" />
31
  <metadata
32
     id="metadata43">
33
    <rdf:RDF>
34
      <cc:Work
35
         rdf:about="">
36
        <dc:format>image/svg+xml</dc:format>
37
        <dc:type
38
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
39
      </cc:Work>
40
    </rdf:RDF>
41
  </metadata>
42
  <defs
43
     id="defs41">
44
    <linearGradient
45
       id="a"
46
       gradientTransform="matrix(-8.7796153,42.985832,-42.985832,-8.7796153,514.83476,136.06192)"
47
       gradientUnits="userSpaceOnUse"
48
       x1="0.152358"
49
       x2="0.96880901"
50
       y1="-0.044911999"
51
       y2="-0.049470998">
52
      <stop
53
         offset="0"
54
         stop-color="#ec706a"
55
         id="stop2" />
56
      <stop
57
         offset="1"
58
         stop-color="#ecd980"
59
         id="stop4" />
60
    </linearGradient>
61
  </defs>
62
  <g
63
     id="g485"
64
     transform="matrix(2.5605898,1.4612315,-1.4612315,2.5605898,-947.38048,-777.17055)">
65
    <path
66
       style="fill:url(#a);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.226;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0"
67
       paint-order="stroke"
68
       d="m 496.76229,150.80474 c -4.25368,20.68081 3.28191,25.95476 3.28191,25.95476 v 0 c 0,0 3.00963,-13.19543 8.64082,-10.76172 v 0 c 4.83401,2.08299 1.12516,10.97002 1.12516,10.97002 v 0 c 0,0 31.78993,-30.5076 7.60484,-40.99434 v 0 c 0,0 -5.30287,-2.76791 -10.69842,-0.65209 v 0 c -3.94735,1.54891 -7.94375,5.71058 -9.95431,15.48337"
69
       stroke-linecap="round"
70
       id="path14-3" />
71
    <path
72
       d="m 530.80335,138.63592 -10.99206,-16.95952 1.75995,-6.49966 10.01483,2.71233 z"
73
       fill="#126d95"
74
       id="path9" />
75
    <path
76
       d="m 533.0598,112.36676 -0.91739,3.38458 -9.99361,-2.70665 0.91739,-3.38458 z"
77
       fill="#126d95"
78
       id="path11" />
79
    <g
80
       fill="#51a9cf"
81
       id="g19"
82
       transform="translate(-295.50101,-692.52836)">
83
      <path
84
         d="m 834.01973,741.0381 c -1.68105,0.0185 -3.22054,1.13771 -3.68367,2.84981 -0.56186,2.07405 0.665,4.21099 2.73743,4.77241 l -13.96475,51.52944 -9.99361,-2.70665 c 8.36013,-31.46487 4.99411,-51.98144 4.99411,-51.98144 14.99782,-11.92097 23.67,-25.56577 27.63101,-32.97331 z"
85
         id="path13" />
86
      <path
87
         d="m 818.56767,802.18881 -0.9174,3.38458 -10.03996,-2.72957 0.91314,-3.37522 z"
88
         id="path15" />
89
      <path
90
         d="m 817.07405,807.70594 -1.75995,6.49966 -18.03534,9.08805 9.78412,-18.31044 z"
91
         id="path17" />
92
    </g>
93
    <path
94
       d="m 540.69709,49.12083 7.72577,-28.52932 c -0.3195,8.40427 0.28451,24.55036 7.21678,42.41047 0,0 -11.89603,16.50235 -21.99788,47.3763 l -10.03442,-2.71758 13.96533,-51.5284 c 2.08221,0.56405 4.21039,-0.66603 4.77182,-2.73844 0.45427,-1.67248 -0.26571,-3.38317 -1.64739,-4.27302"
95
       fill="#126d95"
96
       id="path21" />
97
    <text
98
       transform="translate(-295.73751,-689.6407)"
99
       id="text25" />
100
  </g>
101
</svg>
1102
A www/images/logo/title.svg
1
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>
A www/images/screenshots/01.png
Binary file
A www/images/screenshots/02.png
Binary file
A www/images/screenshots/03.png
Binary file
A www/images/screenshots/04.png
Binary file
A www/images/screenshots/05.png
Binary file
A www/images/screenshots/06.png
Binary file
A www/images/screenshots/07.png
Binary file
A www/images/screenshots/08.png
Binary file
A www/index.shtml
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 n./count.sh" -->
A www/robots.txt
1
user-agent: * 
2
disallow: 
3
crawl-delay: 60
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
118
A www/screenshots.html
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 n class="screenshots">
9
  <p>
10
  Interpolated variable replacement
11
  </pscreenshots/05.png" class="screenshot" alt="variables">
12
  <p>
13
  Technical diagrams
14
  </pscreenshots/01.png" class="screenshot" alt="diagrams">
15
  <p>
16
  Internationalization
17
  </pscreenshots/02.png" class="screenshot" alt="internationalization">
18
  <p>
19
  Mathematics
20
  </pscreenshots/03.png" class="screenshot" alt="math">
21
  <p>
22
  Document outline
23
  </pscreenshots/04.png" class="screenshot" alt="outline">
24
  <p>
25
  Single sourced metadata
26
  </pscreenshots/06.png" class="screenshot" alt="multi-window">
27
  <p>
28
  R computations
29
  </pscreenshots/07.png" class="screenshot" alt="computation">
30
  <p>
31
  Theme-driven PDF output
32
  </pscreenshots/08.png" class="screenshot" alt="output"index.html">home
A www/styles/base.css
1
/*
2
 * Page
3
 Ensure the pag
4
 * Header
5
 */
6
 * Screenshots
7
 */
8
 * Version information
9
 */
10
main > p.version/
11
  width: 157px;
12
  height: 75px;
13
}
14
15
/*
16
 * Navigation
17
 */
18
 * Footer
19
 */
20
footer {
121