Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git
M .gitignore
1414
spell
1515
keenwrite.github.io
16
keenwrite.build_artifacts.txt
1617
D .project
1
<?xml version="1.0" encoding="UTF-8"?>
2
<projectDescription>
3
	<name>Markdown Writer FX</name>
4
	<comment></comment>
5
	<projects>
6
	</projects>
7
	<buildSpec>
8
		<buildCommand>
9
			<name>org.eclipse.jdt.core.javabuilder</name>
10
			<arguments>
11
			</arguments>
12
		</buildCommand>
13
		<buildCommand>
14
			<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
15
			<arguments>
16
			</arguments>
17
		</buildCommand>
18
	</buildSpec>
19
	<natures>
20
		<nature>org.eclipse.jdt.core.javanature</nature>
21
		<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
22
	</natures>
23
	<filteredResources>
24
		<filter>
25
			<id>1438449113801</id>
26
			<name></name>
27
			<type>26</type>
28
			<matcher>
29
				<id>org.eclipse.ui.ide.multiFilter</id>
30
				<arguments>1.0-projectRelativePath-matches-false-false-build</arguments>
31
			</matcher>
32
		</filter>
33
		<filter>
34
			<id>1438449113801</id>
35
			<name></name>
36
			<type>26</type>
37
			<matcher>
38
				<id>org.eclipse.ui.ide.multiFilter</id>
39
				<arguments>1.0-projectRelativePath-matches-false-false-.gradle</arguments>
40
			</matcher>
41
		</filter>
42
	</filteredResources>
43
</projectDescription>
441
D CODE_OF_CONDUCT.md
1
# Contributor Covenant Code of Conduct
2
3
## Our Pledge
4
5
In the interest of fostering an open and welcoming environment, we as
6
contributors and maintainers pledge to making participation in our project and
7
our community a harassment-free experience for everyone, regardless of age, body
8
size, disability, ethnicity, sex characteristics, gender identity and expression,
9
level of experience, education, socio-economic status, nationality, personal
10
appearance, race, religion, or sexual identity and orientation.
11
12
## Our Standards
13
14
Examples of behavior that contributes to creating a positive environment
15
include:
16
17
* Using welcoming and inclusive language
18
* Being respectful of differing viewpoints and experiences
19
* Gracefully accepting constructive criticism
20
* Focusing on what is best for the community
21
* Showing empathy towards other community members
22
23
Examples of unacceptable behavior by participants include:
24
25
* The use of sexualized language or imagery and unwelcome sexual attention or
26
 advances
27
* Trolling, insulting/derogatory comments, and personal or political attacks
28
* Public or private harassment
29
* Publishing others' private information, such as a physical or electronic
30
 address, without explicit permission
31
* Other conduct which could reasonably be considered inappropriate in a
32
 professional setting
33
34
## Our Responsibilities
35
36
Project maintainers are responsible for clarifying the standards of acceptable
37
behavior and are expected to take appropriate and fair corrective action in
38
response to any instances of unacceptable behavior.
39
40
Project maintainers have the right and responsibility to remove, edit, or
41
reject comments, commits, code, wiki edits, issues, and other contributions
42
that are not aligned to this Code of Conduct, or to ban temporarily or
43
permanently any contributor for other behaviors that they deem inappropriate,
44
threatening, offensive, or harmful.
45
46
## Scope
47
48
This Code of Conduct applies both within project spaces and in public spaces
49
when an individual is representing the project or its community. Examples of
50
representing a project or community include using an official project e-mail
51
address, posting via an official social media account, or acting as an appointed
52
representative at an online or offline event. Representation of a project may be
53
further defined and clarified by project maintainers.
54
55
## Enforcement
56
57
Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
reported by contacting the project team at Dave.Jarvis@gmail.com. All
59
complaints will be reviewed and investigated and will result in a response that
60
is deemed necessary and appropriate to the circumstances. The project team is
61
obligated to maintain confidentiality with regard to the reporter of an incident.
62
Further details of specific enforcement policies may be posted separately.
63
64
Project maintainers who do not follow or enforce the Code of Conduct in good
65
faith may face temporary or permanent repercussions as determined by other
66
members of the project's leadership.
67
68
## Attribution
69
70
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72
73
[homepage]: https://www.contributor-covenant.org
74
75
For answers to common questions about this code of conduct, see
76
https://www.contributor-covenant.org/faq
771
D Containerfile
1
FROM alpine:latest
2
3
RUN apk --update add --no-cache fontconfig curl
4
RUN rm -rf /var/cache
5
6
# Download fonts.
7
ENV FONT_DIR=/usr/share/fonts/user
8
RUN mkdir -p $FONT_DIR
9
WORKDIR $FONT_DIR
10
11
ADD "https://fonts.google.com/download?family=Roboto" "roboto.zip"
12
ADD "https://fonts.google.com/download?family=Inconsolata" "inconsolata.zip"
13
ADD "https://github.com/adobe-fonts/source-serif/releases/download/4.004R/source-serif-4.004.zip" "source-serif.zip"
14
ADD "https://github.com/googlefonts/Libre-Baskerville/blob/master/fonts/ttf/LibreBaskerville-Bold.ttf" "LibreBaskerville-Bold.ttf"
15
ADD "https://github.com/googlefonts/Libre-Baskerville/blob/master/fonts/ttf/LibreBaskerville-Italic.ttf" "LibreBaskerville-Italic.ttf"
16
ADD "https://github.com/googlefonts/Libre-Baskerville/blob/master/fonts/ttf/LibreBaskerville-Regular.ttf" "LibreBaskerville-Regular.ttf"
17
ADD "https://www.omnibus-type.com/wp-content/uploads/Archivo-Narrow.zip" "archivo-narrow.zip"
18
19
# Unpack fonts (prior to ConTeXt).
20
RUN unzip -j -o roboto.zip "*.ttf"
21
RUN unzip -j -o inconsolata.zip "**/Inconsolata/*.ttf"
22
RUN unzip -j -o source-serif.zip "source-serif-4.004/OTF/SourceSerif4-*.otf"
23
RUN unzip -j -o archivo-narrow.zip "Archivo-Narrow/otf/*.otf"
24
RUN rm -f roboto.zip
25
RUN rm -f inconsolata.zip
26
RUN rm -f source-serif.zip
27
RUN rm -f archivo-narrow.zip
28
29
# Update system font cache.
30
RUN fc-cache -f -v
31
32
WORKDIR "/opt"
33
34
# Download themes.
35
ADD "https://github.com/DaveJarvis/keenwrite-themes/releases/latest/download/theme-pack.zip" "theme-pack.zip"
36
RUN unzip theme-pack.zip
37
38
# Download ConTeXt.
39
ADD "http://lmtx.pragma-ade.nl/install-lmtx/context-linuxmusl.zip" "context.zip"
40
RUN unzip context.zip -d context
41
RUN rm -f context.zip
42
43
# Install ConTeXt.
44
WORKDIR "context"
45
RUN sh install.sh
46
47
# Configure environment to find ConTeXt.
48
ENV PROFILE=/etc/profile
49
ENV CONTEXT_HOME=/opt/context
50
RUN echo "export CONTEXT_HOME=\"$CONTEXT_HOME\"" >> $PROFILE
51
RUN echo "export PATH=\"\$PATH:\$CONTEXT_HOME/tex/texmf-linuxmusl/bin\"" >> $PROFILE
52
RUN echo "export OSFONTDIR=\"/usr/share/fonts//\""
53
RUN echo "PS1=\"typesetter:\\w\\\$ \"" >> $PROFILE
54
55
# Trim the fat.
56
RUN source $PROFILE
57
RUN rm -rf $CONTEXT_HOME/tex/texmf-context/doc
58
RUN find . -type f -name "*.pdf" -exec rm {} \;
59
60
# Prepare to process text files.
61
WORKDIR "/root"
62
631
M R/conversion.R
6060
# -----------------------------------------------------------------------------
6161
annal <- function( days = 0, format = "%Y-%m-%d", oformat = "%B %d, %Y" ) {
62
  format( when( anchor, days ), format = oformat )
62
  gsub( " 0", " ", format( when( anchor, days ), format = oformat ) )
6363
}
6464
...
196196
  }
197197
198
  gsub( '^0*', '', s )
198
  gsub( "^0*", '', s )
199199
}
200200
...
226226
227227
  # Calculate number of elapsed years.
228
  years = length( seq( from = began, to = ended, by = 'year' ) ) - 1
228
  years = length( seq( from = began, to = ended, by = "year" ) ) - 1
229229
230230
  # Move the start date up by the number of elapsed years.
...
239239
240240
  # Calculate number of elapsed months, excluding years.
241
  months = length( seq( from = began, to = ended, by = 'month' ) ) - 1
241
  months = length( seq( from = began, to = ended, by = "month" ) ) - 1
242242
243243
  # Move the start date up by the number of elapsed months
...
252252
253253
  # Calculate number of elapsed days, excluding months and years.
254
  days = length( seq( from = began, to = ended, by = 'day' ) ) - 1
254
  days = length( seq( from = began, to = ended, by = "day" ) ) - 1
255255
256256
  if( days > 0 ) {
...
288288
# -----------------------------------------------------------------------------
289289
pl.numeric <- function( s, n ) {
290
  concat( cms( n ), concat( " ", pluralize( s, n ) ) )
290
  concat( cms( n ), concat( " ", pluralize( word=s, n=n ) ) )
291291
}
292292
293293
# -----------------------------------------------------------------------------
294294
# Pluralize s if n is not equal to 1.
295295
# -----------------------------------------------------------------------------
296
pl <- function( s, n=2 ) {
297
  pluralize( s, x( n ) )
296
pl <- function( s, count=2 ) {
297
  pluralize( word=s, n=count )
298298
}
299299
...
343343
  dt = strptime( dates, format = format )
344344
  as.integer( difftime( dates[2], dates[1], units = "days" ) )
345
}
346
347
weeks <- function( began, ended ) {
348
  began = when( anchor, began )
349
  ended = when( anchor, ended )
350
351
  if( as.integer( ended - began ) < 0 ) {
352
    tempd = began
353
    began = ended
354
    ended = tempd
355
  }
356
357
  # Calculate number of elapsed weeks.
358
  length( seq( from = began, to = ended, by = "weeks" ) ) - 1
345359
}
346360
...
360374
361375
  # Calculate number of elapsed years.
362
  length( seq( from = began, to = ended, by = 'year' ) ) - 1
376
  length( seq( from = began, to = ended, by = "year" ) ) - 1
363377
}
364378
M R/pluralize.R
11
# -----------------------------------------------------------------------------
2
# Copyright 2020, White Magic Software, Ltd.
3
# 
4
# Permission is hereby granted, free of charge, to any person obtaining
5
# a copy of this software and associated documentation files (the
6
# "Software"), to deal in the Software without restriction, including
7
# without limitation the rights to use, copy, modify, merge, publish,
8
# distribute, sublicense, and/or sell copies of the Software, and to
9
# permit persons to whom the Software is furnished to do so, subject to
10
# the following conditions:
11
# 
12
# The above copyright notice and this permission notice shall be
13
# included in all copies or substantial portions of the Software.
14
# 
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
# -----------------------------------------------------------------------------
23
24
# -----------------------------------------------------------------------------
25
# See Damian Conway's "An Algorithmic Approach to English Pluralization":
26
#   http://goo.gl/oRL4MP
27
# See Oliver Glerke's Evo Inflector: https://github.com/atteo/evo-inflector/
28
# See Shevek's Pluralizer: https://github.com/shevek/linguistics/
29
# See also: http://www.freevectors.net/assets/files/plural.txt
30
# -----------------------------------------------------------------------------
31
pluralize <- function( s, n ) {
32
  result <- s
33
34
  # Partial implementation of Conway's algorithm for nouns.
35
  if( n != 1 ) {
36
    if( pl.noninflective( s ) ||
37
        pl.suffix( "es", s ) ||
38
        pl.suffix( "fish", s ) ||
39
        pl.suffix( "ois", s ) ||
40
        pl.suffix( "sheep", s ) ||
41
        pl.suffix( "deer", s ) ||
42
        pl.suffix( "pox", s ) ||
43
        pl.suffix( "[A-Z].*ese", s ) ||
44
        pl.suffix( "itis", s ) ) {
45
      # 1. Retain non-inflective user-mapped noun as is.
46
      # 2. Retain non-inflective plural as is.
47
      result <- s
48
    }
49
    else if( pl.is.irregular.pl( s ) ) {
50
      # 4. Change irregular plurals based on mapping.
51
      result <- pl.irregular.pl( s )
52
    }
53
    else if( pl.is.irregular.es( s ) ) {
54
      # x. From Shevek's
55
      result <- pl.inflect( s, "", "es" )
56
    }
57
    else if( pl.suffix( "man", s ) ) {
58
      # 5. For -man, change -an to -en
59
      result <- pl.inflect( s, "an", "en" )
60
    }
61
    else if( pl.suffix( "[lm]ouse", s ) ) {
62
      # 5. For [lm]ouse, change -ouse to -ice
63
      result <- pl.inflect( s, "ouse", "ice" )
64
    }
65
    else if( pl.suffix( "tooth", s ) ) {
66
      # 5. For -tooth, change -ooth to -eeth
67
      result <- pl.inflect( s, "ooth", "eeth" )
68
    }
69
    else if( pl.suffix( "goose", s ) ) {
70
      # 5. For -goose, change -oose to -eese
71
      result <- pl.inflect( s, "oose", "eese" )
72
    }
73
    else if( pl.suffix( "foot", s ) ) {
74
      # 5. For -foot, change -oot to -eet
75
      result <- pl.inflect( s, "oot", "eet" )
76
    }
77
    else if( pl.suffix( "zoon", s ) ) {
78
      # 5. For -zoon, change -on to -a
79
      result <- pl.inflect( s, "on", "a" )
80
    }
81
    else if( pl.suffix( "[csx]is", s ) ) {
82
      # 5. Change -cis, -sis, -xis to -es
83
      result <- pl.inflect( s, "is", "es" )
84
    }
85
    else if( pl.suffix( "([cs]h|ss|zz|x|s)", s ) ) {
86
      # 8. Change -ch, -sh, -ss, -zz, -x, -s to -es
87
      result <- pl.inflect( s, "", "es" )
88
    }
89
    else if( pl.suffix( "([aeo]lf|[^d]eaf|arf)", s ) ) {
90
      # 9. Change -f to -ves
91
      result <- pl.inflect( s, "f", "ves" )
92
    }
93
    else if( pl.suffix( "[nlw]ife", s ) ) {
94
      # 10. Change -fe to -ves
95
      result <- pl.inflect( s, "fe", "ves" )
96
    }
97
    else if( pl.suffix( "[aeiou]y", s ) ) {
98
      # 11. Change -[aeiou]y to -ys
99
      result <- pl.inflect( s, "", "s" )
100
    }
101
    else if( pl.suffix( "y", s ) ) {
102
      # 12. Change -y to -ies
103
      result <- pl.inflect( s, "y", "ies" )
104
    }
105
    else if( pl.suffix( "z", s ) ) {
106
      # x. Change -z to -zzes
107
      result <- pl.inflect( s, "", "zes" )
108
    }
109
    else {
110
      # 13. Default plural: add -s
111
      result <- pl.inflect( s, "", "s" )
112
    }
113
  }
114
115
  result
116
}
117
118
# -----------------------------------------------------------------------------
119
# Returns the given string (s) with its suffix replaced by r.
120
# -----------------------------------------------------------------------------
121
pl.inflect <- function( s, suffix, r ) {
122
  gsub( paste( suffix, "$", sep="" ), r, s )
123
}
124
125
# -----------------------------------------------------------------------------
126
# Answers whether the given string (s) has the given ending.
127
# -----------------------------------------------------------------------------
128
pl.suffix <- function( ending, s ) {
129
  grepl( paste( ending, "$", sep="" ), s )
130
}
131
132
# -----------------------------------------------------------------------------
133
# Answers whether the given string (s) is a noninflective noun.
134
# -----------------------------------------------------------------------------
135
pl.noninflective <- function( s ) {
136
  v <- c(
137
    "aircraft",
138
    "Bhutanese",
139
    "bison",
140
    "bream",
141
    "Burmese",
142
    "carp",
143
    "chassis",
144
    "Chinese",
145
    "clippers",
146
    "cod",
147
    "contretemps",
148
    "corps",
149
    "debris",
150
    "djinn",
151
    "eland",
152
    "elk",
153
    "flounder",
154
    "fracas",
155
    "gallows",
156
    "graffiti",
157
    "headquarters",
158
    "high-jinks",
159
    "homework",
160
    "hovercraft",
161
    "innings",
162
    "Japanese",
163
    "Lebanese",
164
    "mackerel",
165
    "means",
166
    "mews",
167
    "mice",
168
    "mumps",
169
    "news",
170
    "pincers",
171
    "pliers",
172
    "Portuguese",
173
    "proceedings",
174
    "salmon",
175
    "scissors",
176
    "sea-bass",
177
    "Senegalese",
178
    "shears",
179
    "Siamese",
180
    "Sinhalese",
181
    "spacecraft",
182
    "swine",
183
    "trout",
184
    "tuna",
185
    "Vietnamese",
186
    "watercraft",
187
    "whiting",
188
    "wildebeest"
189
  )
190
191
  is.element( s, v )
192
}
193
194
# -----------------------------------------------------------------------------
195
# Answers whether the given string (s) is an irregular plural.
196
# -----------------------------------------------------------------------------
197
pl.is.irregular.pl <- function( s ) {
198
  # Could be refactored with pl.irregular.pl...
199
  v <- c(
200
    "beef", "brother", "child", "cow", "ephemeris", "genie", "money",
201
    "mongoose", "mythos", "octopus", "ox", "soliloquy", "trilby"
202
  )
203
204
  is.element( s, v )
205
}
206
207
# -----------------------------------------------------------------------------
208
# Call to pluralize an irregular noun. Only call after confirming
209
# the noun is irregular via pl.is.irregular.pl.
210
# -----------------------------------------------------------------------------
211
pl.irregular.pl <- function( s ) {
212
  v <- list(
213
    "beef" = "beefs",
214
    "brother" = "brothers",
215
    "child" = "children",
216
    "cow" = "cows",
217
    "ephemeris" = "ephemerides",
218
    "genie" = "genies",
219
    "money" = "moneys",
220
    "mongoose" = "mongooses",
221
    "mythos" = "mythoi",
222
    "octopus" = "octopuses",
223
    "ox" = "oxen",
224
    "passerby" = "passersby",
225
    "soliloquy" = "soliloquies",
226
    "trilby" = "trilbys"
227
  )
228
229
  # Faster version of v[[ s ]]
230
  .subset2( v, s )
231
}
232
233
# -----------------------------------------------------------------------------
234
# Answers whether the given string (s) pluralizes with -es.
235
# -----------------------------------------------------------------------------
236
pl.is.irregular.es <- function( s ) {
237
  v <- c(
238
    "acropolis", "aegis", "alias", "asbestos", "bathos", "bias", "bronchitis",
239
    "bursitis", "caddis", "cannabis", "canvas", "chaos", "cosmos", "dais",
240
    "digitalis", "epidermis", "ethos", "eyas", "gas", "glottis", "hubris",
241
    "ibis", "lens", "mantis", "marquis", "metropolis", "pathos", "pelvis",
242
    "polis", "rhinoceros", "sassafrass", "trellis"
243
  )
244
245
  is.element( s, v )
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 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
15
# included in all copies or substantial portions of the Software.
16
# 
17
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
# -----------------------------------------------------------------------------
25
26
# -----------------------------------------------------------------------------
27
# See Damian Conway's "An Algorithmic Approach to English Pluralization":
28
#   http://goo.gl/oRL4MP
29
# See Oliver Glerke's Evo Inflector: https://github.com/atteo/evo-inflector/
30
# See Shevek's Pluralizer: https://github.com/shevek/linguistics/
31
# See also: http://www.freevectors.net/assets/files/plural.txt
32
# -----------------------------------------------------------------------------
33
34
# -----------------------------------------------------------------------------
35
# Applies all pluralization rules.
36
#
37
# @param word The word to pluralize.
38
# @param method The pluralization approach to apply to the word.
39
# @param n When any other value than 1, the word is pluralized.
40
# -----------------------------------------------------------------------------
41
pluralize <- function( word, method = c( "ac", "ca", "a", "c" ), n = 2 ) {
42
  if( n != 1 ) {
43
    method <- match.arg( method )
44
45
    coalesce( 
46
      pluralize_non_inflecting( word ),
47
      pluralize_pronoun( word ),
48
      pluralize_irregular( word, method ),
49
      pluralize_irregular_inflection_for_common_suffixes( word ),
50
      pluralize_fully_assimilated_classical_inflections( word ),
51
      pluralize_classical_variants_of_modern_inflections( word, method ),
52
      pluralize_ch_sh_ss_suffixes( word ),
53
      pluralize_f_and_fe_suffix( word ),
54
      pluralize_y_suffix( word ),
55
      pluralize_o_suffix( word ),
56
      pluralize_compound_words( word ),
57
      pluralize_regular( word )
58
    )
59
  }
60
  else {
61
    word
62
  }
63
}
64
65
# -----------------------------------------------------------------------------
66
# Rule 1
67
#
68
# Retain non-inflective user-mapped noun as is.
69
#
70
# Rule 2
71
#
72
# Irregular verbs that do not inflect in plural.
73
# -----------------------------------------------------------------------------
74
pluralize_non_inflecting <- function( word ) {
75
  coalesce( 
76
    ifelse( word %in% .uninflected_nouns, word, NA_character_ ),
77
    ifelse( word %in% .singular_nouns, word, NA_character_ ),
78
    ifelse( check_suffix( word, .irregular_patterns ), word, NA_character_ )
79
  )
80
} 
81
82
.check_non_inflecting <- function( word ) {
83
  is_uninflected <- word %in% .uninflected_nouns
84
  is_singular <- word %in% .singular_nouns
85
  is_irregular <- check_suffix( word, .irregular_patterns )
86
87
  is_uninflected | is_singular | is_irregular
88
}
89
90
.uninflected_nouns <- c( 
91
  "adonis",
92
  "anis",
93
  "bison",
94
  "bream",
95
  "breeches",
96
  "britches",
97
  "carp",
98
  "chassis",
99
  "clippers",
100
  "cod",
101
  "contretemps",
102
  "corps",
103
  "debris",
104
  "diabetes",
105
  "djinn",
106
  "eland",
107
  "elk",
108
  "flounder",
109
  "gallows",
110
  "graffiti",
111
  "headquarters",
112
  "herpes",
113
  "high-jinks",
114
  "homework",
115
  "innings",
116
  "jackanapes",
117
  "mackerel",
118
  "measles",
119
  "mews",
120
  "mumps",
121
  "news",
122
  "pants",
123
  "physics",
124
  "pincers",
125
  "pliers",
126
  "proceedings",
127
  "rabies",
128
  "salmon",
129
  "scissors",
130
  "sea-bass",
131
  "series",
132
  "shears",
133
  "species",
134
  "swine",
135
  "trout",
136
  "tuna",
137
  "whiting",
138
  "wildebeest"
139
)
140
141
.singular_nouns <- c( 
142
  "bathos",
143
  "caddis",
144
  "cannabis",
145
  "dais",
146
  "digitalis",
147
  "ethos",
148
  "glottis",
149
  "marquis",
150
  "pathos",
151
  "polis"
152
)
153
154
.irregular_patterns <- c( 
155
  "fish$", "ois$", "-sheep$", "deer$", "pox$", "[A-Z].*ese$", "itis$"
156
)
157
158
.prepositions <- c( 
159
  "about",
160
  "above",
161
  "across",
162
  "after",
163
  "among",
164
  "around",
165
  "at",
166
  "athwart",
167
  "before",
168
  "behind",
169
  "below",
170
  "beneath",
171
  "beside",
172
  "besides",
173
  "between",
174
  "betwixt",
175
  "beyond",
176
  "but",
177
  "by",
178
  "during",
179
  "except",
180
  "for",
181
  "from",
182
  "in",
183
  "into",
184
  "near",
185
  "of",
186
  "off",
187
  "on",
188
  "onto",
189
  "out",
190
  "over",
191
  "since",
192
  "till",
193
  "to",
194
  "under",
195
  "until",
196
  "unto",
197
  "upon",
198
  "with"
199
)
200
201
# -----------------------------------------------------------------------------
202
# Rule 3
203
#
204
# Handle pronouns in the nominative, accusative, and dative and propositional
205
# phrases.
206
# -----------------------------------------------------------------------------
207
pluralize_pronoun <- function( word ) {
208
  as.vector( .pluralized_pronouns[word] )
209
}
210
211
.pluralized_pronouns <- c( 
212
  "I" = "we",
213
  "me" = "us",
214
  "myself" = "ourselves",
215
216
  "you" = "you",
217
  "thou" = "ye",
218
  "thee" = "ye",
219
  "yourself" = "yourself",
220
  "thyself" = "yourself",
221
222
  "she" = "they",
223
  "he" = "they",
224
  "it" = "they",
225
  "they" = "they",
226
227
  "her" = "them",
228
  "him" = "them",
229
  "it" = "them",
230
  "them" = "them",
231
232
  "herself" = "themselves",
233
  "himself" = "themselves",
234
  "itself" = "themselves",
235
236
  "themself" = "themselves",
237
  "oneself" = "oneselves"
238
)
239
240
# -----------------------------------------------------------------------------
241
# Rule 4
242
#
243
# Change irregular plurals based on mapping.
244
# -----------------------------------------------------------------------------
245
pluralize_irregular <- function( word, method = c( "ac", "ca", "a", "c" ) ) {
246
  method <- match.arg( method )
247
  plurals <- .irregular_nouns[word]
248
249
  extract_plural <- function( plurals ) {
250
    if( is.null( plurals ) ) {
251
      return( NA_character_ )
252
    }
253
254
    return(
255
      switch( 
256
        method,
257
        "a" = plurals["a"],
258
        "c" = plurals["c"],
259
        "ac" = if.na( plurals["a"], plurals["c"] ),
260
        "ca" = if.na( plurals["c"], plurals["a"] )
261
      )
262
    )
263
  }
264
265
  as.character( lapply( plurals, extract_plural ) )
266
}
267
268
.irregular_nouns <- list( 
269
  "beef"      = c( "a" = "beefs",       "c" = "beeves" ),
270
  "brother"   = c( "a" = "brothers",    "c" = "brethren" ),
271
  "child"     = c( "a" = NA_character_, "c" = "children" ),
272
  "cherub"    = c( "a" = "cherubim",    "c" = NA_character_ ),
273
  "cow"       = c( "a" = "cows",        "c" = "kine" ),
274
  "ephemeris" = c( "a" = NA_character_, "c" = "ephemerides" ),
275
  "genie"     = c( "a" = "genies",      "c" = "genii" ),
276
  "matrix"    = c( "a" = NA_character_, "c" = "matrices" ),
277
  "money"     = c( "a" = "moneys",      "c" = "monies" ),
278
  "mongoose"  = c( "a" = "mongooses",   "c" = NA_character_ ),
279
  "mythos"    = c( "a" = NA_character_, "c" = "mythoi" ),
280
  "octopus"   = c( "a" = "octopuses",   "c" = "octopodes" ),
281
  "ox"        = c( "a" = NA_character_, "c" = "oxen" ),
282
  "passerby"  = c( "a" = NA_character_, "c" = "passersby" ),
283
  "soliloquy" = c( "a" = "soliloquies", "c" = NA_character_ ),
284
  "seraph"    = c( "a" = "seraphim",    "c" = NA_character_ ),
285
  "trilby"    = c( "a" = "trilbys",     "c" = NA_character_ ),
286
  "vertex"    = c( "a" = NA_character_, "c" = "vertices" ),
287
  "vortex"    = c( "a" = NA_character_, "c" = "vortices" )
288
)
289
290
# -----------------------------------------------------------------------------
291
# Rule 5
292
#
293
# Handle irregular inflections for common suffixes.
294
# -----------------------------------------------------------------------------
295
pluralize_irregular_inflection_for_common_suffixes <- function( word ) {
296
  output <- sub( "man$", "men", word )
297
  output <- sub( "([ml])(ouse)$", "\\1ice", output )
298
  output <- sub( "tooth$", "teeth", output )
299
  output <- sub( "goose$", "geese", output )
300
  output <- sub( "foot$", "feet", output )
301
  output <- sub( "zoon$", "zoa", output )
302
  output <- sub( "([csx])(is)$", "\\1es", output )
303
304
  ifelse( output == word, NA_character_, output )
305
}
306
307
# -----------------------------------------------------------------------------
308
# Rule 6
309
#
310
# Handle fully assimilated classical inflections.
311
# -----------------------------------------------------------------------------
312
pluralize_fully_assimilated_classical_inflections <- function( word ) {
313
  output <- replace_suffix(
314
    word, "", "e", c( "alumna", "alga", "vertebra" ) )
315
  output <- replace_suffix(
316
    output, "ex", "ices", c( "codex", "murex", "silex" ) )
317
  output <- replace_suffix(
318
    output, "on", "a", c( 
319
      "aphelion",
320
      "asyndeton",
321
      "criterion",
322
      "hyperbaton",
323
      "noumenon",
324
      "organon",
325
      "perihelion",
326
      "phenomenon",
327
      "prolegomenon"
328
    )
329
  )
330
  output <- replace_suffix(
331
    output, "um", "a", c( 
332
      "agendum",
333
      "bacterium",
334
      "candelabrum",
335
      "datum",
336
      "desideratum",
337
      "erratum",
338
      "extremum",
339
      "ovum",
340
      "stratum"
341
    )
342
  )
343
344
  ifelse( output == word, NA_character_, output )
345
}
346
347
# -----------------------------------------------------------------------------
348
# Rule 7
349
#
350
# Classical variants of modern inflections (e.g., stigmata, soprani).
351
#
352
# See tables A.11 to A.13, A.15, A.16, A.18, A.21 to A.25.
353
# -----------------------------------------------------------------------------
354
pluralize_classical_variants_of_modern_inflections <- function( 
355
  word, method = c( "ac", "ca", "a", "c" ) ) {
356
  method <- match.arg( method )
357
358
  # -a to -as (anglicized) or -ae (classical)
359
  a11 <- c( 
360
    "abscissa",
361
    "amoeba",
362
    "antenna",
363
    "aurora",
364
    "formula",
365
    "hydra",
366
    "hyperbola",
367
    "lacuna",
368
    "medusa",
369
    "nebula",
370
    "nova",
371
    "parabola"
372
  )
373
374
  # Table A.12: -a to -as (anglicized) or -ata (classical)
375
  a12 <- c( 
376
    "anathema",
377
    "bema",
378
    "carcinoma",
379
    "charisma",
380
    "diploma",
381
    "dogma",
382
    "drama",
383
    "edema",
384
    "enema",
385
    "enigma",
386
    "gumma",
387
    "lemma",
388
    "lymphoma",
389
    "magma",
390
    "melisma",
391
    "miasma",
392
    "oedema",
393
    "sarcoma",
394
    "schema",
395
    "soma",
396
    "stigma",
397
    "stoma",
398
    "trauma"
399
  )
400
  
401
  # Table A.13: -en to -ens (anglicized) or -ina (classical)
402
  a13 <- c( "stamen", "foramen", "lumen" )
403
  
404
  # Table A.15: -ex to -exes (anglicized) or -ices (classical)
405
  a15 <- c( 
406
    "apex",
407
    "cortex",
408
    "index",
409
    "latex",
410
    "pontifex",
411
    "simplex",
412
    "vertex",
413
    "vortex"
414
  )
415
  
416
  # Table A.16: -is to -ises (anglicized) or -ides (classical)
417
  a16 <- c( "iris", "clitoris" )
418
  
419
  # Table A.18: -o to -os (anglicized) or -i (classical)
420
  a18 <- c( 
421
    "alto",
422
    "basso",
423
    "canto",
424
    "contralto",
425
    "crescendo",
426
    "solo",
427
    "soprano",
428
    "tempo"
429
  )
430
   
431
  # Table A.21: -um to -ums (anglicized) or -a (classical)
432
  a21 <- c( 
433
    "aquarium",
434
    "compendium",
435
    "consortium",
436
    "cranium",
437
    "curriculum",
438
    "dictum",
439
    "emporium",
440
    "enconium",
441
    "gymnasium",
442
    "honorarium",
443
    "interregnum",
444
    "lustrum",
445
    "maximum",
446
    "medium",
447
    "memorandum",
448
    "millenium",
449
    "minimum",
450
    "momentum",
451
    "optimum",
452
    "phylum",
453
    "quantum",
454
    "rostrum",
455
    "spectrum",
456
    "speculum",
457
    "stadium",
458
    "trapezium",
459
    "ultimatum",
460
    "vacuum",
461
    "velum"
462
  )
463
  
464
  # Table A.22: -us to -uses (anglicized) or -i (classical)
465
  a22 <- c( 
466
    "focus",
467
    "fungus",
468
    "genius",
469
    "incubus",
470
    "nimbus",
471
    "nucleolus",
472
    "radius",
473
    "stylus",
474
    "succubus",
475
    "torus",
476
    "umbilicus",
477
    "uterus"
478
  )
479
  
480
  # Table A.23: -us to -uses (anglicized) or -us (classical)
481
  a23 <- c( 
482
    "apparatus",
483
    "cantus",
484
    "coitus",
485
    "hiatus",
486
    "impetus",
487
    "nexus",
488
    "plexus",
489
    "prospectus",
490
    "sinus",
491
    "status"
492
  )
493
  
494
  output <- replace_suffix( word, "", "im", c( "cherub", "goy", "seraph"  ) )
495
  output <- replace_suffix( output, "", "i", c( "afreet", "afrit", "efreet" ) )
496
  
497
  if( method %in% c( "a", "ac" ) ) {
498
    output <- replace_suffix( output, "us", "uses", a23 )
499
    output <- replace_suffix( output, "us", "uses", a22 )
500
    output <- replace_suffix( output, "um", "ums", a21 )
501
    output <- replace_suffix( output, "o", "os", a18 )
502
    output <- replace_suffix( output, "is", "ises", a16 )
503
    output <- replace_suffix( output, "ex", "exes", a15 )
504
    output <- replace_suffix( output, "en", "ens", a13 )
505
    output <- replace_suffix( output, "a", "as", a12 )
506
    output <- replace_suffix( output, "a", "as", a11 )
507
  } else {
508
    output <- replace_suffix( output, "us", "us", a23 )
509
    output <- replace_suffix( output, "us", "i", a22 )
510
    output <- replace_suffix( output, "um", "a", a21 )
511
    output <- replace_suffix( output, "o", "i", a18 )
512
    output <- replace_suffix( output, "is", "ides", a16 )
513
    output <- replace_suffix( output, "ex", "ices", a15 )
514
    output <- replace_suffix( output, "en", "ina", a13 )
515
    output <- replace_suffix( output, "a", "ata", a12 )
516
    output <- replace_suffix( output, "a", "ae", a11 )
517
  }
518
519
  ifelse( 
520
    output == word & (method %in% c( "a", "ac" ) | !word %in% a23), 
521
    NA_character_, 
522
    output
523
  )
524
}
525
526
# -----------------------------------------------------------------------------
527
# Rule 8
528
#
529
# Suffixes -ch, -sh, -ss, -x, and -z take -es as plural (e.g., churches,
530
# classes).
531
# -----------------------------------------------------------------------------
532
pluralize_ch_sh_ss_suffixes <- function( word ) {
533
  output <- sub( "([cs]h)$", "\\1es", word )
534
  output <- sub( "(x|z)$", "\\1es", word )
535
  output <- replace_suffix( output, "ss", "sses" )
536
537
  ifelse( output == word, NA_character_, output )
538
}
539
540
# -----------------------------------------------------------------------------
541
# Rule 9
542
#
543
# Certain words ending in -f or -fe take -ves in the plural.
544
# -----------------------------------------------------------------------------
545
pluralize_f_and_fe_suffix <- function( word ) {
546
  output <- sub( "([aeo]l|[^d]ea|ar)f$", "\\1ves", word )
547
  output <- sub( "([nlw]i)fe$", "\\1ves", output )
548
549
  ifelse( output == word, NA_character_, output )
550
}
551
552
# -----------------------------------------------------------------------------
553
# Rule 10
554
#
555
# Words ending in -y take -ies.
556
# -----------------------------------------------------------------------------
557
pluralize_y_suffix <- function( word ) {
558
  output <- sub( "([aeiou]y)$", "\\1s", word )
559
  output <- sub( "([A-Z].*y)$", "\\1s", output )
560
  output <- replace_suffix( output, "y", "ies" )
561
562
  ifelse( output == word, NA_character_, output )
563
}
564
565
# -----------------------------------------------------------------------------
566
# Rule 11
567
#
568
# Some words ending in -o take -os (lassos, solos). See tables A.17 and A.18.
569
# Others take -oes (potatoes, dominoes).
570
# When -o is preceded by a vowel always take -os (folios, bamboos).
571
# -----------------------------------------------------------------------------
572
pluralize_o_suffix <- function( word, method = c( "ac", "ca", "a", "c" ) ) {
573
  method <- match.arg( method )
574
575
  # Table A.17: -o to -os
576
  a17 <- c( 
577
    "albino",
578
    "archipelago",
579
    "armadillo",
580
    "commando",
581
    "ditto",
582
    "dynamo",
583
    "embryo",
584
    "fiasco",
585
    "generalissimo",
586
    "ghetto",
587
    "guano",
588
    "inferno",
589
    "jumbo",
590
    "lingo",
591
    "lumbago",
592
    "magneto",
593
    "manifesto",
594
    "medico",
595
    "octavo",
596
    "photo",
597
    "pro",
598
    "quarto",
599
    "rhino",
600
    "stylo"
601
  )
602
603
  # Table A.18: -o to -os (anglicized) or -i (classical)
604
  a18 <- c( 
605
    "alto",
606
    "basso",
607
    "canto",
608
    "contralto",
609
    "crescendo",
610
    "solo",
611
    "soprano",
612
    "tempo"
613
  )
614
615
  output <- replace_suffix( word, "o", "os", a17 )
616
  replacement <- if( method %in% c( "c", "ca" ) ) "i" else "os"
617
  output <- replace_suffix( output, "o", replacement, a18 )
618
619
  ifelse( output == word, NA_character_, output )
620
}
621
622
# -----------------------------------------------------------------------------
623
# Rule 12
624
#
625
# Compound word pluralization.
626
# -----------------------------------------------------------------------------
627
pluralize_compound_words <- function(
628
  word, method = c( "ac", "ca", "a", "c" ) ) {
629
  method <- match.arg( method )
630
631
  # "General" is pluralized.
632
  military <- c(
633
    "Adjutant",
634
    "Brigadier",
635
    "Lieutenant",
636
    "Major",
637
    "Quartermaster"
638
  )
639
640
  # X of Y -> plural(X) of Y
641
  # X at Y -> plural(X) of Y
642
  # X Y general -> X plural(Y) general
643
  # X-in-Y -> plural(X)-in-Y
644
645
  # Major Generals
646
  # Adjutant Generals
647
  # Lieutenant Generals
648
  # Brigadier Generals
649
  # Quartermaster Generals
650
651
  pluralize_cw <- Vectorize(
652
    function( cw, seps ) {
653
      if( cw[length( cw )] %in% c( "General", "general" ) && 
654
          (!cw[length( cw )] %in% military) ) {
655
        cw[1] <- pluralize( cw[1], method )
656
      } else {
657
        cw[1] <- pluralize( cw[1], method )
658
      }
659
660
      paste( paste0( seps, cw ), collapse = "" )
661
    }
662
  )
663
664
  parts <- strsplit( word, "[- ]+" )
665
  seps <- strsplit( word, "[^ -]+" )
666
  is_compound <- grepl( "[- ]", word )
667
  output <- word
668
  output[!is_compound] <- NA_character_
669
  output[is_compound] <- pluralize_cw( parts[is_compound], seps[is_compound] )
670
671
  output
672
}
673
674
# -----------------------------------------------------------------------------
675
# Rule 13
676
#
677
# Add -es if ending in -s (e.g., tennis, lychnis); otherwise, add -s.
678
# -----------------------------------------------------------------------------
679
pluralize_regular <- function( word ) {
680
  ending <- 's'
681
682
  if( endsWith( word, ending ) ) {
683
    ending <- "es"
684
  }
685
686
  paste0( word, ending )
687
}
688
689
# -----------------------------------------------------------------------------
690
# Determines whether the word ends with one of the given suffixes.
691
# -----------------------------------------------------------------------------
692
check_suffix <- function( x, suffixes ) {
693
  pattern <- paste0( "(", paste( suffixes, collapse = "|" ), ")$" )
694
  grepl( pattern, x, ignore.case = TRUE )
695
}
696
697
# -----------------------------------------------------------------------------
698
# Replaces the suffix of the word.
699
# -----------------------------------------------------------------------------
700
replace_suffix <- function( x, suffix, replacement, eligible = NULL ) {
701
  ifelse( 
702
    is.null( eligible ) | x %in% eligible, 
703
    sub( paste0( suffix, "$" ), replacement, x ),
704
    x
705
  )
706
}
707
708
# -----------------------------------------------------------------------------
709
# Returns y if x is na, otherwise x.
710
# -----------------------------------------------------------------------------
711
if.na <- function( x, y ) {
712
  ifelse( is.na( x ), y, x )
713
}
714
715
# -----------------------------------------------------------------------------
716
# Reduces the given function list.
717
# -----------------------------------------------------------------------------
718
coalesce <- function( ... ) {
719
  args <- list( ... )
720
  Reduce( if.na, args )
246721
}
247722
M build.gradle
1
import static org.gradle.api.JavaVersion.*
2
13
plugins {
24
  id 'application'
35
  id 'org.openjfx.javafxplugin' version '0.0.13'
46
  id 'com.palantir.git-version' version '0.15.0'
57
}
68
79
repositories {
810
  mavenCentral()
911
10
  maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
12
  maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
1113
  maven { url 'https://nexus.bedatadriven.com/content/groups/public' }
1214
1315
  maven {
14
    url "https://css4j.github.io/maven/"
16
    url "https://css4j.github.io/maven"
1517
    mavenContent {
1618
      releasesOnly()
...
5961
  def v_junit = '5.9.1'
6062
  def v_flexmark = '0.64.0'
61
  def v_jackson = '2.13.4'
63
  def v_jackson = '2.14.0'
6264
  def v_echosvg = '0.2.1'
65
  def v_picocli = '4.7.0'
6366
6467
  // JavaFX
65
  implementation 'org.controlsfx:controlsfx:11.1.1'
66
  implementation 'org.fxmisc.richtext:richtextfx:0.10.9'
67
  implementation 'org.fxmisc.flowless:flowless:0.6.10'
68
  implementation 'org.controlsfx:controlsfx:11.1.2'
69
  implementation 'org.fxmisc.richtext:richtextfx:0.11.0'
70
  implementation 'org.fxmisc.flowless:flowless:0.7.0'
6871
  implementation 'org.fxmisc.wellbehaved:wellbehavedfx:0.3.3'
6972
  implementation 'com.miglayout:miglayout-javafx:11.0'
70
  implementation 'com.dlsc.preferencesfx:preferencesfx-core:11.10.0'
73
  implementation 'com.dlsc.preferencesfx:preferencesfx-core:11.11.0'
7174
7275
  // Markdown
...
118121
119122
  // Command-line parsing
120
  implementation 'info.picocli:picocli:4.6.3'
123
  implementation "info.picocli:picocli:${v_picocli}"
124
  annotationProcessor "info.picocli:picocli-codegen:${v_picocli}"
121125
122126
  // Spelling, TeX, Docking, KeenQuotes
...
135139
  testImplementation "org.junit.jupiter:junit-jupiter-params:${v_junit}"
136140
  testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
137
}
138
139
compileJava {
140
  options.compilerArgs << '-Xlint:unchecked' << '-Xlint:deprecation'
141141
}
142
143
def resourceDir = sourceSets.main.resources.srcDirs[0]
144142
143
final resourceDir = sourceSets.main.resources.srcDirs[0]
145144
final Properties config = new Properties()
146145
final File configFile = file( "${resourceDir}/bootstrap.properties" )
147146
final FileInputStream configStream = new FileInputStream( configFile )
148147
config.load( configStream )
149148
configStream.close()
150149
151150
final String applicationName = config.get( 'application.title' ).toString().toLowerCase()
152
final String applicationClass = "com.${applicationName}.Launcher"
151
final String applicationPackage = "com.${applicationName}"
152
final String applicationClass = "${applicationPackage}.Launcher"
153
154
java {
155
  sourceCompatibility = VERSION_17
156
  targetCompatibility = VERSION_17
157
}
158
159
compileJava {
160
  options.compilerArgs
161
      << '-Xlint:unchecked'
162
      << '-Xlint:deprecation'
163
      << "-Aproject=${applicationPackage}/${applicationName}"
164
}
153165
154166
application {
A container/Containerfile
1
FROM alpine:latest
2
3
RUN apk --update add --no-cache fontconfig curl
4
RUN rm -rf /var/cache
5
6
# Download fonts.
7
ENV FONT_DIR=/usr/share/fonts/user
8
RUN mkdir -p $FONT_DIR
9
WORKDIR $FONT_DIR
10
11
ADD "https://fonts.google.com/download?family=Roboto" "roboto.zip"
12
ADD "https://fonts.google.com/download?family=Inconsolata" "inconsolata.zip"
13
ADD "https://github.com/adobe-fonts/source-serif/releases/download/4.004R/source-serif-4.004.zip" "source-serif.zip"
14
ADD "https://github.com/googlefonts/Libre-Baskerville/blob/master/fonts/ttf/LibreBaskerville-Bold.ttf" "LibreBaskerville-Bold.ttf"
15
ADD "https://github.com/googlefonts/Libre-Baskerville/blob/master/fonts/ttf/LibreBaskerville-Italic.ttf" "LibreBaskerville-Italic.ttf"
16
ADD "https://github.com/googlefonts/Libre-Baskerville/blob/master/fonts/ttf/LibreBaskerville-Regular.ttf" "LibreBaskerville-Regular.ttf"
17
ADD "https://www.omnibus-type.com/wp-content/uploads/Archivo-Narrow.zip" "archivo-narrow.zip"
18
19
# Unpack fonts (prior to ConTeXt).
20
RUN unzip -j -o roboto.zip "*.ttf"
21
RUN unzip -j -o inconsolata.zip "**/Inconsolata/*.ttf"
22
RUN unzip -j -o source-serif.zip "source-serif-4.004/OTF/SourceSerif4-*.otf"
23
RUN unzip -j -o archivo-narrow.zip "Archivo-Narrow/otf/*.otf"
24
RUN rm -f roboto.zip
25
RUN rm -f inconsolata.zip
26
RUN rm -f source-serif.zip
27
RUN rm -f archivo-narrow.zip
28
29
# Update system font cache.
30
RUN fc-cache -f -v
31
32
WORKDIR "/opt"
33
34
# Download themes.
35
ADD "https://github.com/DaveJarvis/keenwrite-themes/releases/latest/download/theme-pack.zip" "theme-pack.zip"
36
RUN unzip theme-pack.zip
37
38
# Download ConTeXt.
39
ADD "http://lmtx.pragma-ade.nl/install-lmtx/context-linuxmusl.zip" "context.zip"
40
RUN unzip context.zip -d context
41
RUN rm -f context.zip
42
43
# Install ConTeXt.
44
WORKDIR "context"
45
RUN sh install.sh
46
47
# Configure environment to find ConTeXt.
48
ENV PROFILE=/etc/profile
49
ENV CONTEXT_HOME=/opt/context
50
RUN echo "export CONTEXT_HOME=\"$CONTEXT_HOME\"" >> $PROFILE
51
RUN echo "export PATH=\"\$PATH:\$CONTEXT_HOME/tex/texmf-linuxmusl/bin\"" >> $PROFILE
52
RUN echo "export OSFONTDIR=\"/usr/share/fonts//\""
53
RUN echo "PS1=\"typesetter:\\w\\\$ \"" >> $PROFILE
54
55
# Trim the fat.
56
RUN source $PROFILE
57
RUN rm -rf $CONTEXT_HOME/tex/texmf-context/doc
58
RUN find . -type f -name "*.pdf" -exec rm {} \;
59
60
# Prepare to process text files.
61
WORKDIR "/root"
62
163
A container/context-container.sh
1
#!/usr/bin/env bash
2
3
if [ -z ${IMAGES_DIR} ]; then
4
  echo "Set IMAGES_DIR"
5
  exit 10
6
fi
7
8
readonly CONTAINER_NAME=typesetter
9
10
# Force clean
11
podman rmi --all --force
12
13
# Build from Containerfile
14
podman build --tag ${CONTAINER_NAME} .
15
16
# Connect and mount images
17
podman run \
18
  --rm \
19
  -i \
20
  -v ${IMAGES_DIR}:/root/images:ro \
21
  -t ${CONTAINER_NAME} \
22
  /bin/sh --login -c 'context --version'
23
24
# Create a persistent container
25
# podman create typesetter typesetter
26
27
# Create a long-running task
28
# podman create -ti typesetter /bin/sh
29
30
# Connect
31
32
# Export
33
# podman image save context -o typesetter.tar
34
# zip -9 -r typesetter.zip typesetter.tar
35
136
D context-container.sh
1
#!/usr/bin/env bash
2
3
if [ -z ${IMAGES_DIR} ]; then
4
  echo "Set IMAGES_DIR"
5
  exit 10
6
fi
7
8
readonly CONTAINER_NAME=typesetter
9
10
# Force clean
11
podman rmi --all --force
12
13
# Build from Containerfile
14
podman build --tag ${CONTAINER_NAME} .
15
16
# Connect and mount images
17
podman run \
18
  --rm \
19
  -i \
20
  -v ${IMAGES_DIR}:/root/images:ro \
21
  -t ${CONTAINER_NAME} \
22
  /bin/sh --login -c 'context --version'
23
24
# Create a persistent container
25
# podman create typesetter typesetter
26
27
# Create a long-running task
28
# podman create -ti typesetter /bin/sh
29
30
# Connect
31
32
# Export
33
# podman image save context -o typesetter.tar
34
# zip -9 -r typesetter.zip typesetter.tar
35
361
M libs/keenquotes.jar
Binary file
D 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;
11
12
  requires batik.anim;
13
  requires batik.bridge;
14
  requires batik.css;
15
  requires batik.gvt;
16
  requires batik.transcoder;
17
  requires batik.util;
18
19
  requires com.dlsc.formsfx;
20
  requires transitive com.dlsc.preferencesfx;
21
  requires com.fasterxml.jackson.databind;
22
  requires transitive com.fasterxml.jackson.dataformat.yaml;
23
24
  requires flexmark;
25
  requires flexmark.util.data;
26
  requires flexmark.util.sequence;
27
28
  requires keenquotes;
29
  requires keentex;
30
  requires tokenize;
31
32
  requires org.apache.commons.lang3;
33
  requires org.jsoup;
34
  requires org.controlsfx.controls;
35
  requires org.fxmisc.flowless;
36
  requires org.fxmisc.richtext;
37
  requires org.fxmisc.undo;
38
39
  requires commons.io;
40
  requires eventbus.java;
41
  requires flying.saucer.core;
42
  requires info.picocli;
43
  requires jsymspell;
44
  requires plexus.utils;
45
  requires tiwulfx.dock;
46
  requires wellbehavedfx;
47
  requires xml.apis.ext;
48
  requires java.logging;
49
}
50
511
M src/main/java/com/keenwrite/Bootstrap.java
88
import java.util.Properties;
99
10
import static com.keenwrite.events.StatusEvent.clue;
11
1012
/**
1113
 * Responsible for loading the bootstrap.properties file, which is
...
1921
 */
2022
public final class Bootstrap {
23
  private static final String PATH_BOOTSTRAP = "/bootstrap.properties";
24
2125
  /**
2226
   * Must be populated before deriving the app title (order matters).
2327
   */
2428
  private static final Properties sP = new Properties();
29
30
  public static String APP_TITLE;
31
  public static String APP_TITLE_LOWERCASE;
32
  public static String APP_VERSION;
33
  public static String APP_YEAR;
2534
2635
  static {
27
    try( final var in = openResource( "/bootstrap.properties" ) ) {
36
    try( final var in = openResource( PATH_BOOTSTRAP ) ) {
2837
      sP.load( in );
29
    } catch( final Exception ignored ) {
30
      // Bootstrap properties cannot be found, throw in the towel.
38
39
      APP_TITLE = sP.getProperty( "application.title" );
40
    } catch( final Exception ex ) {
41
      APP_TITLE = "KeenWrite";
42
43
      // Bootstrap properties cannot be found, use a default value.
44
      final var fmt = "Unable to load %s resource, applying defaults.%n";
45
      clue( ex, fmt, PATH_BOOTSTRAP );
3146
    }
32
  }
3347
34
  public static final String APP_TITLE = sP.getProperty( "application.title" );
35
  public static final String APP_TITLE_LOWERCASE = APP_TITLE.toLowerCase();
36
  public static final String APP_VERSION = Launcher.getVersion();
37
  public static final String APP_YEAR = getYear();
48
    APP_TITLE_LOWERCASE = APP_TITLE.toLowerCase();
3849
39
  static {
50
    try {
51
      APP_VERSION = Launcher.getVersion();
52
    } catch( final Exception ex ) {
53
      APP_VERSION = "0.0.0";
54
55
      // Application version cannot be found, use a default value.
56
      final var fmt = "Unable to determine application version.";
57
      clue( ex, fmt );
58
    }
59
60
    APP_YEAR = getYear();
61
4062
    // This also sets the user agent for the SVG rendering library.
4163
    System.setProperty( "http.agent", APP_TITLE + " " + APP_VERSION );
M src/main/java/com/keenwrite/Launcher.java
1111
import java.util.Properties;
1212
import java.util.function.Consumer;
13
import java.util.logging.LogManager;
1314
1415
import static com.keenwrite.Bootstrap.*;
...
7677
      final var properties = loadProperties( "app.properties" );
7778
      return properties.getProperty( "application.version" );
78
    } catch( final Exception ex ) {
79
    } catch( final IOException ex ) {
7980
      throw new RuntimeException( ex );
8081
    }
...
147148
      error.printStackTrace( System.err );
148149
    }
150
  }
151
152
  /**
153
   * Suppress writing to standard error, suppresses writing log messages.
154
   */
155
  private static void disableLogging() {
156
    LogManager.getLogManager().reset();
157
    stderrDisable();
158
  }
159
160
  /**
161
   * TODO: Delete this after JavaFX/GTK 3 no longer barfs useless warnings.
162
   */
163
  private static void stderrDisable() {
164
    System.err.close();
149165
  }
150166
...
158174
  private static void out( final String message, final Object... args ) {
159175
    System.out.printf( format( "%s%n", message ), args );
176
  }
177
178
  private static void showAppInfo() {
179
    out( "%n%s version %s", APP_TITLE, APP_VERSION );
180
    out( "Copyright 2016-%s White Magic Software, Ltd.", APP_YEAR );
181
    out( "Portions copyright 2015-2020 Karl Tauber.%n" );
160182
  }
161183
...
202224
      }
203225
      else {
204
        MainApp.disableLogging();
226
        disableLogging();
205227
      }
206228
...
216238
      log( t );
217239
    }
218
  }
219
220
  private static void showAppInfo() {
221
    out( "%n%s version %s", APP_TITLE, APP_VERSION );
222
    out( "Copyright 2016-%s White Magic Software, Ltd.", APP_YEAR );
223
    out( "Portions copyright 2015-2020 Karl Tauber.%n" );
224240
  }
225241
}
M src/main/java/com/keenwrite/MainApp.java
1515
import java.io.PrintStream;
1616
import java.util.function.BooleanSupplier;
17
import java.util.logging.LogManager;
1817
1918
import static com.keenwrite.Bootstrap.APP_TITLE;
...
3635
  private Workspace mWorkspace;
3736
  private MainScene mMainScene;
38
39
  /**
40
   * Suppress writing to standard error, suppresses writing log messages.
41
   */
42
  static void disableLogging() {
43
    LogManager.getLogManager().reset();
44
    stderrDisable();
45
  }
46
47
  /**
48
   * TODO: Delete this after JavaFX/GTK 3 no longer barfs useless warnings.
49
   */
50
  private static void stderrDisable() {
51
    System.err.close();
52
  }
5337
5438
  /**
...
119103
  @Override
120104
  public void start( final Stage stage ) {
121
    stderrDisable();
122
123105
    // Must be instantiated after the UI is initialized (i.e., not in main)
124106
    // because it interacts with GUI properties.
M src/main/java/com/keenwrite/MainPane.java
10161016
  }
10171017
1018
  private GenericBuilder<Mutator, ProcessorContext> createProcessorContextBuilder() {
1018
  private GenericBuilder<Mutator, ProcessorContext> processorContextBuilder() {
10191019
    final var w = getWorkspace();
10201020
...
10611061
    final var inputPath = textEditor.getPath();
10621062
1063
    return createProcessorContextBuilder()
1063
    return processorContextBuilder()
10641064
      .with( Mutator::setInputPath, inputPath )
10651065
      .with( Mutator::setOutputPath, outputPath )
...
10751075
   */
10761076
  private ProcessorContext createProcessorContext( final Path inputPath ) {
1077
    return createProcessorContextBuilder()
1077
    return processorContextBuilder()
10781078
      .with( Mutator::setInputPath, inputPath )
10791079
      .with( Mutator::setExportFormat, NONE )
M src/main/java/com/keenwrite/Messages.java
22
package com.keenwrite;
33
4
import com.keenwrite.collections.InterpolatingMap;
45
import com.keenwrite.preferences.Key;
56
import com.keenwrite.sigils.PropertyKeyOperator;
67
import com.keenwrite.sigils.SigilKeyOperator;
7
import com.keenwrite.collections.InterpolatingMap;
88
99
import java.text.MessageFormat;
...
2626
    // locale cannot be changed using the application, making interpolation of
2727
    // values viable as a one-time operation.
28
    final var bundle = getBundle( APP_BUNDLE_NAME );
28
    try {
29
      final var bundle = getBundle( APP_BUNDLE_NAME );
2930
30
    bundle.keySet().forEach( key -> MAP.put( key, bundle.getString( key ) ) );
31
    MAP.interpolate();
31
      bundle.keySet().forEach( key -> MAP.put( key, bundle.getString( key ) ) );
32
      MAP.interpolate();
33
    } catch( final Exception ignored ) {
34
      // This is bad, but it'll be extremely apparent when the UI loads. We
35
      // can't log this through regular channels because that'd lead to a
36
      // circular dependency.
37
    }
3238
  }
3339
...
7884
  }
7985
80
  private Messages() {}
86
  private Messages() { }
8187
}
8288
M src/main/java/com/keenwrite/editors/definition/yaml/YamlTreeTransformer.java
1414
import java.util.Map.Entry;
1515
16
import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.MINIMIZE_QUOTES;
17
import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.SPLIT_LINES;
16
import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.*;
1817
import static com.keenwrite.events.StatusEvent.clue;
1918
M src/main/java/com/keenwrite/events/StatusEvent.java
175175
176176
  /**
177
   * Notifies listeners of a custom message.
178
   *
179
   * @param ex   The exception that warranted calling this method.
180
   * @param fmt  The string format specifier.
181
   * @param args The arguments to weave into the format specifier.
182
   */
183
  public static void clue(
184
    final Exception ex,
185
    final String fmt,
186
    final Object... args ) {
187
    final var msg = format( fmt, args );
188
    clue( msg, ex );
189
  }
190
191
  /**
177192
   * Notifies listeners of an exception occurs that warrants the user's
178193
   * attention.
M src/main/java/com/keenwrite/io/MediaTypeSniffer.java
1
/* Copyright 2022 White Magic Software, Ltd. -- All rights reserved. */
12
package com.keenwrite.io;
23
...
1011
1112
/**
12
 * Responsible for associating file signatures with IANA-defined
13
 * {@link MediaType} instances. For details see:
14
 * <ul>
15
 *   <li>
16
 *     <a href="https://www.garykessler.net/library/file_sigs.html">Kessler's List</a>
17
 *   </li>
18
 *   <li>
19
 *     <a href="https://en.wikipedia.org/wiki/List_of_file_signatures">Wikipedia's List</a>
20
 *   </li>
21
 *   <li>
22
 *     <a href="https://github.com/veniware/Space-Maker/blob/master/FileSignatures.cs">Space Maker's List</a>
23
 *   </li>
24
 * </ul>
13
 * Associates file signatures with IANA-defined {@link MediaType}s. See:
14
 * <a href="https://www.garykessler.net/library/file_sigs.html">
15
 * Kessler's List
16
 * </a>,
17
 * <a href="https://en.wikipedia.org/wiki/List_of_file_signatures">
18
 * Wikipedia's List
19
 * </a>, and
20
 * <a href="https://github.com/veniware/Space-Maker/blob/master/FileSignatures.cs">
21
 * Space Maker's List
22
 * </a>
2523
 */
2624
public class MediaTypeSniffer {
2725
  private static final int FORMAT_LENGTH = 11;
2826
  private static final int END_OF_DATA = -2;
2927
3028
  private static final Map<int[], MediaType> FORMAT = new LinkedHashMap<>();
29
30
  private static void add( final int[] data, final MediaType mediaType ) {
31
    FORMAT.put( data, mediaType );
32
  }
3133
3234
  static {
3335
    //@formatter:off
34
    FORMAT.put( ints( 0x3C, 0x73, 0x76, 0x67, 0x20 ), IMAGE_SVG_XML );
35
    FORMAT.put( ints( 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), IMAGE_PNG );
36
    FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xE0 ), IMAGE_JPEG );
37
    FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xEE ), IMAGE_JPEG );
38
    FORMAT.put( ints( 0xFF, 0xD8, 0xFF, 0xE1, -1, -1, 0x45, 0x78, 0x69, 0x66, 0x00 ), IMAGE_JPEG );
39
    FORMAT.put( ints( 0x49, 0x49, 0x2A, 0x00 ), IMAGE_TIFF );
40
    FORMAT.put( ints( 0x4D, 0x4D, 0x00, 0x2A ), IMAGE_TIFF );
41
    FORMAT.put( ints( 0x47, 0x49, 0x46, 0x38 ), IMAGE_GIF );
42
    FORMAT.put( ints( 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E ), APP_PDF );
43
    FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D ), APP_EPS );
44
    FORMAT.put( ints( 0x25, 0x21, 0x50, 0x53 ), APP_PS );
45
    FORMAT.put( ints( 0x38, 0x42, 0x50, 0x53, 0x00, 0x01 ), IMAGE_PHOTOSHOP );
46
    FORMAT.put( ints( 0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), VIDEO_MNG );
47
    FORMAT.put( ints( 0x42, 0x4D ), IMAGE_BMP );
48
    FORMAT.put( ints( 0xFF, 0xFB, 0x30 ), AUDIO_MP3 );
49
    FORMAT.put( ints( 0x49, 0x44, 0x33 ), AUDIO_MP3 );
50
    FORMAT.put( ints( 0x3C, 0x21 ), TEXT_HTML );
51
    FORMAT.put( ints( 0x3C, 0x68, 0x74, 0x6D, 0x6C ), TEXT_HTML );
52
    FORMAT.put( ints( 0x3C, 0x68, 0x65, 0x61, 0x64 ), TEXT_HTML );
53
    FORMAT.put( ints( 0x3C, 0x62, 0x6F, 0x64, 0x79 ), TEXT_HTML );
54
    FORMAT.put( ints( 0x3C, 0x48, 0x54, 0x4D, 0x4C ), TEXT_HTML );
55
    FORMAT.put( ints( 0x3C, 0x48, 0x45, 0x41, 0x44 ), TEXT_HTML );
56
    FORMAT.put( ints( 0x3C, 0x42, 0x4F, 0x44, 0x59 ), TEXT_HTML );
57
    FORMAT.put( ints( 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20 ), TEXT_XML );
58
    FORMAT.put( ints( 0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78 ), TEXT_XML );
59
    FORMAT.put( ints( 0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00 ), TEXT_XML );
60
    FORMAT.put( ints( 0x23, 0x64, 0x65, 0x66 ), IMAGE_X_BITMAP );
61
    FORMAT.put( ints( 0x21, 0x20, 0x58, 0x50, 0x4D, 0x32 ), IMAGE_X_PIXMAP );
62
    FORMAT.put( ints( 0x2E, 0x73, 0x6E, 0x64 ), AUDIO_SIMPLE );
63
    FORMAT.put( ints( 0x64, 0x6E, 0x73, 0x2E ), AUDIO_SIMPLE );
64
    FORMAT.put( ints( 0x52, 0x49, 0x46, 0x46 ), AUDIO_WAV );
65
    FORMAT.put( ints( 0x50, 0x4B ), APP_ZIP );
66
    FORMAT.put( ints( 0x41, 0x43, -1, -1, -1, -1, 0x00, 0x00, 0x00, 0x00, 0x00 ), APP_ACAD );
67
    FORMAT.put( ints( 0xCA, 0xFE, 0xBA, 0xBE ), APP_JAVA );
68
    FORMAT.put( ints( 0xAC, 0xED ), APP_JAVA_OBJECT );
36
    add( ints( 0x3C, 0x73, 0x76, 0x67, 0x20 ), IMAGE_SVG_XML );
37
    add( ints( 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), IMAGE_PNG );
38
    add( ints( 0xFF, 0xD8, 0xFF, 0xE0 ), IMAGE_JPEG );
39
    add( ints( 0xFF, 0xD8, 0xFF, 0xEE ), IMAGE_JPEG );
40
    add( ints( 0xFF, 0xD8, 0xFF, 0xE1, -1, -1, 0x45, 0x78, 0x69, 0x66, 0x00 ), IMAGE_JPEG );
41
    add( ints( 0x49, 0x49, 0x2A, 0x00 ), IMAGE_TIFF );
42
    add( ints( 0x4D, 0x4D, 0x00, 0x2A ), IMAGE_TIFF );
43
    add( ints( 0x47, 0x49, 0x46, 0x38 ), IMAGE_GIF );
44
    add( ints( 0x52, 0x49, 0x46, 0x46, -1, -1, -1, -1, 0x57, 0x45, 0x42, 0x50 ), IMAGE_WEBP );
45
    add( ints( 0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E ), APP_PDF );
46
    add( ints( 0x25, 0x21, 0x50, 0x53, 0x2D, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x2D ), APP_EPS );
47
    add( ints( 0x25, 0x21, 0x50, 0x53 ), APP_PS );
48
    add( ints( 0x38, 0x42, 0x50, 0x53, 0x00, 0x01 ), IMAGE_PHOTOSHOP );
49
    add( ints( 0x8A, 0x4D, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A ), VIDEO_MNG );
50
    add( ints( 0x42, 0x4D ), IMAGE_BMP );
51
    add( ints( 0xFF, 0xFB, 0x30 ), AUDIO_MP3 );
52
    add( ints( 0x49, 0x44, 0x33 ), AUDIO_MP3 );
53
    add( ints( 0x3C, 0x21 ), TEXT_HTML );
54
    add( ints( 0x3C, 0x68, 0x74, 0x6D, 0x6C ), TEXT_HTML );
55
    add( ints( 0x3C, 0x68, 0x65, 0x61, 0x64 ), TEXT_HTML );
56
    add( ints( 0x3C, 0x62, 0x6F, 0x64, 0x79 ), TEXT_HTML );
57
    add( ints( 0x3C, 0x48, 0x54, 0x4D, 0x4C ), TEXT_HTML );
58
    add( ints( 0x3C, 0x48, 0x45, 0x41, 0x44 ), TEXT_HTML );
59
    add( ints( 0x3C, 0x42, 0x4F, 0x44, 0x59 ), TEXT_HTML );
60
    add( ints( 0x3C, 0x3F, 0x78, 0x6D, 0x6C, 0x20 ), TEXT_XML );
61
    add( ints( 0xFE, 0xFF, 0x00, 0x3C, 0x00, 0x3f, 0x00, 0x78 ), TEXT_XML );
62
    add( ints( 0xFF, 0xFE, 0x3C, 0x00, 0x3F, 0x00, 0x78, 0x00 ), TEXT_XML );
63
    add( ints( 0x23, 0x64, 0x65, 0x66 ), IMAGE_X_BITMAP );
64
    add( ints( 0x21, 0x20, 0x58, 0x50, 0x4D, 0x32 ), IMAGE_X_PIXMAP );
65
    add( ints( 0x2E, 0x73, 0x6E, 0x64 ), AUDIO_SIMPLE );
66
    add( ints( 0x64, 0x6E, 0x73, 0x2E ), AUDIO_SIMPLE );
67
    add( ints( 0x52, 0x49, 0x46, 0x46 ), AUDIO_WAV );
68
    add( ints( 0x50, 0x4B ), APP_ZIP );
69
    add( ints( 0x41, 0x43, -1, -1, -1, -1, 0x00, 0x00, 0x00, 0x00, 0x00 ), APP_ACAD );
70
    add( ints( 0xCA, 0xFE, 0xBA, 0xBE ), APP_JAVA );
71
    add( ints( 0xAC, 0xED ), APP_JAVA_OBJECT );
6972
    //@formatter:on
70
  }
71
72
  private MediaTypeSniffer() {
7373
  }
7474
...
8484
8585
    final var source = new int[]{
86
      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
86
      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
87
    };
8788
8889
    for( int i = 0; i < data.length; i++ ) {
...
193194
    final var magic = new int[ FORMAT_LENGTH ];
194195
    int i = -1;
196
195197
    while( ++i < data.length ) {
196198
      magic[ i ] = data[ i ];
M src/main/java/com/keenwrite/preferences/LocaleProperty.java
3030
   * <p>
3131
   * See
32
   * <a href="https://www.oracle.com/java/technologies/javase/jdk12locales.html">JDK 12 Locales</a>
33
   * for details.
32
   * <a href="https://oracle.com/java/technologies/javase/jdk12locales.html">
33
   * JDK 12 Locales
34
   * </a> for details.
3435
   * </p>
3536
   */
M src/main/java/com/keenwrite/preferences/SimpleTableControl.java
1919
import java.util.ArrayList;
2020
import java.util.Map.Entry;
21
import java.util.concurrent.atomic.AtomicBoolean;
2221
import java.util.function.BiFunction;
2322
import java.util.function.Function;
...
5150
    );
5251
    table.getSelectionModel().setSelectionMode( MULTIPLE );
53
54
    final var inserted = workaround( table );
5552
5653
    final var buttons = new ButtonBar();
5754
    buttons.getButtons().addAll(
5855
      createButton(
5956
        "Add", "PLUS",
6057
        event -> {
6158
          sCounter++;
6259
63
          inserted.set( true );
6460
          model.add( createEntry( "key" + sCounter, "value" + sCounter ) );
6561
        }
...
9490
  private Entry<K, V> createEntry( final String k, final String v ) {
9591
    return new SimpleEntry<>( (K) k, (V) v );
96
  }
97
98
  /**
99
   * TODO: Delete method when bug is fixed. See the
100
   * <a href="https://github.com/dlsc-software-consulting-gmbh/PreferencesFX/issues/413">issue
101
   * tracker</a> for details about the bug.
102
   *
103
   * @param table Add a width listener to correct a slight width change.
104
   * @return A Boolean lock so that the bug fix and "Add" button can
105
   * be used to ensure regular resizes don't interfere with programmatic ones.
106
   */
107
  private AtomicBoolean workaround(
108
    final TableView<Entry<K, V>> table ) {
109
    final var inserted = new AtomicBoolean( true );
110
111
    table.widthProperty().addListener( ( c, o, n ) -> {
112
      if( o != null && n != null
113
        && o.intValue() == n.intValue() - 2
114
        && inserted.getAndSet( false ) ) {
115
        table.setPrefWidth( table.getPrefWidth() - 2 );
116
      }
117
    } );
118
119
    return inserted;
12092
  }
12193
...
167139
   * @return The newly configured column.
168140
   */
169
  private <T> TableColumn<Entry<K, V>, T> createColumn(
170
    final TableView<Entry<K, V>> table,
171
    final Function<Entry<K, V>, T> mapEntry,
172
    final BiFunction<CellEditEvent<Entry<K, V>, T>, Entry<K, V>, Entry<K, V>> creator,
141
  private <T, E extends Entry<K, V>> TableColumn<E, T> createColumn(
142
    final TableView<E> table,
143
    final Function<E, T> mapEntry,
144
    final BiFunction<CellEditEvent<E, T>, E, E> creator,
173145
    final String label,
174146
    final double width
175147
  ) {
176
    final var column = new TableColumn<Entry<K, V>, T>( label );
148
    final var column = new TableColumn<E, T>( label );
177149
178150
    column.setEditable( true );
M src/main/java/com/keenwrite/preferences/Workspace.java
8787
8888
    //@formatter:off
89
    entry( KEY_UI_FONT_EDITOR_NAME, asStringProperty( FONT_NAME_EDITOR_DEFAULT ) ),
90
    entry( KEY_UI_FONT_EDITOR_SIZE, asDoubleProperty( FONT_SIZE_EDITOR_DEFAULT ) ),
91
    entry( KEY_UI_FONT_PREVIEW_NAME, asStringProperty( FONT_NAME_PREVIEW_DEFAULT ) ),
92
    entry( KEY_UI_FONT_PREVIEW_SIZE, asDoubleProperty( FONT_SIZE_PREVIEW_DEFAULT ) ),
93
    entry( KEY_UI_FONT_PREVIEW_MONO_NAME, asStringProperty( FONT_NAME_PREVIEW_MONO_NAME_DEFAULT ) ),
94
    entry( KEY_UI_FONT_PREVIEW_MONO_SIZE, asDoubleProperty( FONT_SIZE_PREVIEW_MONO_SIZE_DEFAULT ) ),
89
    entry(
90
      KEY_UI_FONT_EDITOR_NAME,
91
      asStringProperty( FONT_NAME_EDITOR_DEFAULT )
92
    ),
93
    entry(
94
     KEY_UI_FONT_EDITOR_SIZE,
95
     asDoubleProperty( FONT_SIZE_EDITOR_DEFAULT )
96
    ),
97
    entry(
98
     KEY_UI_FONT_PREVIEW_NAME,
99
     asStringProperty( FONT_NAME_PREVIEW_DEFAULT )
100
    ),
101
    entry(
102
     KEY_UI_FONT_PREVIEW_SIZE,
103
     asDoubleProperty( FONT_SIZE_PREVIEW_DEFAULT )
104
    ),
105
    entry(
106
     KEY_UI_FONT_PREVIEW_MONO_NAME,
107
     asStringProperty( FONT_NAME_PREVIEW_MONO_NAME_DEFAULT )
108
    ),
109
    entry(
110
     KEY_UI_FONT_PREVIEW_MONO_SIZE,
111
     asDoubleProperty( FONT_SIZE_PREVIEW_MONO_SIZE_DEFAULT )
112
    ),
95113
96114
    entry( KEY_UI_WINDOW_X, asDoubleProperty( WINDOW_X_DEFAULT ) ),
...
104122
    entry( KEY_UI_SKIN_CUSTOM, asFileProperty( SKIN_CUSTOM_DEFAULT ) ),
105123
106
    entry( KEY_UI_PREVIEW_STYLESHEET, asFileProperty( PREVIEW_CUSTOM_DEFAULT ) ),
124
    entry(
125
      KEY_UI_PREVIEW_STYLESHEET, asFileProperty( PREVIEW_CUSTOM_DEFAULT )
126
    ),
107127
108128
    entry( KEY_LANGUAGE_LOCALE, asLocaleProperty( LOCALE_DEFAULT ) ),
M src/main/java/com/keenwrite/preview/HtmlPreview.java
7070
   * </p>
7171
   */
72
  private static final String HTML_HEAD =
73
    """
74
      <!doctype html>
75
      <html lang='%s'><head><title> </title><meta charset='utf-8'/>
76
      %s%s%s<style>body{font-family:'%s';font-size: %dpx;}</style>%s</head><body>
77
      """;
72
  private static final String HTML_HEAD = """
73
    <!doctype html>
74
    <html lang='%s'><head><title> </title><meta charset='utf-8'/>
75
    %s%s%s<style>body{font-family:'%s';font-size: %dpx;}</style>%s</head><body>
76
    """;
7877
7978
  private static final String HTML_TAIL = "</body></html>";
...
120119
      mScrollLockButton.setText( getLockText( mScrollLocked ) );
121120
      mScrollLockButton.setMargin( new Insets( 1, 0, 0, 0 ) );
122
      mScrollLockButton.addActionListener( e -> fireScrollLockEvent( !mScrollLocked ) );
121
      mScrollLockButton.addActionListener(
122
        e -> fireScrollLockEvent( !mScrollLocked )
123
      );
123124
124125
      verticalPanel.add( verticalBar, CENTER );
...
146147
  public void handle( final ScrollLockEvent event ) {
147148
    mScrollLocked = event.isLocked();
148
    invokeLater( () -> mScrollLockButton.setText( getLockText( mScrollLocked ) ) );
149
    invokeLater(
150
      () -> mScrollLockButton.setText( getLockText( mScrollLocked ) )
151
    );
149152
  }
150153
...
355358
356359
  @Override
357
  public void componentMoved( final ComponentEvent e ) {}
360
  public void componentMoved( final ComponentEvent e ) { }
358361
359362
  @Override
360
  public void componentShown( final ComponentEvent e ) {}
363
  public void componentShown( final ComponentEvent e ) { }
361364
362365
  @Override
363
  public void componentHidden( final ComponentEvent e ) {}
366
  public void componentHidden( final ComponentEvent e ) { }
364367
365368
  private static String toStylesheetString( final URL url ) {
M src/main/java/com/keenwrite/preview/SmoothImageReplacedElement.java
1414
 */
1515
public final class SmoothImageReplacedElement extends ImageReplacedElement {
16
  private final static Lanczos3Filter FILTER = new Lanczos3Filter();
16
  private static final Lanczos3Filter FILTER = new Lanczos3Filter();
1717
1818
  /**
M src/main/java/com/keenwrite/preview/SvgRasterizer.java
2929
import static com.keenwrite.events.StatusEvent.clue;
3030
import static com.keenwrite.preview.HighQualityRenderingHints.RENDERING_HINTS;
31
import static io.sf.carte.echosvg.bridge.UnitProcessor.createContext;
32
import static io.sf.carte.echosvg.bridge.UnitProcessor.svgHorizontalLengthToUserSpace;
31
import static io.sf.carte.echosvg.bridge.UnitProcessor.*;
3332
import static io.sf.carte.echosvg.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
3433
import static io.sf.carte.echosvg.transcoder.TranscodingHints.Key;
35
import static io.sf.carte.echosvg.transcoder.image.ImageTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER;
34
import static io.sf.carte.echosvg.transcoder.image.ImageTranscoder.*;
3635
import static io.sf.carte.echosvg.util.SVGConstants.SVG_WIDTH_ATTRIBUTE;
3736
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
...
300299
    final var root = document.getDocumentElement();
301300
    final var width = root.getAttribute( SVG_WIDTH_ATTRIBUTE );
302
    
301
303302
    return rasterizeString( xml, INT_FORMAT.parse( width ).intValue() );
304303
  }
M src/main/java/com/keenwrite/preview/images/Lanczos3Filter.java
88
99
public final class Lanczos3Filter implements ResampleFilter {
10
  private final static float PI_FLOAT = (float) Math.PI;
10
  private static final float PI_FLOAT = (float) Math.PI;
1111
1212
  private float sincModified( float value ) {
M src/main/java/com/keenwrite/preview/images/ResampleOp.java
1010
import java.util.concurrent.atomic.AtomicInteger;
1111
12
import static com.keenwrite.preview.images.ConstrainedDimension.createAbsolutionDimension;
12
import static com.keenwrite.preview.images.ConstrainedDimension.*;
1313
import static java.awt.image.BufferedImage.*;
1414
import static java.awt.image.DataBuffer.TYPE_USHORT;
...
141141
    waitForAllThreads( threads );
142142
143
    //noinspection UnusedAssignment
144
    workPixels = null; // free memory
143
    // free memory
144
    // noinspection UnusedAssignment
145
    workPixels = null;
146
145147
    final BufferedImage out;
146
    if( dest != null && dstWidth == dest.getWidth() && dstHeight == dest.getHeight() ) {
148
149
    if( dest != null &&
150
      dstWidth == dest.getWidth() &&
151
      dstHeight == dest.getHeight() ) {
147152
      out = dest;
148153
      int nrDestChannels = ImageUtils.nrChannels( dest );
...
299304
    boolean useChannel3 = nrChannels > 3;
300305
    for( int x = start; x < dstWidth; x += delta ) {
301
      final int xLocation = x * nrChannels;
306
      final int xLoc = x * nrChannels;
302307
      for( int y = dstHeight - 1; y >= 0; y-- ) {
303308
        final int yTimesNumContributors =
...
312317
        int index = yTimesNumContributors;
313318
        for( int j = max - 1; j >= 0; j-- ) {
314
          int valueLocation = verticalSubsamplingData.arrPixel[ index ];
319
          int valueLoc = verticalSubsamplingData.arrPixel[ index ];
315320
          float arrWeight = verticalSubsamplingData.arrWeight[ index ];
316
          sample0 += (workPixels[ valueLocation ][ xLocation ] & 0xff) * arrWeight;
317
          sample1 += (workPixels[ valueLocation ][ xLocation + 1 ] & 0xff) * arrWeight;
318
          sample2 += (workPixels[ valueLocation ][ xLocation + 2 ] & 0xff) * arrWeight;
321
          sample0 += (workPixels[ valueLoc ][ xLoc ] & 0xff) * arrWeight;
322
          sample1 += (workPixels[ valueLoc ][ xLoc + 1 ] & 0xff) * arrWeight;
323
          sample2 += (workPixels[ valueLoc ][ xLoc + 2 ] & 0xff) * arrWeight;
319324
          if( useChannel3 ) {
320
            sample3 += (workPixels[ valueLocation ][ xLocation + 3 ] & 0xff) * arrWeight;
325
            sample3 += (workPixels[ valueLoc ][ xLoc + 3 ] & 0xff) * arrWeight;
321326
          }
322327
M src/main/java/com/keenwrite/processors/XhtmlProcessor.java
3333
 */
3434
public final class XhtmlProcessor extends ExecutorProcessor<String> {
35
  private final static Curler sTypographer =
35
  private static final Curler sTypographer =
3636
    new Curler( createContractions(), FILTER_XML, true );
3737
M src/main/java/com/keenwrite/processors/markdown/BaseMarkdownProcessor.java
88
import com.keenwrite.processors.markdown.extensions.r.RInlineExtension;
99
import com.vladsch.flexmark.ext.definition.DefinitionExtension;
10
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension;
10
import com.vladsch.flexmark.ext.gfm.strikethrough.*;
1111
import com.vladsch.flexmark.ext.superscript.SuperscriptExtension;
1212
import com.vladsch.flexmark.ext.tables.TablesExtension;
M src/main/java/com/keenwrite/processors/markdown/MarkdownProcessor.java
6161
    final Processor<String> processor;
6262
    final Function<String, String> evaluator;
63
    final List<Extension> extensions = new ArrayList<>();
63
    final List<Extension> result = new ArrayList<>();
6464
6565
    if( mediaType == TEXT_R_MARKDOWN ) {
66
      extensions.add( RInlineExtension.create( context ) );
66
      result.add( RInlineExtension.create( context ) );
6767
      processor = IDENTITY;
6868
      evaluator = new RInlineEvaluator( context );
6969
    }
7070
    else {
7171
      processor = new VariableProcessor( IDENTITY, context );
7272
      evaluator = processor;
7373
    }
7474
7575
    // Add typographic, table, strikethrough, and similar extensions.
76
    extensions.addAll( super.createExtensions( context ) );
76
    result.addAll( super.createExtensions( context ) );
7777
78
    extensions.add( ImageLinkExtension.create( context ) );
79
    extensions.add( TeXExtension.create( processor, context ) );
80
    extensions.add( FencedBlockExtension.create( processor, evaluator, context ) );
78
    result.add( ImageLinkExtension.create( context ) );
79
    result.add( TeXExtension.create( processor, context ) );
80
    result.add( FencedBlockExtension.create( processor, evaluator, context ) );
8181
8282
    if( context.isExportFormat( ExportFormat.NONE ) ) {
83
      extensions.add( CaretExtension.create( context ) );
83
      result.add( CaretExtension.create( context ) );
8484
    }
8585
86
    extensions.add( DocumentOutlineExtension.create( processor ) );
87
    return extensions;
86
    result.add( DocumentOutlineExtension.create( processor ) );
87
    return result;
8888
  }
8989
}
M src/main/java/com/keenwrite/processors/markdown/extensions/fences/FencedBlockExtension.java
3636
 */
3737
public final class FencedBlockExtension extends HtmlRendererAdapter {
38
  private final static String TEMP_DIR = System.getProperty( "java.io.tmpdir" );
38
  private static final String TEMP_DIR = System.getProperty( "java.io.tmpdir" );
3939
4040
  /**
4141
   * Ensure that the device is always closed to prevent an out-of-resources
4242
   * error, regardless of whether the R expression the user tries to evaluate
4343
   * is valid by swallowing errors alongside a {@code finally} block.
4444
   */
45
  private final static String R_SVG_EXPORT =
45
  private static final String R_SVG_EXPORT =
4646
    "tryCatch({svg('%s'%s)%n%s%n},finally={dev.off()})%n";
4747
48
  private final static String STYLE_DIAGRAM = "diagram-";
49
  private final static int STYLE_DIAGRAM_LEN = STYLE_DIAGRAM.length();
48
  private static final String STYLE_DIAGRAM = "diagram-";
49
  private static final int STYLE_DIAGRAM_LEN = STYLE_DIAGRAM.length();
5050
51
  private final static String STYLE_R_CHUNK = "{r";
51
  private static final String STYLE_R_CHUNK = "{r";
5252
53
  private final static class VerbatimRVariableProcessor
53
  private static final class VerbatimRVariableProcessor
5454
    extends RVariableProcessor {
5555
M src/main/java/com/keenwrite/processors/r/RBootstrapController.java
2020
public final class RBootstrapController {
2121
22
  private final static RKeyOperator KEY_OPERATOR = new RKeyOperator();
22
  private static final RKeyOperator KEY_OPERATOR = new RKeyOperator();
2323
2424
  private final Workspace mWorkspace;
M src/main/java/com/keenwrite/processors/text/AbstractTextReplacer.java
88
 */
99
public abstract class AbstractTextReplacer implements TextReplacer {
10
  /**
11
   * Optimization: Cache keys until the map changes.
12
   */
13
  private String[] mKeys;
14
15
  /**
16
   * Optimization: Cache values until the map changes.
17
   */
18
  private String[] mValues;
19
20
  /**
21
   * Optimization: Detect when the map changes.
22
   */
23
  private int mMapHash;
1024
1125
  /**
1226
   * Default (empty) constructor.
1327
   */
1428
  protected AbstractTextReplacer() {
1529
  }
1630
1731
  protected String[] keys( final Map<String, String> map ) {
18
    return map.keySet().toArray( new String[ 0 ] );
32
    updateCache( map );
33
34
    return mKeys;
1935
  }
2036
2137
  protected String[] values( final Map<String, String> map ) {
22
    return map.values().toArray( new String[ 0 ] );
38
    updateCache( map );
39
40
    return mValues;
41
  }
42
43
  private void updateCache( final Map<String, String> map ) {
44
    if( map.hashCode() != mMapHash ) {
45
      mKeys = map.keySet().toArray( new String[ 0 ] );
46
      mValues = map.values().toArray( new String[ 0 ] );
47
      mMapHash = map.hashCode();
48
    }
2349
  }
2450
}
M src/main/java/com/keenwrite/processors/text/AhoCorasickReplacer.java
1414
   * Default (empty) constructor.
1515
   */
16
  protected AhoCorasickReplacer() {
17
  }
16
  protected AhoCorasickReplacer() { }
1817
1918
  @Override
2019
  public String replace( final String text, final Map<String, String> map ) {
2120
    // Create a buffer sufficiently large that re-allocations are minimized.
22
    final var sb = new StringBuilder( (int)(text.length() * 1.25) );
21
    final var sb = new StringBuilder( (int) (text.length() * 1.25) );
2322
2423
    // Definition names cannot overlap.
M src/main/java/com/keenwrite/processors/text/StringUtilsReplacer.java
22
package com.keenwrite.processors.text;
33
4
import org.apache.commons.lang3.StringUtils;
5
46
import java.util.Map;
57
68
import static org.apache.commons.lang3.StringUtils.replaceEach;
79
810
/**
9
 * Replaces text using Apache's StringUtils.replaceEach method.
11
 * Replaces text using a brute-force
12
 * {@link StringUtils#replaceEach(String, String[], String[])}} method.
1013
 */
1114
public class StringUtilsReplacer extends AbstractTextReplacer {
1215
1316
  /**
1417
   * Default (empty) constructor.
1518
   */
16
  protected StringUtilsReplacer() {
17
  }
19
  protected StringUtilsReplacer() { }
1820
1921
  @Override
M src/main/java/com/keenwrite/security/PermissiveCertificate.java
1919
   * Create a trust manager that does not validate certificate chains.
2020
   */
21
  private final static TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{
21
  private static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{
2222
    new X509TrustManager() {
2323
      @Override
M src/main/java/com/keenwrite/sigils/RKeyOperator.java
1212
  private static final char KEY_SEPARATOR_R = '$';
1313
14
  private final StringBuilder mVarName = new StringBuilder( 128 );
15
1416
  /**
1517
   * Constructs a new instance capable of converting dot-separated variable
1618
   * names into R's dollar-symbol-separated names.
1719
   */
18
  public RKeyOperator() {}
20
  public RKeyOperator() { }
1921
2022
  /**
...
3739
    assert !key.isBlank();
3840
39
    final var rVarName = new StringBuilder( key.length() + 3 );
40
    rVarName.append( "v" );
41
    rVarName.append( KEY_SEPARATOR_R );
42
    rVarName.append( key );
41
    mVarName.setLength( 0 );
42
43
    //final var rVarName = new StringBuilder( key.length() + 3 );
44
    mVarName.append( "v" );
45
    mVarName.append( KEY_SEPARATOR_R );
46
    mVarName.append( key );
4347
4448
    // The 3 is for v$ + first char, which cannot be a separator.
45
    for( int i = rVarName.length() - 1; i >= 3; i-- ) {
46
      if( rVarName.charAt( i ) == KEY_SEPARATOR_DEF ) {
47
        rVarName.setCharAt( i, KEY_SEPARATOR_R );
49
    for( int i = mVarName.length() - 1; i >= 3; i-- ) {
50
      if( mVarName.charAt( i ) == KEY_SEPARATOR_DEF ) {
51
        mVarName.setCharAt( i, KEY_SEPARATOR_R );
4852
      }
4953
    }
5054
51
    return rVarName.toString();
55
    return mVarName.toString();
5256
  }
5357
}
M src/main/java/com/keenwrite/typesetting/Typesetter.java
244244
245245
    private Path newExtension( final String baseName, final String ext ) {
246
      return getOutputPath().resolveSibling( removeExtension( baseName ) + ext );
246
      final var path = getOutputPath();
247
      return path.resolveSibling( removeExtension( baseName ) + ext );
247248
    }
248249
M src/main/java/com/keenwrite/ui/actions/ApplicationBars.java
1111
import javafx.scene.control.ToolBar;
1212
import org.controlsfx.control.StatusBar;
13
import org.jetbrains.annotations.NotNull;
1314
1415
import java.util.HashMap;
...
3940
   */
4041
  public static MenuBar createMenuBar( final GuiCommands actions ) {
41
    final var SEPARATOR_ACTION = new SeparatorAction();
42
    final var SEPARATOR = new SeparatorAction();
4243
43
    //@formatter:off
4444
    return new MenuBar(
45
    createMenu(
45
      createMenuFile( actions, SEPARATOR ),
46
      createMenuEdit( actions, SEPARATOR ),
47
      createMenuFormat( actions ),
48
      createMenuInsert( actions, SEPARATOR ),
49
      createMenuVariable( actions, SEPARATOR ),
50
      createMenuView( actions, SEPARATOR ),
51
      createMenuHelp( actions )
52
    );
53
  }
54
55
  @NotNull
56
  private static Menu createMenuFile(
57
    final GuiCommands actions, final SeparatorAction SEPARATOR ) {
58
    // @formatter:off
59
    return createMenu(
4660
      get( "Main.menu.file" ),
4761
      addAction( "file.new", e -> actions.file_new() ),
4862
      addAction( "file.open", e -> actions.file_open() ),
49
      SEPARATOR_ACTION,
63
      SEPARATOR,
5064
      addAction( "file.close", e -> actions.file_close() ),
5165
      addAction( "file.close_all", e -> actions.file_close_all() ),
52
      SEPARATOR_ACTION,
66
      SEPARATOR,
5367
      addAction( "file.save", e -> actions.file_save() ),
5468
      addAction( "file.save_as", e -> actions.file_save_as() ),
5569
      addAction( "file.save_all", e -> actions.file_save_all() ),
56
      SEPARATOR_ACTION,
57
      addAction( "file.export", e -> {} )
70
      SEPARATOR,
71
      addAction( "file.export", e -> { } )
5872
        .addSubActions(
5973
          addAction( "file.export.pdf", e -> actions.file_export_pdf() ),
6074
          addAction( "file.export.pdf.dir", e -> actions.file_export_pdf_dir() ),
6175
          addAction( "file.export.html_svg", e -> actions.file_export_html_svg() ),
6276
          addAction( "file.export.html_tex", e -> actions.file_export_html_tex() ),
6377
          addAction( "file.export.xhtml_tex", e -> actions.file_export_xhtml_tex() )
6478
        ),
65
      SEPARATOR_ACTION,
79
      SEPARATOR,
6680
      addAction( "file.exit", e -> actions.file_exit() )
67
    ),
68
    createMenu(
81
    );
82
    // @formatter:on
83
  }
84
85
  @NotNull
86
  private static Menu createMenuEdit(
87
    final GuiCommands actions, final SeparatorAction SEPARATOR ) {
88
    return createMenu(
6989
      get( "Main.menu.edit" ),
70
      SEPARATOR_ACTION,
90
      SEPARATOR,
7191
      addAction( "edit.undo", e -> actions.edit_undo() ),
7292
      addAction( "edit.redo", e -> actions.edit_redo() ),
73
      SEPARATOR_ACTION,
93
      SEPARATOR,
7494
      addAction( "edit.cut", e -> actions.edit_cut() ),
7595
      addAction( "edit.copy", e -> actions.edit_copy() ),
7696
      addAction( "edit.paste", e -> actions.edit_paste() ),
7797
      addAction( "edit.select_all", e -> actions.edit_select_all() ),
78
      SEPARATOR_ACTION,
98
      SEPARATOR,
7999
      addAction( "edit.find", e -> actions.edit_find() ),
80100
      addAction( "edit.find_next", e -> actions.edit_find_next() ),
81101
      addAction( "edit.find_prev", e -> actions.edit_find_prev() ),
82
      SEPARATOR_ACTION,
102
      SEPARATOR,
83103
      addAction( "edit.preferences", e -> actions.edit_preferences() )
84
    ),
85
    createMenu(
104
    );
105
  }
106
107
  @NotNull
108
  private static Menu createMenuFormat( final GuiCommands actions ) {
109
    return createMenu(
86110
      get( "Main.menu.format" ),
87111
      addAction( "format.bold", e -> actions.format_bold() ),
88112
      addAction( "format.italic", e -> actions.format_italic() ),
89113
      addAction( "format.monospace", e -> actions.format_monospace() ),
90114
      addAction( "format.superscript", e -> actions.format_superscript() ),
91115
      addAction( "format.subscript", e -> actions.format_subscript() ),
92116
      addAction( "format.strikethrough", e -> actions.format_strikethrough() )
93
    ),
94
    createMenu(
117
    );
118
  }
119
120
  @NotNull
121
  private static Menu createMenuInsert(
122
    final GuiCommands actions,
123
    final SeparatorAction SEPARATOR ) {
124
    // @formatter:off
125
    return createMenu(
95126
      get( "Main.menu.insert" ),
96127
      addAction( "insert.blockquote", e -> actions.insert_blockquote() ),
97128
      addAction( "insert.code", e -> actions.insert_code() ),
98129
      addAction( "insert.fenced_code_block", e -> actions.insert_fenced_code_block() ),
99
      SEPARATOR_ACTION,
130
      SEPARATOR,
100131
      addAction( "insert.link", e -> actions.insert_link() ),
101132
      addAction( "insert.image", e -> actions.insert_image() ),
102
      SEPARATOR_ACTION,
133
      SEPARATOR,
103134
      addAction( "insert.heading_1", e -> actions.insert_heading_1() ),
104135
      addAction( "insert.heading_2", e -> actions.insert_heading_2() ),
105136
      addAction( "insert.heading_3", e -> actions.insert_heading_3() ),
106
      SEPARATOR_ACTION,
137
      SEPARATOR,
107138
      addAction( "insert.unordered_list", e -> actions.insert_unordered_list() ),
108139
      addAction( "insert.ordered_list", e -> actions.insert_ordered_list() ),
109140
      addAction( "insert.horizontal_rule", e -> actions.insert_horizontal_rule() )
110
    ),
111
    createMenu(
141
    );
142
    // @formatter:on
143
  }
144
145
  @NotNull
146
  private static Menu createMenuVariable(
147
    final GuiCommands actions, final SeparatorAction SEPARATOR ) {
148
    return createMenu(
112149
      get( "Main.menu.definition" ),
113150
      addAction( "definition.insert", e -> actions.definition_autoinsert() ),
114
      SEPARATOR_ACTION,
151
      SEPARATOR,
115152
      addAction( "definition.create", e -> actions.definition_create() ),
116153
      addAction( "definition.rename", e -> actions.definition_rename() ),
117154
      addAction( "definition.delete", e -> actions.definition_delete() )
118
    ),
119
    createMenu(
155
    );
156
  }
157
158
  @NotNull
159
  private static Menu createMenuView(
160
    final GuiCommands actions, final SeparatorAction SEPARATOR ) {
161
    return createMenu(
120162
      get( "Main.menu.view" ),
121163
      addAction( "view.refresh", e -> actions.view_refresh() ),
122
      SEPARATOR_ACTION,
164
      SEPARATOR,
123165
      addAction( "view.preview", e -> actions.view_preview() ),
124166
      addAction( "view.outline", e -> actions.view_outline() ),
125
      addAction( "view.statistics", e-> actions.view_statistics() ),
126
      addAction( "view.files", e-> actions.view_files() ),
127
      SEPARATOR_ACTION,
167
      addAction( "view.statistics", e -> actions.view_statistics() ),
168
      addAction( "view.files", e -> actions.view_files() ),
169
      SEPARATOR,
128170
      addAction( "view.menubar", e -> actions.view_menubar() ),
129171
      addAction( "view.toolbar", e -> actions.view_toolbar() ),
130172
      addAction( "view.statusbar", e -> actions.view_statusbar() ),
131
      SEPARATOR_ACTION,
173
      SEPARATOR,
132174
      addAction( "view.log", e -> actions.view_log() )
133
    ),
134
    createMenu(
175
    );
176
  }
177
178
  @NotNull
179
  private static Menu createMenuHelp( final GuiCommands actions ) {
180
    return createMenu(
135181
      get( "Main.menu.help" ),
136182
      addAction( "help.about", e -> actions.help_about() )
137
    ) );
138
    //@formatter:on
183
    );
139184
  }
140185
141186
  public static Node createToolBar() {
142
    final var SEPARATOR_ACTION = new SeparatorAction();
187
    final var SEPARATOR = new SeparatorAction();
143188
144189
    return createToolBar(
145190
      getAction( "file.new" ),
146191
      getAction( "file.open" ),
147192
      getAction( "file.save" ),
148
      SEPARATOR_ACTION,
193
      SEPARATOR,
149194
      getAction( "file.export.pdf" ),
150
      SEPARATOR_ACTION,
195
      SEPARATOR,
151196
      getAction( "edit.undo" ),
152197
      getAction( "edit.redo" ),
153198
      getAction( "edit.cut" ),
154199
      getAction( "edit.copy" ),
155200
      getAction( "edit.paste" ),
156
      SEPARATOR_ACTION,
201
      SEPARATOR,
157202
      getAction( "format.bold" ),
158203
      getAction( "format.italic" ),
159204
      getAction( "format.superscript" ),
160205
      getAction( "format.subscript" ),
161206
      getAction( "insert.blockquote" ),
162207
      getAction( "insert.code" ),
163208
      getAction( "insert.fenced_code_block" ),
164
      SEPARATOR_ACTION,
209
      SEPARATOR,
165210
      getAction( "insert.link" ),
166211
      getAction( "insert.image" ),
167
      SEPARATOR_ACTION,
212
      SEPARATOR,
168213
      getAction( "insert.heading_1" ),
169
      SEPARATOR_ACTION,
214
      SEPARATOR,
170215
      getAction( "insert.unordered_list" ),
171216
      getAction( "insert.ordered_list" )
M src/main/java/com/keenwrite/util/AlphanumComparator.java
114114
        if( (result = thisChunkLength - thatChunkLength) == 0 ) {
115115
          for( var i = 0; i < thisChunkLength; i++ ) {
116
            if( (result = thisChunk.charAt( i ) - thatChunk.charAt( i )) != 0 ) {
116
            final var diff = thisChunk.charAt( i ) - thatChunk.charAt( i );
117
            result = diff;
118
119
            if( result != 0 ) {
117120
              return result;
118121
            }
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;
11
12
  requires batik.anim;
13
  requires batik.bridge;
14
  requires batik.css;
15
  requires batik.gvt;
16
  requires batik.transcoder;
17
  requires batik.util;
18
19
  requires com.dlsc.formsfx;
20
  requires transitive com.dlsc.preferencesfx;
21
  requires com.fasterxml.jackson.databind;
22
  requires transitive com.fasterxml.jackson.dataformat.yaml;
23
24
  requires flexmark;
25
  requires flexmark.util.data;
26
  requires flexmark.util.sequence;
27
28
  requires keenquotes;
29
  requires keentex;
30
  requires tokenize;
31
32
  requires org.apache.commons.lang3;
33
  requires org.jsoup;
34
  requires org.controlsfx.controls;
35
  requires org.fxmisc.flowless;
36
  requires org.fxmisc.richtext;
37
  requires org.fxmisc.undo;
38
39
  requires commons.io;
40
  requires eventbus.java;
41
  requires flying.saucer.core;
42
  requires info.picocli;
43
  requires jsymspell;
44
  requires plexus.utils;
45
  requires tiwulfx.dock;
46
  requires wellbehavedfx;
47
  requires xml.apis.ext;
48
  requires java.logging;
49
}
50
151
M src/test/java/com/keenwrite/io/FileObjectTest.java
1616
 */
1717
public class FileObjectTest {
18
  private final static String TEMP_DIR = System.getProperty( "java.io.tmpdir" );
18
  private static final String TEMP_DIR = System.getProperty( "java.io.tmpdir" );
1919
2020
  /**
M src/test/java/com/keenwrite/preview/DiagramUrlGeneratorTest.java
1212
class DiagramUrlGeneratorTest {
1313
  // @formatter:off
14
  private final static String[] DIAGRAMS = new String[]{
14
  private static final String[] DIAGRAMS = new String[]{
1515
    "graphviz",
1616
    "digraph G {Hello->World; World->Hello;}",
M src/test/java/com/keenwrite/richtext/StyleClassedTextAreaTest.java
5252
  }
5353
54
  private final static String TEXT = """
54
  private static final String TEXT = """
5555
    In my younger and more vulnerable years my father gave me some advice
5656
    that I’ve been turning over in my mind ever since.